diff --git a/MultiplayerExtensions/Beatmaps/PreviewBeatmapStub.cs b/MultiplayerExtensions/Beatmaps/PreviewBeatmapStub.cs index 673fa55..730a1da 100644 --- a/MultiplayerExtensions/Beatmaps/PreviewBeatmapStub.cs +++ b/MultiplayerExtensions/Beatmaps/PreviewBeatmapStub.cs @@ -91,9 +91,9 @@ public PreviewBeatmapStub(PreviewBeatmapPacket packet) _rawCoverTask = Task.FromResult(packet.coverImage); } - public PreviewBeatmapStub(Beatmap bm) + public PreviewBeatmapStub(string levelID, Beatmap bm) { - this.levelID = bm.ID; + this.levelID = levelID; this.levelHash = bm.Hash; this.beatmap = bm; diff --git a/MultiplayerExtensions/Environments/LobbyPlaceManager.cs b/MultiplayerExtensions/Environments/LobbyPlaceManager.cs index 5dc0da0..a8ebd63 100644 --- a/MultiplayerExtensions/Environments/LobbyPlaceManager.cs +++ b/MultiplayerExtensions/Environments/LobbyPlaceManager.cs @@ -53,7 +53,10 @@ public void SetCenterScreenScale() float angleBetweenPlayersWithEvenAdjustment = MultiplayerPlayerPlacement.GetAngleBetweenPlayersWithEvenAdjustment(_lobbyStateDataModel.maxPartySize, MultiplayerPlayerLayout.Circle); float outerCircleRadius = Mathf.Max(MultiplayerPlayerPlacement.GetOuterCircleRadius(angleBetweenPlayersWithEvenAdjustment, innerCircleRadius), minOuterCircleRadius); float scaleRatio = outerCircleRadius / minOuterCircleRadius; - MultiplayerLobbyCenterStageManager centerscreen = Resources.FindObjectsOfTypeAll().First(); + MultiplayerLobbyCenterStageManager[] centerscreens = Resources.FindObjectsOfTypeAll(); + if (centerscreens.Length == 0) + return; + MultiplayerLobbyCenterStageManager centerscreen = centerscreens.First(); centerscreen.transform.localScale = new Vector3(scaleRatio, scaleRatio, scaleRatio); } diff --git a/MultiplayerExtensions/HarmonyPatches/CustomSongsPatches.cs b/MultiplayerExtensions/HarmonyPatches/CustomSongsPatches.cs index d6ad64b..7f04821 100644 --- a/MultiplayerExtensions/HarmonyPatches/CustomSongsPatches.cs +++ b/MultiplayerExtensions/HarmonyPatches/CustomSongsPatches.cs @@ -8,7 +8,7 @@ namespace MultiplayerExtensions.HarmonyPatches { [HarmonyPatch(typeof(MultiplayerLevelSelectionFlowCoordinator), "enableCustomLevels", MethodType.Getter)] - public class EnableCustomLevelsPatch + internal class EnableCustomLevelsPatch { /// /// Overrides getter for @@ -20,8 +20,20 @@ static bool Prefix(ref bool __result) } } + [HarmonyPatch(typeof(HostLobbySetupViewController), "SetPlayersMissingLevelText", MethodType.Normal)] + internal class MissingLevelStartPatch + { + /// + /// Disables starting of game if not all players have song. + /// + static void Prefix(HostLobbySetupViewController __instance, string playersMissingLevelText) + { + __instance.SetStartGameEnabled(playersMissingLevelText == null, HostLobbySetupViewController.CannotStartGameReason.None); + } + } + [HarmonyPatch(typeof(NetworkPlayerEntitlementChecker), "GetEntitlementStatus", MethodType.Normal)] - public class CustomLevelEntitlementPatch + internal class CustomLevelEntitlementPatch { /// /// Changes the return value of the entitlement checker if it is a custom song. @@ -32,7 +44,7 @@ static bool Prefix(string levelId, ref Task __result) if (hash == null) return true; - if (SongCore.Loader.GetLevelByHash(hash) != null) + if (SongCore.Collections.songWithHashPresent(hash)) __result = Task.FromResult(EntitlementsStatus.Ok); else __result = Plugin.BeatSaver.Hash(hash).ContinueWith(r => @@ -48,7 +60,7 @@ static bool Prefix(string levelId, ref Task __result) } [HarmonyPatch(typeof(NetworkPlayerEntitlementChecker), "GetPlayerLevelEntitlementsAsync", MethodType.Normal)] - public class StartGameLevelEntitlementPatch + internal class StartGameLevelEntitlementPatch { /// /// Changes the return value if it returns 'NotDownloaded' so that the host can start the game. diff --git a/MultiplayerExtensions/HarmonyPatches/HarmonyManager.cs b/MultiplayerExtensions/HarmonyPatches/HarmonyManager.cs index e03e96f..ba20158 100644 --- a/MultiplayerExtensions/HarmonyPatches/HarmonyManager.cs +++ b/MultiplayerExtensions/HarmonyPatches/HarmonyManager.cs @@ -40,6 +40,7 @@ static HarmonyManager() AddDefaultPatch(); AddDefaultPatch(); AddDefaultPatch(); + AddDefaultPatch(); //AddDefaultPatch(); (doesn't support generics) } diff --git a/MultiplayerExtensions/HarmonyPatches/NetworkingPatches.cs b/MultiplayerExtensions/HarmonyPatches/NetworkingPatches.cs index 8f5d201..673e86a 100644 --- a/MultiplayerExtensions/HarmonyPatches/NetworkingPatches.cs +++ b/MultiplayerExtensions/HarmonyPatches/NetworkingPatches.cs @@ -1,6 +1,8 @@ using HarmonyLib; +using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Reflection.Emit; using UnityEngine; @@ -57,24 +59,27 @@ internal static bool Prefix() } } - [HarmonyPatch(typeof(ConnectedPlayerManager), "FlushUnreliableQueue", MethodType.Normal)] + [HarmonyPatch(typeof(ConnectedPlayerManager), "PollUpdate", MethodType.Normal)] internal class UpdateUnreliableFrequencyPatch { - private static float nextTime = 0f; - private static float frequency = 0.1f; + private static readonly MethodInfo _updateUnreliableMethod = typeof(ConnectedPlayerManager).GetMethod("FlushUnreliableQueue", BindingFlags.NonPublic | BindingFlags.Instance); - internal static bool Prefix() + internal static IEnumerable Transpiler(IEnumerable instructions) { - if (Time.time > nextTime) + var codes = instructions.ToList(); + for (int i = 0; i < codes.Count; i++) { - nextTime = Time.time + frequency; - return true; + if (codes[i].opcode == OpCodes.Ldarg_0 && codes[i+1].Calls(_updateUnreliableMethod)) + { + codes.RemoveRange(i, 2); + } } - return false; + + return codes.AsEnumerable(); } } - //Make this work with harmony manager + // TODO: Make this work with harmony manager [HarmonyPatch(typeof(ConnectedPlayerManager), "SendUnreliable", MethodType.Normal)] internal class RemoveByteLimitPatch { diff --git a/MultiplayerExtensions/Installers/MultiplayerInstaller.cs b/MultiplayerExtensions/Installers/MPCoreInstaller.cs similarity index 96% rename from MultiplayerExtensions/Installers/MultiplayerInstaller.cs rename to MultiplayerExtensions/Installers/MPCoreInstaller.cs index 2107980..065a6c1 100644 --- a/MultiplayerExtensions/Installers/MultiplayerInstaller.cs +++ b/MultiplayerExtensions/Installers/MPCoreInstaller.cs @@ -5,7 +5,7 @@ namespace MultiplayerExtensions.Installers { - class MultiplayerInstaller : MonoInstaller + class MPCoreInstaller : MonoInstaller { public HarmonyPatchInfo? lobbyPlayerDataPatch; public HarmonyPatchInfo? levelLoaderPatch; diff --git a/MultiplayerExtensions/Installers/InterfaceInstaller.cs b/MultiplayerExtensions/Installers/MPMenuInstaller.cs similarity index 80% rename from MultiplayerExtensions/Installers/InterfaceInstaller.cs rename to MultiplayerExtensions/Installers/MPMenuInstaller.cs index 8ad0158..369e1db 100644 --- a/MultiplayerExtensions/Installers/InterfaceInstaller.cs +++ b/MultiplayerExtensions/Installers/MPMenuInstaller.cs @@ -2,11 +2,12 @@ using MultiplayerExtensions.Environments; using MultiplayerExtensions.OverrideClasses; using MultiplayerExtensions.UI; +using UnityEngine; using Zenject; namespace MultiplayerExtensions.Installers { - class InterfaceInstaller : MonoInstaller + class MPMenuInstaller : MonoInstaller { public override void InstallBindings() { @@ -33,9 +34,11 @@ public override void Start() ServerPlayerListController playerListController = Container.Resolve(); GameServerPlayersTableView playersTableView = playerListController.GetField("_gameServerPlayersTableView"); GameServerPlayerTableCell playerTableCell = playersTableView.GetField("_gameServerPlayerCellPrefab"); - PlayerTableCellStub playerTableCellStub = playerTableCell.gameObject.AddComponent(); - playerTableCellStub.Construct(playerTableCell); - Destroy(playerTableCell.GetComponent()); + GameServerPlayerTableCell newPlayerTableCell = GameObject.Instantiate(playerTableCell); + newPlayerTableCell.gameObject.SetActive(false); + PlayerTableCellStub playerTableCellStub = newPlayerTableCell.gameObject.AddComponent(); + playerTableCellStub.Construct(newPlayerTableCell); + Destroy(newPlayerTableCell.GetComponent()); playersTableView.SetField("_gameServerPlayerCellPrefab", playerTableCellStub); } } diff --git a/MultiplayerExtensions/MultiplayerExtensions.csproj b/MultiplayerExtensions/MultiplayerExtensions.csproj index daac61a..3ae4750 100644 --- a/MultiplayerExtensions/MultiplayerExtensions.csproj +++ b/MultiplayerExtensions/MultiplayerExtensions.csproj @@ -4,7 +4,7 @@ Library Properties MultiplayerExtensions - 0.4.4 + 0.4.5 net472 true portable diff --git a/MultiplayerExtensions/OverrideClasses/LevelLoaderStub.cs b/MultiplayerExtensions/OverrideClasses/LevelLoaderStub.cs index 6185669..fe79681 100644 --- a/MultiplayerExtensions/OverrideClasses/LevelLoaderStub.cs +++ b/MultiplayerExtensions/OverrideClasses/LevelLoaderStub.cs @@ -10,7 +10,7 @@ public override void LoadLevel(BeatmapIdentifierNetSerializable beatmapId, Gamep { string? levelId = beatmapId.levelID; string? hash = Utilities.Utils.LevelIdToHash(beatmapId.levelID); - if (SongCore.Loader.GetLevelById(levelId) != null || hash == null) + if (SongCore.Collections.songWithHashPresent(hash) || hash == null) { Plugin.Log?.Debug($"(SongLoader) Level with ID '{levelId}' already exists."); base.LoadLevel(beatmapId, gameplayModifiers, initialStartTime); diff --git a/MultiplayerExtensions/OverrideClasses/PlayerTableCellStub.cs b/MultiplayerExtensions/OverrideClasses/PlayerTableCellStub.cs index e375662..33aaf65 100644 --- a/MultiplayerExtensions/OverrideClasses/PlayerTableCellStub.cs +++ b/MultiplayerExtensions/OverrideClasses/PlayerTableCellStub.cs @@ -72,6 +72,8 @@ public override void Awake() { public override void SetData(IConnectedPlayer connectedPlayer, ILobbyPlayerDataModel playerDataModel, bool isHost, Task getLevelEntitlementTask) { + if (getLevelEntitlementTask != null) + getLevelEntitlementTask = getLevelEntitlementTask.ContinueWith(r => AdditionalContentModel.EntitlementStatus.Owned); base.SetData(connectedPlayer, playerDataModel, isHost, getLevelEntitlementTask); GetLevelEntitlement(connectedPlayer); lastPlayer = connectedPlayer; @@ -84,12 +86,18 @@ private async void GetLevelEntitlement(IConnectedPlayer player) entitlementCts = new CancellationTokenSource(); string? levelId = _playersDataModel.GetPlayerBeatmapLevel(_playersDataModel.hostUserId)?.levelID; - if (levelId == null) + if (levelId == null) return; lastLevelId = levelId; - EntitlementsStatus entitlement = player.isMe ? await _entitlementChecker.GetEntitlementStatus(levelId) : await _entitlementChecker.GetTcsTaskCanPlayerPlayLevel(player, levelId, entitlementCts.Token, out _); - SetLevelEntitlement(player, entitlement); + + bool needsRpc = false; + Task entitlement = player.isMe ? + _entitlementChecker.GetEntitlementStatus(levelId) : + _entitlementChecker.GetTcsTaskCanPlayerPlayLevel(player, levelId, entitlementCts.Token, out needsRpc); + if (needsRpc) + _menuRpcManager.GetIsEntitledToLevel(levelId); + SetLevelEntitlement(player, await entitlement); } private void SetLevelEntitlement(IConnectedPlayer player, EntitlementsStatus status) diff --git a/MultiplayerExtensions/OverrideClasses/PlayersDataModelStub.cs b/MultiplayerExtensions/OverrideClasses/PlayersDataModelStub.cs index 1fd27c6..350e172 100644 --- a/MultiplayerExtensions/OverrideClasses/PlayersDataModelStub.cs +++ b/MultiplayerExtensions/OverrideClasses/PlayersDataModelStub.cs @@ -80,7 +80,7 @@ public override void HandleMenuRpcManagerClearBeatmap(string userId) public async override void HandleMenuRpcManagerGetSelectedBeatmap(string userId) { ILobbyPlayerDataModel lobbyPlayerDataModel = this.GetLobbyPlayerDataModel(this.localUserId); - if (_multiplayerSessionManager.GetPlayerByUserId(userId).HasState("modded") && lobbyPlayerDataModel?.beatmapLevel is PreviewBeatmapStub preview) + if (lobbyPlayerDataModel != null && MPState.CurrentGameType != MultiplayerGameType.QuickPlay && _multiplayerSessionManager.GetPlayerByUserId(userId).HasState("modded") && lobbyPlayerDataModel?.beatmapLevel is PreviewBeatmapStub preview) _packetManager.Send(await PreviewBeatmapPacket.FromPreview(preview, lobbyPlayerDataModel.beatmapCharacteristic.serializedName, lobbyPlayerDataModel.beatmapDifficulty)); else if (lobbyPlayerDataModel != null && lobbyPlayerDataModel.beatmapLevel != null) this._menuRpcManager.SelectBeatmap(new BeatmapIdentifierNetSerializable(lobbyPlayerDataModel.beatmapLevel.levelID, lobbyPlayerDataModel.beatmapCharacteristic.serializedName, lobbyPlayerDataModel.beatmapDifficulty)); @@ -109,7 +109,7 @@ public async override void HandleMenuRpcManagerSelectedBeatmap(string userId, Be if (localPreview != null) preview = new PreviewBeatmapStub(hash, localPreview); if (preview == null) - preview = await FetchBeatSaverPreview(hash); + preview = await FetchBeatSaverPreview(beatmapId.levelID, hash); HMMainThreadDispatcher.instance.Enqueue(() => base.SetPlayerBeatmapLevel(userId, preview, beatmapId.difficulty, characteristic)); } } @@ -123,7 +123,7 @@ public async override void HandleMenuRpcManagerSelectedBeatmap(string userId, Be public async new void SetLocalPlayerBeatmapLevel(string levelId, BeatmapDifficulty beatmapDifficulty, BeatmapCharacteristicSO characteristic) { string? hash = Utilities.Utils.LevelIdToHash(levelId); - Plugin.Log?.Debug($"Local user selected song '{hash}'."); + Plugin.Log?.Debug($"Local user selected song '{hash ?? levelId}'."); if (hash != null) { if (_playersData.Values.Any(playerData => playerData.beatmapLevel?.levelID == levelId)) @@ -139,7 +139,7 @@ public async override void HandleMenuRpcManagerSelectedBeatmap(string userId, Be if (localPreview != null) preview = new PreviewBeatmapStub(hash, localPreview); if (preview == null) - preview = await FetchBeatSaverPreview(hash); + preview = await FetchBeatSaverPreview(levelId, hash); HMMainThreadDispatcher.instance.Enqueue(() => base.SetPlayerBeatmapLevel(base.localUserId, preview, beatmapDifficulty, characteristic)); _packetManager.Send(await PreviewBeatmapPacket.FromPreview(preview, characteristic.serializedName, beatmapDifficulty)); @@ -195,12 +195,12 @@ private void OnSelectedBeatmap(string userId, BeatmapIdentifierNetSerializable? /// /// Creates a preview from a BeatSaver request. /// - public async Task FetchBeatSaverPreview(string hash) + public async Task FetchBeatSaverPreview(string levelID, string hash) { try { Beatmap bm = await Plugin.BeatSaver.Hash(hash); - return new PreviewBeatmapStub(bm); + return new PreviewBeatmapStub(levelID, bm); } catch(Exception ex) { diff --git a/MultiplayerExtensions/Plugin.cs b/MultiplayerExtensions/Plugin.cs index bf45af7..22a5119 100644 --- a/MultiplayerExtensions/Plugin.cs +++ b/MultiplayerExtensions/Plugin.cs @@ -43,8 +43,8 @@ public Plugin(IPALogger logger, Config conf, Zenjector zenjector, PluginMetadata PluginMetadata = pluginMetadata; Log = logger; Config = conf.Generated(); - zenjector.OnApp(); - zenjector.OnMenu(); + zenjector.OnApp(); + zenjector.OnMenu(); HttpOptions options = new HttpOptions("MultiplayerExtensions", new Version(pluginMetadata.Version.ToString())); BeatSaver = new BeatSaver(options); } diff --git a/MultiplayerExtensions/UI/ClientLobbySetupPanel.bsml b/MultiplayerExtensions/UI/ClientLobbySetupPanel.bsml index 09ffa5c..e2c0f0f 100644 --- a/MultiplayerExtensions/UI/ClientLobbySetupPanel.bsml +++ b/MultiplayerExtensions/UI/ClientLobbySetupPanel.bsml @@ -1,5 +1,5 @@ - + diff --git a/MultiplayerExtensions/UI/HostLobbySetupPanel.bsml b/MultiplayerExtensions/UI/HostLobbySetupPanel.bsml index 105d925..9b165f5 100644 --- a/MultiplayerExtensions/UI/HostLobbySetupPanel.bsml +++ b/MultiplayerExtensions/UI/HostLobbySetupPanel.bsml @@ -1,5 +1,5 @@ - + diff --git a/MultiplayerExtensions/manifest.json b/MultiplayerExtensions/manifest.json index c713458..4d86171 100644 --- a/MultiplayerExtensions/manifest.json +++ b/MultiplayerExtensions/manifest.json @@ -3,7 +3,7 @@ "id": "MultiplayerExtensions", "name": "MultiplayerExtensions", "author": "Zingabopp and Goobwabber", - "version": "0.4.4", + "version": "0.4.5", "description": "Expands the functionality of Beat Saber Multiplayer.", "gameVersion": "1.13.2", "dependsOn": {