Skip to content

Commit e3a938c

Browse files
authored
Merge pull request #78 from roydejong/classic-update
Classic BSSB updates
2 parents 6f43bde + 9226022 commit e3a938c

42 files changed

Lines changed: 1124 additions & 637 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ATTRIBUTION.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,9 @@ SOFTWARE.
5151
- `Assets/Sprites/Robot.png`
5252

5353
[Icons by Microsoft](https://www.iconfinder.com/iconsets/fluent-solid-24px-vol-1), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
54+
55+
## Social Relationship icon pack by Freepik
56+
57+
- `Assets/Sprites/SocialNetwork.png`
58+
59+
[Social network icons created by Freepik - Flaticon](https://www.flaticon.com/free-icons/social-network), under Flaticon License.

Assets/Sprites.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ internal static class Sprites
5858
/// Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
5959
public static Sprite? Robot;
6060

61+
/// Social network icon
62+
/// Social Relationship icon pack by Freepik (https://www.flaticon.com/packs/social-relationship-7)
63+
/// Under Flaticon License
64+
public static Sprite? SocialNetwork;
65+
6166
public static bool IsInitialized { get; private set; }
6267

6368
public static void Initialize()
@@ -74,6 +79,7 @@ public static void Initialize()
7479
Portal = LoadSpriteFromResources("ServerBrowser.Assets.Sprites.Portal.png");
7580
PortalUser = LoadSpriteFromResources("ServerBrowser.Assets.Sprites.PortalUser.png");
7681
Robot = LoadSpriteFromResources("ServerBrowser.Assets.Sprites.Robot.png");
82+
SocialNetwork = LoadSpriteFromResources("ServerBrowser.Assets.Sprites.SocialNetwork.png");
7783
}
7884

7985
private static Sprite? LoadSpriteFromResources(string resourcePath, float pixelsPerUnit = 100.0f)

Assets/Sprites/Portal.png

-4.19 KB
Loading

Assets/Sprites/PortalUser.png

-9.75 KB
Loading

Assets/Sprites/SocialNetwork.png

4.97 KB
Loading

Core/BssbBrowser.cs

Lines changed: 144 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Net;
25
using System.Threading;
36
using System.Threading.Tasks;
7+
using ServerBrowser.Models;
48
using ServerBrowser.Models.Requests;
59
using ServerBrowser.Models.Responses;
10+
using ServerBrowser.Network.Discovery;
611
using SiraUtil.Logging;
12+
using UnityEngine;
713
using Zenject;
814

915
namespace ServerBrowser.Core
@@ -12,98 +18,186 @@ namespace ServerBrowser.Core
1218
/// Utility for browsing paginated lobbies on the Server Browser API.
1319
/// </summary>
1420
// ReSharper disable once ClassNeverInstantiated.Global
15-
public class BssbBrowser
21+
public class BssbBrowser : IInitializable, IDisposable
1622
{
17-
public const int DefaultPageSize = 6;
18-
1923
[Inject] private readonly SiraLog _log = null!;
2024
[Inject] private readonly BssbApiClient _apiClient = null!;
21-
22-
public BrowseQueryParams QueryParams = new();
23-
public BrowseResponse? PageData { get; private set; }
24-
25+
[Inject] private readonly DiscoveryClient _discoveryClient = null!;
26+
2527
/// <summary>
2628
/// This event is raised when loading a page has finished or failed.
2729
/// </summary>
2830
public event EventHandler? UpdateEvent;
29-
30-
private CancellationTokenSource? _loadingCts;
31-
private int _pageIndex;
31+
32+
public BrowseQueryParams QueryParams = new();
33+
public readonly Dictionary<string, BssbServer> AllServers = new();
34+
public string? MessageOfTheDay { get; private set; } = null;
3235

3336
public bool IsLoading { get; private set; }
34-
public bool LoadingErrored { get; private set; }
37+
public bool ApiRequestFailed { get; private set; }
3538

39+
private CancellationTokenSource? _loadingCts;
40+
private readonly Dictionary<string, float> _discoveryResponseAges = new();
41+
42+
private const int DiscoveryTimeoutSeconds = 30; // discovery packets are sent every 5 secs
43+
44+
#region Init
45+
46+
public void Initialize()
47+
{
48+
_discoveryClient.ResponseReceived += HandleDiscoveryResponse;
49+
}
50+
51+
public void Dispose()
52+
{
53+
_discoveryClient.Dispose();
54+
_discoveryClient.ResponseReceived -= HandleDiscoveryResponse;
55+
}
56+
57+
#endregion
58+
59+
#region Refresh API
60+
3661
public async Task ResetRefresh()
3762
{
3863
CancelLoading();
39-
_pageIndex = 0;
4064
await Refresh();
4165
}
4266

4367
public async Task Refresh()
4468
{
45-
CancelLoading();
46-
TriggerUpdate(true);
47-
48-
// Calculate pagination offset
49-
var pageSize = PageData?.PageSize ?? DefaultPageSize;
50-
QueryParams.Offset = (_pageIndex * pageSize);
51-
52-
// Query API (load page)
5369
try
5470
{
55-
PageData = await _apiClient.Browse(QueryParams, _loadingCts!.Token);
71+
CancelLoading();
72+
TriggerUpdate(true);
73+
74+
// Query API (load page)
75+
BrowseResponse? apiResult = null;
76+
77+
try
78+
{
79+
apiResult = await _apiClient.Browse(QueryParams, _loadingCts!.Token);
80+
81+
if (apiResult is null)
82+
_log.Warn($"Browser API request failed (request error, or invalid response)");
83+
else if (apiResult.Servers == null)
84+
_log.Warn($"Browser API sent null server list");
85+
else
86+
ProcessApiResponse(apiResult);
87+
}
88+
catch (TaskCanceledException)
89+
{
90+
_log.Info($"Browser API request cancelled");
91+
}
92+
93+
// Trigger update
94+
TriggerUpdate(false, apiResult == null);
5695
}
57-
catch (TaskCanceledException)
96+
catch (Exception ex)
5897
{
59-
PageData = null;
98+
_log.Error($"Browser refresh failed: {ex}");
99+
TriggerUpdate(false, true);
60100
}
101+
}
61102

62-
if (PageData is not null)
63-
_log.Debug($"BrowseData loaded page (Index={_pageIndex}, TotalCount={PageData.TotalResultCount}, " +
64-
$"Limit={PageData.PageSize}, LobbiesCount={PageData.Servers?.Count ?? 0}, " +
65-
$"MOTD={PageData.MessageOfTheDay})");
66-
else
67-
_log.Error($"BrowseData page load failed - received null response (Index={_pageIndex})");
68-
69-
// Trigger update
70-
TriggerUpdate(false, (PageData == null));
103+
public void CancelLoading()
104+
{
105+
_loadingCts?.Cancel();
106+
_loadingCts?.Dispose();
107+
_loadingCts = new();
71108
}
72109

73-
public async Task PageUp()
110+
public void EnableDiscovery()
74111
{
75-
if (_pageIndex <= 0)
76-
return;
77-
78-
_pageIndex--;
79-
await Refresh();
112+
_discoveryClient.StartBroadcast();
80113
}
81114

82-
public async Task PageDown()
115+
public void DisableDiscovery()
83116
{
84-
_pageIndex++;
85-
await Refresh();
117+
_discoveryClient.StopBroadcast();
86118
}
119+
120+
#endregion
87121

88-
public void CancelLoading()
122+
#region Refresh data handlers
123+
124+
private void ProcessApiResponse(BrowseResponse apiResponse)
89125
{
90-
_loadingCts?.Cancel();
91-
_loadingCts?.Dispose();
126+
MessageOfTheDay = apiResponse.MessageOfTheDay;
127+
128+
if (apiResponse.Servers == null)
129+
return;
92130

93-
_loadingCts = new();
131+
// Remove stale discovery responses
132+
var localDiscoveryKeys = new HashSet<string>(_discoveryResponseAges.Keys);
133+
134+
foreach (var discoveryKey in localDiscoveryKeys)
135+
{
136+
var timeDiff = Time.realtimeSinceStartup - _discoveryResponseAges[discoveryKey];
137+
if (timeDiff < DiscoveryTimeoutSeconds)
138+
continue;
139+
140+
// Timed out
141+
AllServers.Remove(discoveryKey);
142+
_discoveryResponseAges.Remove(discoveryKey);
143+
}
94144

95-
if (IsLoading)
145+
// Add or update servers, index missing servers
146+
var keysMissing = new HashSet<string>(AllServers.Keys);
147+
148+
foreach (var server in apiResponse.Servers)
96149
{
97-
TriggerUpdate(false, true);
150+
AllServers[server.Key] = server;
151+
keysMissing.Remove(server.Key);
152+
}
153+
154+
// Remove servers not in API response
155+
foreach (var key in keysMissing)
156+
{
157+
if (localDiscoveryKeys.Contains(key))
158+
// Don't remove
159+
continue;
160+
161+
AllServers.Remove(key);
98162
}
99163
}
164+
165+
private void HandleDiscoveryResponse(DiscoveryResponsePacket response, IPEndPoint source)
166+
{
167+
var serverData = response.ToServerData(source);
168+
169+
AllServers[serverData.Key] = serverData;
170+
_discoveryResponseAges[serverData.Key] = Time.realtimeSinceStartup;
171+
172+
TriggerUpdate(IsLoading);
173+
}
174+
175+
#endregion
100176

101-
private void TriggerUpdate(bool isLoading, bool didError = false)
177+
#region Events
178+
179+
private void TriggerUpdate(bool isLoading, bool apiRequestFailed = false)
102180
{
103181
IsLoading = isLoading;
104-
LoadingErrored = didError;
105-
182+
ApiRequestFailed = apiRequestFailed;
183+
106184
UpdateEvent?.Invoke(this, EventArgs.Empty);
107185
}
186+
187+
#endregion
188+
189+
#region Sort helper
190+
191+
public List<BssbServer> GetAllServersSorted()
192+
{
193+
return AllServers.Values
194+
// Primary sort: preference, our guess on how likely the player wants to see/join it
195+
.OrderByDescending(server => server.PreferentialSortScore)
196+
// Secondary sort: prefer newer lobbies
197+
.ThenByDescending(server => server.ReadOnlyFirstSeen)
198+
.ToList();
199+
}
200+
201+
#endregion
108202
}
109203
}

Core/BssbDataCollector.cs

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Linq;
33
using IgnoranceCore;
4-
using IPA.Utilities;
54
using MultiplayerCore.Players;
65
using ServerBrowser.Models;
76
using ServerBrowser.Models.Enums;
@@ -74,13 +73,13 @@ private void HandleSessionConnected()
7473
}
7574

7675
if (Current.IsBeatTogetherHost)
77-
_log.Debug("Detected a BeatTogether host");
76+
_log.Info("Detected a BeatTogether host");
7877
else if (Current.IsBeatUpServerHost)
79-
_log.Debug("Detected a BeatUpServer host");
80-
else if (Current.IsBeatDediHost)
81-
_log.Debug("Detected a BeatDedi host");
78+
_log.Info("Detected a BeatUpServer host");
79+
else if (Current.IsBeatNetHost)
80+
_log.Info("Detected a BeatNet host");
8281
else if (Current.IsAwsGameLiftHost)
83-
_log.Debug("Detected an Amazon GameLift host");
82+
_log.Info("Detected an Amazon GameLift host");
8483

8584
Current.ServerTypeCode = DetermineServerType();
8685

@@ -168,8 +167,8 @@ private string DetermineServerType()
168167
if (Current.IsBeatUpServerHost)
169168
return Current.IsQuickPlay ? "beatupserver_quickplay" : "beatupserver_dedicated";
170169

171-
if (Current.IsBeatDediHost)
172-
return Current.IsQuickPlay ? "beatdedi_quickplay" : "beatdedi_custom";
170+
if (Current.IsBeatNetHost)
171+
return Current.IsQuickPlay ? "beatnet_quickplay" : "beatnet_custom";
173172

174173
return "unknown";
175174
}
@@ -182,14 +181,16 @@ private string DetermineServerType()
182181
[AffinityPostfix]
183182
[AffinityPatch(typeof(GameLiftConnectionManager), "HandleConnectToServerSuccess")]
184183
private void HandleGameLiftPreConnect(string playerSessionId, string hostName, int port, string gameSessionId,
185-
string secret, string code, BeatmapLevelSelectionMask selectionMask,
186-
GameplayServerConfiguration configuration)
184+
string secret, string code, BeatmapLevelSelectionMask selectionMask, GameplayServerConfiguration configuration)
187185
{
188186
// nb: HandleConnectToServerSuccess means handshake is complete, and we are about to reconnect to the
189187
// dedicated server for the actual multiplayer session - we're not yet actually successfully connected.
190188

191189
_log.Info($"Game will connect to GameLift session (playerSessionId={playerSessionId}, "
192-
+ $"hostName={hostName}, port={port}, gameSessionId={gameSessionId}, secret={secret}, code={code}, "
190+
+ $"hostName={hostName}, "
191+
+ $"port={port}, "
192+
+ $"gameSessionId/remoteUserId={gameSessionId}, "
193+
+ $"secret={secret}, code={code}, "
193194
+ $"maxPlayerCount={configuration.maxPlayerCount}, "
194195
+ $"discoveryPolicy={configuration.discoveryPolicy}, "
195196
+ $"gameplayServerMode={configuration.gameplayServerMode}, "
@@ -232,22 +233,27 @@ private void HandleGameLiftPreConnect(string playerSessionId, string hostName, i
232233
[AffinityPostfix]
233234
[AffinityPatch(typeof(LobbyGameStateController), "StartMultiplayerLevel")]
234235
private void HandleStartMultiplayerLevel(ILevelGameplaySetupData gameplaySetupData,
235-
IDifficultyBeatmap? difficultyBeatmap, Action beforeSceneSwitchCallback)
236+
IBeatmapLevelData beatmapLevelData, Action beforeSceneSwitchCallback, LobbyGameStateController __instance)
236237
{
237-
var previewBeatmapLevel = gameplaySetupData.beatmapLevel.beatmapLevel;
238-
var beatmapDifficulty = gameplaySetupData.beatmapLevel.beatmapDifficulty;
239-
var beatmapCharacteristic = gameplaySetupData.beatmapLevel.beatmapCharacteristic;
238+
var levelId = gameplaySetupData.beatmapKey.levelId;
239+
var beatmapLevel = __instance._beatmapLevelsModel.GetBeatmapLevel(levelId)!;
240+
241+
var beatmapDifficulty = gameplaySetupData.beatmapKey.difficulty;
242+
var beatmapCharacteristic = gameplaySetupData.beatmapKey.beatmapCharacteristic;
240243
var gameplayModifiers = gameplaySetupData.gameplayModifiers;
241244

242-
_log.Info($"Starting multiplayer level (levelID={previewBeatmapLevel.levelID}, " +
243-
$"songName={previewBeatmapLevel.songName}, songSubName={previewBeatmapLevel.songSubName}, " +
244-
$"songAuthorName={previewBeatmapLevel.songAuthorName}, " +
245-
$"levelAuthorName={previewBeatmapLevel.levelAuthorName}, " +
246-
$"difficulty={beatmapDifficulty}, characteristic={beatmapCharacteristic}, " +
247-
$"modifiers={gameplayModifiers})");
248-
249-
Current.Level = BssbServerLevel.FromLevelStartData(previewBeatmapLevel, beatmapDifficulty, difficultyBeatmap,
250-
gameplayModifiers, beatmapCharacteristic.serializedName);
245+
_log.Info($"Starting multiplayer level (" +
246+
$"levelID={levelId}, " +
247+
$"songName={beatmapLevel.songName}, " +
248+
$"songSubName={beatmapLevel.songSubName}, " +
249+
$"songAuthorName={beatmapLevel.songAuthorName}, " +
250+
$"difficulty={beatmapDifficulty}, " +
251+
$"characteristic={beatmapCharacteristic}, " +
252+
$"modifiers={gameplayModifiers}" +
253+
$")");
254+
255+
Current.Level = BssbServerLevel.FromLevelStartData(beatmapLevel, beatmapDifficulty, gameplayModifiers,
256+
beatmapCharacteristic.serializedName);
251257

252258
if (Current.Level.Difficulty.HasValue && Current.LobbyDifficulty != BssbDifficulty.All)
253259
Current.LobbyDifficulty = Current.Level.Difficulty.Value.ToBssbDifficulty();
@@ -308,7 +314,7 @@ private void HandleSetPlayerIsPartyOwner(string userId, bool isPartyOwner)
308314
private void HandleSongStartSync(MultiplayerPlayerStartState localPlayerSyncState,
309315
MultiplayerController __instance)
310316
{
311-
var sessionGameId = __instance.GetField<string, MultiplayerController>("_sessionGameId");
317+
var sessionGameId = __instance._sessionGameId;
312318

313319
_log.Info($"Multiplayer song started (sessionGameId={sessionGameId}, " +
314320
$"localPlayerSyncState={localPlayerSyncState})");

0 commit comments

Comments
 (0)