diff --git a/CustomNotes/CustomNotes.csproj b/CustomNotes/CustomNotes.csproj
index 33e6e52..9db8e4b 100644
--- a/CustomNotes/CustomNotes.csproj
+++ b/CustomNotes/CustomNotes.csproj
@@ -36,6 +36,11 @@
$(BeatSaberDir)\Beat Saber_Data\Managed\BeatmapCore.dll
False
+
+ False
+ $(BeatSaberDir)\Beat Saber_Data\Managed\BGNet.dll
+ False
+
$(BeatSaberDir)\Plugins\BSML.dll
False
@@ -65,10 +70,20 @@
$(BeatSaberDir)\Beat Saber_Data\Managed\IPA.Loader.dll
False
+
+ False
+ $(BeatSaberDir)\Beat Saber_Data\Managed\LiteNetLib.dll
+ False
+
$(BeatSaberDir)\Beat Saber_Data\Managed\Main.dll
False
+
+ False
+ $(BeatSaberDir)\Plugins\MultiplayerExtensions.dll
+ False
+
$(BeatSaberDir)\Libs\SemVer.dll
False
@@ -78,6 +93,7 @@
$(BeatSaberDir)\Plugins\SiraUtil.dll
False
+
$(BeatSaberDir)\Beat Saber_Data\Managed\Unity.TextMeshPro.dll
False
@@ -114,18 +130,29 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -137,6 +164,7 @@
+
@@ -151,6 +179,14 @@
+
+
+
+
+
+ NoteQuickAccessController.cs
+
+
copy "$(TargetPath)" "$(BeatSaberDir)\Plugins"
diff --git a/CustomNotes/Data/CustomNote.cs b/CustomNotes/Data/CustomNote.cs
index 58d0edd..0335613 100644
--- a/CustomNotes/Data/CustomNote.cs
+++ b/CustomNotes/Data/CustomNote.cs
@@ -1,6 +1,7 @@
using CustomNotes.Utilities;
using System;
using System.IO;
+using System.Security.Cryptography;
using UnityEngine;
namespace CustomNotes.Data
@@ -15,6 +16,7 @@ public class CustomNote
public GameObject NoteDotLeft { get; }
public GameObject NoteDotRight { get; }
public GameObject NoteBomb { get; }
+ public string MD5Hash { get; } = string.Empty;
public string ErrorMessage { get; } = string.Empty;
public CustomNote(string fileName)
@@ -25,7 +27,17 @@ public CustomNote(string fileName)
{
try
{
- AssetBundle = AssetBundle.LoadFromFile(Path.Combine(Plugin.PluginAssetPath, fileName));
+ string path = Path.Combine(Plugin.PluginAssetPath, fileName);
+
+ using (var stream = File.OpenRead(path)) {
+ using (var md5 = MD5.Create()) {
+ var hash = md5.ComputeHash(stream);
+ // https://modelsaber.com/api/v2/get.php?type=bloq&filter=hash:
+ MD5Hash = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
+ AssetBundle = AssetBundle.LoadFromStream(stream);
+ }
+ }
+
GameObject note = AssetBundle.LoadAsset("assets/_customnote.prefab");
Descriptor = note.GetComponent();
diff --git a/CustomNotes/Data/MultiplayerCustomNoteData.cs b/CustomNotes/Data/MultiplayerCustomNoteData.cs
new file mode 100644
index 0000000..1623c45
--- /dev/null
+++ b/CustomNotes/Data/MultiplayerCustomNoteData.cs
@@ -0,0 +1,22 @@
+using CustomNotes.Packets;
+
+namespace CustomNotes.Data
+{
+ class MultiplayerCustomNoteData
+ {
+
+ public string NoteHash { get; private set; } = CustomNotesPacket.DEFAULT_NOTES;
+ public float NoteScale { get; private set; } = 1f;
+
+ public MultiplayerCustomNoteData(string noteHash = CustomNotesPacket.DEFAULT_NOTES, float noteScale = 1f) {
+ NoteHash = noteHash;
+ NoteScale = noteScale;
+ }
+
+ public void UpdateFromPacket(CustomNotesPacket packet) {
+ NoteHash = packet.NoteHash;
+ NoteScale = packet.NoteScale;
+ }
+
+ }
+}
diff --git a/CustomNotes/INoteHashPacketManager.cs b/CustomNotes/INoteHashPacketManager.cs
new file mode 100644
index 0000000..05b685b
--- /dev/null
+++ b/CustomNotes/INoteHashPacketManager.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CustomNotes.Interfaces
+{
+ internal interface INoteHashPacketManager
+ {
+
+ bool HasHashFromPlayer(IConnectedPlayer connectedPlayer);
+ string GetHashFromPlayer(IConnectedPlayer connectedPlayer);
+
+ }
+}
diff --git a/CustomNotes/Installers/CustomNotesCoreInstaller.cs b/CustomNotes/Installers/CustomNotesCoreInstaller.cs
index 463a0dc..6d01f4e 100644
--- a/CustomNotes/Installers/CustomNotesCoreInstaller.cs
+++ b/CustomNotes/Installers/CustomNotesCoreInstaller.cs
@@ -1,8 +1,9 @@
-using Zenject;
-using SiraUtil.Interfaces;
-using CustomNotes.Managers;
+using CustomNotes.Managers;
using CustomNotes.Providers;
using CustomNotes.Settings.Utilities;
+using IPA.Loader;
+using SiraUtil.Interfaces;
+using Zenject;
namespace CustomNotes.Installers
{
@@ -22,6 +23,18 @@ public override void InstallBindings()
Container.Bind(typeof(IModelProvider), typeof(CustomGameNoteProvider)).To().AsSingle();
Container.Bind(typeof(IModelProvider), typeof(CustomBombNoteProvider)).To().AsSingle();
+
+ Container.Bind(typeof(IModelProvider), typeof(CustomMultiplayerNoteProvider)).To().AsSingle();
+ Container.Bind(typeof(IModelProvider), typeof(CustomMultiplayerBombProvider)).To().AsSingle();
+
+ if (PluginManager.GetPluginFromId("MultiplayerExtensions") != null)
+ {
+ Container.BindInterfacesAndSelfTo().AsSingle();
+ }
+ else
+ {
+ Container.BindInterfacesAndSelfTo().AsSingle();
+ }
}
}
}
\ No newline at end of file
diff --git a/CustomNotes/Installers/CustomNotesGameInstaller.cs b/CustomNotes/Installers/CustomNotesGameInstaller.cs
index cd9bbb4..6fbb08b 100644
--- a/CustomNotes/Installers/CustomNotesGameInstaller.cs
+++ b/CustomNotes/Installers/CustomNotesGameInstaller.cs
@@ -1,5 +1,6 @@
-using Zenject;
-using CustomNotes.Managers;
+using CustomNotes.Managers;
+using CustomNotes.Providers;
+using Zenject;
namespace CustomNotes.Installers
{
@@ -17,6 +18,7 @@ public override void InstallBindings()
if (_noteAssetLoader.SelectedNote != 0)
{
Container.Bind().To().AsSingle();
+ Container.BindInterfacesAndSelfTo().AsSingle();
}
}
}
diff --git a/CustomNotes/Installers/CustomNotesMenuInstaller.cs b/CustomNotes/Installers/CustomNotesMenuInstaller.cs
index 8cf6a49..dd50789 100644
--- a/CustomNotes/Installers/CustomNotesMenuInstaller.cs
+++ b/CustomNotes/Installers/CustomNotesMenuInstaller.cs
@@ -1,8 +1,8 @@
-using Zenject;
-using SiraUtil;
-using CustomNotes.Managers;
+using CustomNotes.Managers;
using CustomNotes.Settings;
using CustomNotes.Settings.UI;
+using SiraUtil;
+using Zenject;
namespace CustomNotes.Installers
{
@@ -10,13 +10,12 @@ internal class CustomNotesMenuInstaller : Installer
{
public override void InstallBindings()
{
-
-
Container.Bind().FromNewComponentAsViewController().AsSingle();
Container.Bind().FromNewComponentAsViewController().AsSingle();
Container.Bind().FromNewComponentAsViewController().AsSingle();
- Container.BindFlowCoordinator();
+ Container.Bind().FromNewComponentOnNewGameObject($"{nameof(NotesFlowCoordinator)}HostGameObject").AsSingle();
+ Container.BindInterfacesAndSelfTo().AsSingle();
Container.BindInterfacesTo().AsSingle();
}
}
diff --git a/CustomNotes/Interfaces/ICustomNoteNetworkPacketManager.cs b/CustomNotes/Interfaces/ICustomNoteNetworkPacketManager.cs
new file mode 100644
index 0000000..a7c43c5
--- /dev/null
+++ b/CustomNotes/Interfaces/ICustomNoteNetworkPacketManager.cs
@@ -0,0 +1,11 @@
+using CustomNotes.Data;
+
+namespace CustomNotes.Interfaces
+{
+ internal interface ICustomNoteNetworkPacketManager
+ {
+ bool HasDataFromPlayer(IConnectedPlayer connectedPlayer);
+
+ MultiplayerCustomNoteData GetData(IConnectedPlayer connectedPlayer);
+ }
+}
diff --git a/CustomNotes/Managers/CustomBombController.cs b/CustomNotes/Managers/CustomBombController.cs
index 77dd650..ec1d5bb 100644
--- a/CustomNotes/Managers/CustomBombController.cs
+++ b/CustomNotes/Managers/CustomBombController.cs
@@ -1,29 +1,18 @@
-using Zenject;
-using UnityEngine;
-using CustomNotes.Data;
+using CustomNotes.Data;
using CustomNotes.Settings.Utilities;
using SiraUtil.Objects;
+using UnityEngine;
+using Zenject;
namespace CustomNotes.Managers
{
- internal class CustomBombController : MonoBehaviour
+ internal class CustomBombController : CustomBombControllerBase
{
- private PluginConfig _pluginConfig;
-
- private CustomNote _customNote;
- private NoteMovement _noteMovement;
- private BombNoteController _bombNoteController;
-
- protected Transform bombMesh;
-
- protected GameObject activeNote;
- protected SiraPrefabContainer container;
- protected SiraPrefabContainer.Pool bombPool;
-
[Inject]
internal void Init(PluginConfig pluginConfig, NoteAssetLoader noteAssetLoader, [Inject(Id = "cn.bomb")] SiraPrefabContainer.Pool bombContainerPool)
{
_pluginConfig = pluginConfig;
+ _noteSize = pluginConfig.NoteSize;
_customNote = noteAssetLoader.CustomNoteObjects[noteAssetLoader.SelectedNote];
_bombNoteController = GetComponent();
@@ -36,29 +25,46 @@ internal void Init(PluginConfig pluginConfig, NoteAssetLoader noteAssetLoader, [
MeshRenderer bm = GetComponentInChildren();
bm.enabled = false;
}
+ }
+
+ internal abstract class CustomBombControllerBase : MonoBehaviour
+ {
+ protected PluginConfig _pluginConfig;
+
+ protected CustomNote _customNote;
+ protected NoteMovement _noteMovement;
+ protected NoteController _bombNoteController;
+
+ protected float _noteSize = 1f;
+
+ protected Transform bombMesh;
+
+ protected GameObject activeNote;
+ protected SiraPrefabContainer container;
+ protected SiraPrefabContainer.Pool bombPool;
- private void DidFinish()
+ protected virtual void DidFinish()
{
container.transform.SetParent(null);
bombPool.Despawn(container);
}
- private void Controller_Init(NoteController noteController)
+ protected virtual void Controller_Init(NoteController noteController)
{
SpawnThenParent(bombPool);
}
- private void ParentNote(GameObject fakeMesh)
+ protected virtual void ParentNote(GameObject fakeMesh)
{
fakeMesh.SetActive(true);
container.transform.SetParent(bombMesh);
fakeMesh.transform.localPosition = container.transform.localPosition = new Vector3(0.0f, 0.0f, 0.0f);
container.transform.localRotation = Quaternion.identity;
- fakeMesh.transform.localScale = new Vector3(0.4f, 0.4f, 0.4f) * _pluginConfig.NoteSize;
+ fakeMesh.transform.localScale = new Vector3(0.4f, 0.4f, 0.4f) * _noteSize;
container.transform.localScale = Vector3.one;
}
- private void SpawnThenParent(SiraPrefabContainer.Pool bombModelPool)
+ protected virtual void SpawnThenParent(SiraPrefabContainer.Pool bombModelPool)
{
container = bombModelPool.Spawn();
activeNote = container.Prefab;
@@ -66,7 +72,7 @@ private void SpawnThenParent(SiraPrefabContainer.Pool bombModelPool)
ParentNote(activeNote);
}
- protected void OnDestroy()
+ protected virtual void OnDestroy()
{
if (_bombNoteController != null)
{
diff --git a/CustomNotes/Managers/CustomMultiplayerBombController.cs b/CustomNotes/Managers/CustomMultiplayerBombController.cs
new file mode 100644
index 0000000..175ca41
--- /dev/null
+++ b/CustomNotes/Managers/CustomMultiplayerBombController.cs
@@ -0,0 +1,57 @@
+using CustomNotes.Data;
+using CustomNotes.Packets;
+using CustomNotes.Providers;
+using CustomNotes.Settings.Utilities;
+using SiraUtil.Objects;
+using System;
+using UnityEngine;
+using Zenject;
+
+namespace CustomNotes.Managers
+{
+ class CustomMultiplayerBombController : CustomBombControllerBase
+ {
+ [Inject]
+ internal void Init(DiContainer Container,
+ PluginConfig pluginConfig,
+ ConnectedPlayerNotePoolProvider connectedPlayerNotePoolProvider,
+ IConnectedPlayer connectedPlayer)
+ {
+ _pluginConfig = pluginConfig;
+
+ string id = connectedPlayerNotePoolProvider.GetPoolIDForPlayer(connectedPlayer);
+
+ if (id.Equals(CustomNotesPacket.DEFAULT_NOTES))
+ {
+ return;
+ }
+
+ bombPool = Container.TryResolveId($"cn.multi.{id}.bomb");
+
+ _customNote = Container.TryResolveId($"cn.multi.{id}.note");
+
+ if (bombPool == null)
+ {
+ return;
+ }
+
+ try
+ {
+ _noteSize = Container.ResolveId($"cn.multi.{connectedPlayer.userId}.scale");
+ }
+ catch (Exception ex)
+ {
+ Logger.log.Error($" ({nameof(CustomMultiplayerBombController)}) This shouldn't happen: {ex.Message}");
+ }
+
+ _bombNoteController = GetComponent();
+ _noteMovement = GetComponent();
+ _bombNoteController.didInitEvent += Controller_Init;
+ _noteMovement.noteDidFinishJumpEvent += DidFinish;
+ bombMesh = gameObject.transform.Find("Mesh");
+
+ MeshRenderer bm = GetComponentInChildren();
+ bm.enabled = false;
+ }
+ }
+}
diff --git a/CustomNotes/Managers/CustomMultiplayerNoteController.cs b/CustomNotes/Managers/CustomMultiplayerNoteController.cs
new file mode 100644
index 0000000..ca9910a
--- /dev/null
+++ b/CustomNotes/Managers/CustomMultiplayerNoteController.cs
@@ -0,0 +1,121 @@
+using CustomNotes.Data;
+using CustomNotes.Overrides;
+using CustomNotes.Packets;
+using CustomNotes.Providers;
+using CustomNotes.Settings.Utilities;
+using SiraUtil.Objects;
+using System;
+using UnityEngine;
+using Zenject;
+
+namespace CustomNotes.Managers
+{
+ public class CustomMultiplayerNoteController : CustomNoteControllerBase
+ {
+
+ [Inject]
+ private CustomMultiplayerNoteEventManager _customMultiplayerNoteEventManager;
+
+ [Inject]
+ internal void Init(DiContainer Container,
+ PluginConfig pluginConfig,
+ ConnectedPlayerNotePoolProvider connectedPlayerNotePoolProvider,
+ CustomMultiplayerNoteEventManager customMultiplayerNoteEventManager,
+ IConnectedPlayer connectedPlayer)
+ {
+ _pluginConfig = pluginConfig;
+
+ string id = connectedPlayerNotePoolProvider.GetPoolIDForPlayer(connectedPlayer);
+
+ if(id.Equals(CustomNotesPacket.DEFAULT_NOTES))
+ {
+ return;
+ }
+
+ _leftArrowNotePool = Container.TryResolveId($"cn.multi.{id}.left.arrow");
+ _rightArrowNotePool = Container.TryResolveId($"cn.multi.{id}.right.arrow");
+ _leftDotNotePool = Container.TryResolveId($"cn.multi.{id}.left.dot") ?? _leftArrowNotePool;
+ _rightDotNotePool = Container.TryResolveId($"cn.multi.{id}.right.dot") ?? _rightArrowNotePool;
+
+ _customNote = Container.TryResolveId($"cn.multi.{id}.note");
+
+ if(_leftArrowNotePool == null)
+ {
+ return;
+ }
+
+ try
+ {
+ _noteSize = Container.ResolveId($"cn.multi.{connectedPlayer.userId}.scale");
+ }
+ catch (Exception ex)
+ {
+ Logger.log.Error($" ({nameof(CustomMultiplayerNoteController)}) This shouldn't happen: {ex.Message}");
+ }
+
+ _customMultiplayerNoteEventManager = customMultiplayerNoteEventManager;
+
+ _gameNoteController = GetComponent();
+ _customNoteColorNoteVisuals = gameObject.AddComponent();
+
+ // The NoteControllers NoteWasCut & NoteWasMissed events aren't fired for Multiplayer Notes ...
+ _customMultiplayerNoteEventManager.onGameNoteCutEvent += WasCut;
+ _customMultiplayerNoteEventManager.onGameNoteMissEvent += DidFinish;
+ _gameNoteController.didInitEvent += Controller_DidInit;
+ _customNoteColorNoteVisuals.didInitEvent += Visuals_DidInit;
+
+ noteCube = _gameNoteController.gameObject.transform.Find("NoteCube");
+
+ MeshRenderer noteMesh = GetComponentInChildren();
+ noteMesh.forceRenderingOff = true;
+ }
+
+ protected override void DidFinish(NoteController nc) {
+ if (nc != this._gameNoteController) return;
+ container.transform.SetParent(null);
+ switch (nc.noteData.colorType) {
+ case ColorType.ColorA:
+ case ColorType.ColorB:
+ if (container != null) {
+ activePool?.Despawn(container);
+ _customMultiplayerNoteEventManager?.GameNoteDespawnedCallback(_gameNoteController as MultiplayerConnectedPlayerGameNoteController);
+ container = null;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ protected override void OnDestroy() {
+ if (_gameNoteController != null && _customMultiplayerNoteEventManager != null) {
+ _customMultiplayerNoteEventManager.onGameNoteCutEvent -= WasCut;
+ _customMultiplayerNoteEventManager.onGameNoteMissEvent -= DidFinish;
+ _gameNoteController.didInitEvent -= Controller_DidInit;
+ }
+ if (_customNoteColorNoteVisuals != null) {
+ _customNoteColorNoteVisuals.didInitEvent -= Visuals_DidInit;
+ }
+ }
+
+ protected override void ParentNote(GameObject fakeMesh)
+ {
+ container.transform.SetParent(noteCube);
+ fakeMesh.transform.localPosition = container.transform.localPosition = new Vector3(0.0f, 0.0f, 0.0f);
+ fakeMesh.transform.rotation = container.transform.rotation = fakeMesh.transform.localRotation = Quaternion.Euler(0f, 0f, 0f);
+ container.transform.localRotation = noteCube.parent.localRotation;
+ container.transform.Rotate(new Vector3(0, -90, 0), Space.Self);
+ fakeMesh.transform.localScale = new Vector3(0.4f, 0.4f, 0.4f) * _noteSize;
+ container.transform.localScale = Vector3.one;
+ }
+
+ protected override void SpawnThenParent(SiraPrefabContainer.Pool noteModelPool) {
+ container = noteModelPool.Spawn();
+ activeNote = container.Prefab;
+ activePool = noteModelPool;
+ ParentNote(activeNote);
+ _customMultiplayerNoteEventManager?.GameNoteSpawnedCallback(_gameNoteController as MultiplayerConnectedPlayerGameNoteController);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/CustomNotes/Managers/CustomMultiplayerNoteEventManager.cs b/CustomNotes/Managers/CustomMultiplayerNoteEventManager.cs
new file mode 100644
index 0000000..709901e
--- /dev/null
+++ b/CustomNotes/Managers/CustomMultiplayerNoteEventManager.cs
@@ -0,0 +1,151 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace CustomNotes.Managers
+{
+ ///
+ /// Multiplayer Game Note Controllers don't emit the NoteCut and NoteMissed Events, this is a replacement
+ ///
+ public class CustomMultiplayerNoteEventManager : IDisposable
+ {
+
+ private class ActiveItemTracker : IDisposable where T : MonoBehaviour
+ {
+ public HashSet activeItems { get; private set; } = new HashSet();
+
+ public ActiveItemTracker() {}
+
+ public void OnSpawn(T item)
+ {
+ this.activeItems.Add(item);
+ }
+ public void OnDespawn(T item)
+ {
+ this.activeItems.Remove(item);
+ }
+ public bool Contains(T item)
+ {
+ return this.activeItems.Contains(item);
+ }
+ public void Dispose()
+ {
+ this.activeItems = null;
+ }
+
+ }
+
+ public event Action onGameNoteCutEvent;
+ public event Action onGameNoteMissEvent;
+
+ private static readonly IPA.Utilities.FieldAccessor.Accessor _vUnitAccessor = IPA.Utilities.FieldAccessor.GetAccessor("_v");
+ private static readonly IPA.Utilities.FieldAccessor.Accessor _vAccessor = IPA.Utilities.FieldAccessor.GetAccessor("_v");
+
+ private IConnectedPlayerNoteEventManager _connectedPlayerNoteEventManager;
+
+ private ActiveItemTracker _activeMultiGameNoteControllers = new ActiveItemTracker();
+
+ public CustomMultiplayerNoteEventManager(IConnectedPlayerNoteEventManager connectedPlayerNoteEventManager)
+ {
+ _connectedPlayerNoteEventManager = connectedPlayerNoteEventManager;
+ connectedPlayerNoteEventManager.connectedPlayerNoteWasCutEvent += HandleNoteWasCutEvent;
+ connectedPlayerNoteEventManager.connectedPlayerNoteWasMissedEvent += HandleNoteWasMissedEvent;
+ }
+
+ public void Dispose()
+ {
+ _connectedPlayerNoteEventManager.connectedPlayerNoteWasCutEvent -= HandleNoteWasCutEvent;
+ _connectedPlayerNoteEventManager.connectedPlayerNoteWasMissedEvent -= HandleNoteWasMissedEvent;
+ }
+
+ public void GameNoteSpawnedCallback(MultiplayerConnectedPlayerGameNoteController noteController)
+ {
+ if(!_activeMultiGameNoteControllers.Contains(noteController))
+ {
+ _activeMultiGameNoteControllers.OnSpawn(noteController);
+ }
+ }
+
+ //private bool _isDownStackingGameNotes = false;
+ //private Stack _despawnGNCStack = new Stack(5);
+
+ public void GameNoteDespawnedCallback(MultiplayerConnectedPlayerGameNoteController noteController)
+ {
+ //_despawnGNCStack.Push(noteController);
+ }
+
+ /*public void ManageGameNoteDespawnStack()
+ {
+ _isDownStackingGameNotes = true;
+ while (_despawnGNCStack.Count > 0)
+ {
+ _activeMultiGameNoteControllers.OnDespawn(_despawnGNCStack.Pop() as MultiplayerConnectedPlayerGameNoteController);
+ }
+ _isDownStackingGameNotes = false;
+ }*/
+
+ private void HandleNoteWasCutEvent(NoteCutInfoNetSerializable ncins)
+ {
+ foreach (MultiplayerConnectedPlayerGameNoteController noteController in _activeMultiGameNoteControllers.activeItems)
+ {
+ if (CompareNotes(noteController, ncins))
+ {
+ this.onGameNoteCutEvent?.Invoke(noteController, ToNCI(ncins));
+ break;
+ }
+ }
+ //if (!_isDownStackingGameNotes) ManageGameNoteDespawnStack();
+ }
+
+ private void HandleNoteWasMissedEvent(NoteMissInfoNetSerializable nmins)
+ {
+ foreach (MultiplayerConnectedPlayerGameNoteController noteController in _activeMultiGameNoteControllers.activeItems)
+ {
+ if (CompareNotes(noteController, nmins))
+ {
+ this.onGameNoteMissEvent?.Invoke(noteController);
+ break;
+ }
+ }
+ //if (!_isDownStackingGameNotes) ManageGameNoteDespawnStack();
+ }
+
+ public static bool CompareNotes(NoteController nc, NoteCutInfoNetSerializable nci)
+ {
+ NoteData noteData = nc.noteData;
+ return Mathf.Approximately(noteData.time, nci.noteTime) && noteData.lineIndex == nci.noteLineIndex && noteData.noteLineLayer == nci.noteLineLayer;
+ }
+
+ public static bool CompareNotes(NoteController nc, NoteMissInfoNetSerializable nmi)
+ {
+ NoteData noteData = nc.noteData;
+ return Mathf.Approximately(noteData.time, nmi.noteTime) && noteData.lineIndex == nmi.noteLineIndex && noteData.noteLineLayer == nmi.noteLineLayer;
+ }
+
+ public static NoteCutInfo ToNCI(NoteCutInfoNetSerializable i)
+ {
+ return new NoteCutInfo(
+ i.cutWasOk,
+ i.cutWasOk,
+ i.cutWasOk,
+ !i.cutWasOk,
+ i.saberSpeed,
+ _vUnitAccessor(ref i.saberDir),
+ ColorTypeToSaberType(i.colorType),
+ 0,
+ 0,
+ _vAccessor(ref i.cutPoint),
+ _vUnitAccessor(ref i.cutNormal),
+ 0,
+ 0,
+ null
+ );
+ }
+
+ public static SaberType ColorTypeToSaberType(ColorType ct)
+ {
+ return ct == ColorType.ColorA ? SaberType.SaberA : SaberType.SaberB;
+ }
+
+ }
+}
diff --git a/CustomNotes/Managers/CustomNoteController.cs b/CustomNotes/Managers/CustomNoteController.cs
index 0bf72d5..9bf78c4 100644
--- a/CustomNotes/Managers/CustomNoteController.cs
+++ b/CustomNotes/Managers/CustomNoteController.cs
@@ -1,34 +1,16 @@
-using Zenject;
-using UnityEngine;
-using SiraUtil.Objects;
-using CustomNotes.Data;
-using SiraUtil.Interfaces;
+using CustomNotes.Data;
using CustomNotes.Overrides;
using CustomNotes.Settings.Utilities;
using CustomNotes.Utilities;
+using SiraUtil.Interfaces;
+using SiraUtil.Objects;
+using UnityEngine;
+using Zenject;
namespace CustomNotes.Managers
{
- public class CustomNoteController : MonoBehaviour, IColorable
+ public class CustomNoteController : CustomNoteControllerBase
{
- private PluginConfig _pluginConfig;
-
- protected Transform noteCube;
- private CustomNote _customNote;
- private GameNoteController _gameNoteController;
- private CustomNoteColorNoteVisuals _customNoteColorNoteVisuals;
-
- protected GameObject activeNote;
- protected SiraPrefabContainer container;
- protected SiraPrefabContainer.Pool activePool;
-
- private SiraPrefabContainer.Pool _leftDotNotePool;
- private SiraPrefabContainer.Pool _rightDotNotePool;
- private SiraPrefabContainer.Pool _leftArrowNotePool;
- private SiraPrefabContainer.Pool _rightArrowNotePool;
-
- public Color Color => _customNoteColorNoteVisuals != null ? _customNoteColorNoteVisuals.noteColor : Color.white;
-
[Inject]
internal void Init(PluginConfig pluginConfig,
NoteAssetLoader noteAssetLoader,
@@ -38,6 +20,7 @@ internal void Init(PluginConfig pluginConfig,
[InjectOptional(Id = "cn.right.dot")] SiraPrefabContainer.Pool rightDotNotePool)
{
_pluginConfig = pluginConfig;
+ _noteSize = pluginConfig.NoteSize;
_leftArrowNotePool = leftArrowNotePool;
_rightArrowNotePool = rightArrowNotePool;
@@ -59,8 +42,30 @@ internal void Init(PluginConfig pluginConfig,
MeshRenderer noteMesh = GetComponentInChildren();
noteMesh.forceRenderingOff = true;
}
+ }
+
+ public abstract class CustomNoteControllerBase : MonoBehaviour, IColorable
+ {
+ protected PluginConfig _pluginConfig;
+
+ protected Transform noteCube;
+ protected CustomNote _customNote;
+ protected NoteController _gameNoteController;
+ protected CustomNoteColorNoteVisuals _customNoteColorNoteVisuals;
+ protected float _noteSize = 1f;
+
+ protected GameObject activeNote;
+ protected SiraPrefabContainer container;
+ protected SiraPrefabContainer.Pool activePool;
+
+ protected SiraPrefabContainer.Pool _leftDotNotePool;
+ protected SiraPrefabContainer.Pool _rightDotNotePool;
+ protected SiraPrefabContainer.Pool _leftArrowNotePool;
+ protected SiraPrefabContainer.Pool _rightArrowNotePool;
+
+ public Color Color => _customNoteColorNoteVisuals != null ? _customNoteColorNoteVisuals.noteColor : Color.white;
- private void DidFinish(NoteController nc)
+ protected virtual void DidFinish(NoteController nc)
{
container.transform.SetParent(null);
switch (nc.noteData.colorType)
@@ -78,12 +83,12 @@ private void DidFinish(NoteController nc)
}
}
- private void WasCut(NoteController nc, NoteCutInfo _)
+ protected virtual void WasCut(NoteController nc, NoteCutInfo _)
{
DidFinish(nc);
}
- private void Controller_DidInit(NoteController noteController)
+ protected virtual void Controller_DidInit(NoteController noteController)
{
var data = noteController.noteData;
SpawnThenParent(data.colorType == ColorType.ColorA
@@ -91,16 +96,16 @@ private void Controller_DidInit(NoteController noteController)
: data.cutDirection == NoteCutDirection.Any ? _rightDotNotePool : _rightArrowNotePool);
}
- private void ParentNote(GameObject fakeMesh)
+ protected virtual void ParentNote(GameObject fakeMesh)
{
container.transform.SetParent(noteCube);
fakeMesh.transform.localPosition = container.transform.localPosition = new Vector3(0.0f, 0.0f, 0.0f);
container.transform.localRotation = Quaternion.identity;
- fakeMesh.transform.localScale = new Vector3(0.4f, 0.4f, 0.4f) * _pluginConfig.NoteSize;
+ fakeMesh.transform.localScale = new Vector3(0.4f, 0.4f, 0.4f) * _noteSize;
container.transform.localScale = Vector3.one;
}
- private void SpawnThenParent(SiraPrefabContainer.Pool noteModelPool)
+ protected virtual void SpawnThenParent(SiraPrefabContainer.Pool noteModelPool)
{
container = noteModelPool.Spawn();
activeNote = container.Prefab;
@@ -108,7 +113,7 @@ private void SpawnThenParent(SiraPrefabContainer.Pool noteModelPool)
ParentNote(activeNote);
}
- protected void SetActiveThenColor(GameObject note, Color color)
+ protected virtual void SetActiveThenColor(GameObject note, Color color)
{
note.SetActive(true);
if (_customNote.Descriptor.UsesNoteColor)
@@ -117,7 +122,7 @@ protected void SetActiveThenColor(GameObject note, Color color)
}
}
- private void Visuals_DidInit(ColorNoteVisuals visuals, NoteController noteController)
+ protected virtual void Visuals_DidInit(ColorNoteVisuals visuals, NoteController noteController)
{
SetActiveThenColor(activeNote, visuals.noteColor);
// Hide certain parts of the default note which is not required
@@ -125,13 +130,13 @@ private void Visuals_DidInit(ColorNoteVisuals visuals, NoteController noteContro
{
_customNoteColorNoteVisuals.TurnOffVisuals();
}
- else if(_pluginConfig.NoteSize != 1)
+ else if(_noteSize != 1)
{
- _customNoteColorNoteVisuals.ScaleVisuals(_pluginConfig.NoteSize);
+ _customNoteColorNoteVisuals.ScaleVisuals(_noteSize);
}
}
- protected void OnDestroy()
+ protected virtual void OnDestroy()
{
if (_gameNoteController != null)
{
diff --git a/CustomNotes/Managers/CustomNoteManager.cs b/CustomNotes/Managers/CustomNoteManager.cs
index d2b1c6b..257f3fc 100644
--- a/CustomNotes/Managers/CustomNoteManager.cs
+++ b/CustomNotes/Managers/CustomNoteManager.cs
@@ -1,7 +1,7 @@
-using Zenject;
-using CustomNotes.Data;
-using SiraUtil.Services;
+using CustomNotes.Data;
using CustomNotes.Utilities;
+using SiraUtil.Services;
+using Zenject;
namespace CustomNotes.Managers
{
diff --git a/CustomNotes/Managers/CustomNotesNetworkPacketManager.cs b/CustomNotes/Managers/CustomNotesNetworkPacketManager.cs
new file mode 100644
index 0000000..416e87f
--- /dev/null
+++ b/CustomNotes/Managers/CustomNotesNetworkPacketManager.cs
@@ -0,0 +1,134 @@
+using CustomNotes.Data;
+using CustomNotes.Interfaces;
+using CustomNotes.Packets;
+using CustomNotes.Settings.Utilities;
+using MultiplayerExtensions.Packets;
+using System;
+using System.Collections.Generic;
+using Zenject;
+
+namespace CustomNotes.Managers
+{
+ internal class DummyCustomNotesNetworkPacketManager : ICustomNoteNetworkPacketManager
+ {
+ private readonly MultiplayerCustomNoteData _defaultData = new MultiplayerCustomNoteData();
+
+ public DummyCustomNotesNetworkPacketManager()
+ {
+ Logger.log.Info("Disabling CustomNote sync, no MultiplayerExtensions detected.");
+ }
+
+ public bool HasDataFromPlayer(IConnectedPlayer connectedPlayer)
+ {
+ return false;
+ }
+
+ public MultiplayerCustomNoteData GetData(IConnectedPlayer connectedPlayer)
+ {
+ return _defaultData;
+ }
+ }
+
+ internal class CustomNotesNetworkPacketManager : ICustomNoteNetworkPacketManager, IInitializable, IDisposable
+ {
+ private readonly PluginConfig _pluginConfig;
+ private readonly NoteAssetLoader _noteAssetLoader;
+
+ private readonly PacketManager _packetManager;
+ private readonly IMultiplayerSessionManager _sessionManager;
+
+ private readonly MultiplayerCustomNoteData _defaultData = new MultiplayerCustomNoteData();
+ private readonly Dictionary _customNoteData = new Dictionary();
+
+ [Inject]
+ public CustomNotesNetworkPacketManager(PluginConfig pluginConfig,
+ NoteAssetLoader noteAssetLoader,
+ [InjectOptional] PacketManager packetManager,
+ [InjectOptional] IMultiplayerSessionManager sessionManager)
+ {
+ _pluginConfig = pluginConfig;
+ _noteAssetLoader = noteAssetLoader;
+ _packetManager = packetManager;
+ _sessionManager = sessionManager;
+ }
+
+ public void Initialize()
+ {
+ if (_sessionManager == null || _packetManager == null) return;
+
+ Logger.log.Info($"Initializing {nameof(CustomNotesNetworkPacketManager)}");
+
+ _sessionManager.connectedEvent += OnSessionConnectedEvent;
+ _sessionManager.playerDisconnectedEvent += OnPlayerDisconnected;
+ _packetManager.RegisterCallback(HandleCustomNotesPacket);
+ _noteAssetLoader.customNoteSelectionChangedEvent += OnCustomNoteSelectionChanged;
+ }
+
+ private void OnSessionConnectedEvent()
+ {
+ Logger.log.Debug("Connected to a new Session!");
+ SendUpdatePacket();
+ }
+
+ private void OnCustomNoteSelectionChanged(int index, CustomNote note)
+ {
+ if(_sessionManager.isConnected)
+ {
+ SendUpdatePacket();
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_sessionManager != null)
+ {
+ _sessionManager.connectedEvent -= OnSessionConnectedEvent;
+ _sessionManager.playerDisconnectedEvent -= OnPlayerDisconnected;
+ }
+ if (_packetManager != null)
+ {
+ _packetManager.UnregisterCallback();
+ }
+ if(_noteAssetLoader != null)
+ {
+ _noteAssetLoader.customNoteSelectionChangedEvent -= OnCustomNoteSelectionChanged;
+ }
+ }
+
+ public bool HasDataFromPlayer(IConnectedPlayer connectedPlayer)
+ {
+ return _customNoteData.ContainsKey(connectedPlayer.userId);
+ }
+
+ public MultiplayerCustomNoteData GetData(IConnectedPlayer connectedPlayer)
+ {
+ return GetCustomNoteData(connectedPlayer) ?? _defaultData;
+ }
+
+ private MultiplayerCustomNoteData GetCustomNoteData(IConnectedPlayer connectedPlayer)
+ {
+ if (_customNoteData.TryGetValue(connectedPlayer.userId, out MultiplayerCustomNoteData data)) return data;
+ return null;
+ }
+
+ private void HandleCustomNotesPacket(CustomNotesPacket packet, IConnectedPlayer connectedPlayer)
+ {
+ Logger.log.Info($"Received {nameof(packet)} from {connectedPlayer.userName} ({connectedPlayer.userId}): hash = {packet.NoteHash} , scale = {packet.NoteScale}");
+ var data = GetCustomNoteData(connectedPlayer) ?? new MultiplayerCustomNoteData();
+ data.UpdateFromPacket(packet);
+ _customNoteData[connectedPlayer.userId] = data;
+ }
+
+ private void OnPlayerDisconnected(IConnectedPlayer connectedPlayer)
+ {
+ _customNoteData.Remove(connectedPlayer.userId);
+ }
+
+ private void SendUpdatePacket()
+ {
+ var note = _noteAssetLoader.CustomNoteObjects[_noteAssetLoader.SelectedNote];
+ Logger.log.Debug($"Sending {nameof(CustomNotesPacket)}.");
+ _sessionManager.Send(CustomNotesPacket.CreatePacket(note.MD5Hash, _pluginConfig.NoteSize));
+ }
+ }
+}
diff --git a/CustomNotes/Managers/MenuButtonManager.cs b/CustomNotes/Managers/MenuButtonManager.cs
index db78238..7315785 100644
--- a/CustomNotes/Managers/MenuButtonManager.cs
+++ b/CustomNotes/Managers/MenuButtonManager.cs
@@ -1,8 +1,9 @@
-using System;
-using Zenject;
-using BeatSaberMarkupLanguage;
-using CustomNotes.Settings.UI;
+using BeatSaberMarkupLanguage;
+using BeatSaberMarkupLanguage.GameplaySetup;
using BeatSaberMarkupLanguage.MenuButtons;
+using CustomNotes.Settings.UI;
+using System;
+using Zenject;
namespace CustomNotes.Managers
{
@@ -12,16 +13,20 @@ internal class MenuButtonManager : IInitializable, IDisposable
private readonly MainFlowCoordinator _mainFlowCoordinator;
private readonly NotesFlowCoordinator _notesFlowCoordinator;
- public MenuButtonManager(MainFlowCoordinator mainFlowCoordinator, NotesFlowCoordinator notesFlowCoordinator)
+ private NoteQuickAccessController _noteQuickAccessController;
+
+ public MenuButtonManager(MainFlowCoordinator mainFlowCoordinator, NotesFlowCoordinator notesFlowCoordinator, NoteQuickAccessController noteQuickAccessController)
{
_mainFlowCoordinator = mainFlowCoordinator;
_notesFlowCoordinator = notesFlowCoordinator;
_menuButton = new MenuButton("Custom Notes", "Change Custom Notes Here!", ShowNotesFlow, true);
+ _noteQuickAccessController = noteQuickAccessController;
}
public void Initialize()
{
MenuButtons.instance.RegisterButton(_menuButton);
+ GameplaySetup.instance.AddTab("Custom Notes", "CustomNotes.Settings.UI.Views.quickAccess.bsml", _noteQuickAccessController);
}
public void Dispose()
@@ -30,6 +35,10 @@ public void Dispose()
{
MenuButtons.instance.UnregisterButton(_menuButton);
}
+ if(GameplaySetup.IsSingletonAvailable)
+ {
+ GameplaySetup.instance.RemoveTab("Custom Notes");
+ }
}
private void ShowNotesFlow()
diff --git a/CustomNotes/Managers/NoteAssetLoader.cs b/CustomNotes/Managers/NoteAssetLoader.cs
index d332a6b..1b7ea2c 100644
--- a/CustomNotes/Managers/NoteAssetLoader.cs
+++ b/CustomNotes/Managers/NoteAssetLoader.cs
@@ -1,12 +1,12 @@
-using System;
-using Zenject;
-using System.IO;
-using System.Linq;
-using CustomNotes.Data;
+using CustomNotes.Data;
using CustomNotes.Providers;
+using CustomNotes.Settings.Utilities;
using CustomNotes.Utilities;
+using System;
using System.Collections.Generic;
-using CustomNotes.Settings.Utilities;
+using System.IO;
+using System.Linq;
+using Zenject;
namespace CustomNotes.Managers
{
@@ -32,9 +32,13 @@ public int SelectedNote
_customGameNoteProvider.Priority = 300;
_customBombNoteProvider.Priority = CustomNoteObjects[_selectedNote].NoteBomb != null ? 300 : -1;
}
+
+ customNoteSelectionChangedEvent?.Invoke(_selectedNote, CustomNoteObjects[_selectedNote]);
}
}
+ public event Action customNoteSelectionChangedEvent;
+
public IList CustomNoteObjects { get; private set; }
public IEnumerable CustomNoteFiles { get; private set; } = Enumerable.Empty();
@@ -42,7 +46,9 @@ public int SelectedNote
private readonly CustomGameNoteProvider _customGameNoteProvider;
private readonly CustomBombNoteProvider _customBombNoteProvider;
- internal NoteAssetLoader(PluginConfig pluginConfig, CustomGameNoteProvider customGameNoteProvider, CustomBombNoteProvider customBombNoteProvider)
+ internal NoteAssetLoader(PluginConfig pluginConfig,
+ CustomGameNoteProvider customGameNoteProvider,
+ CustomBombNoteProvider customBombNoteProvider)
{
_pluginConfig = pluginConfig;
_customGameNoteProvider = customGameNoteProvider;
@@ -110,6 +116,21 @@ public void Dispose()
CustomNoteFiles = Enumerable.Empty();
}
+ ///
+ /// Returns the CustomNote with hash hash or null if it's not found
+ ///
+ /// The hash to look for
+ /// CustomNote
+ public CustomNote GetNoteByHash(string hash)
+ {
+ foreach(CustomNote note in CustomNoteObjects)
+ {
+ if (note.MD5Hash.Equals(hash))
+ return note;
+ }
+ return null;
+ }
+
private IList LoadCustomNotes(IEnumerable customNoteFiles)
{
IList customNotes = new List
diff --git a/CustomNotes/Packets/CustomNotesPacket.cs b/CustomNotes/Packets/CustomNotesPacket.cs
new file mode 100644
index 0000000..b9d7f8c
--- /dev/null
+++ b/CustomNotes/Packets/CustomNotesPacket.cs
@@ -0,0 +1,68 @@
+using LiteNetLib.Utils;
+using UnityEngine;
+
+namespace CustomNotes.Packets
+{
+ class CustomNotesPacket : INetSerializable, IPoolablePacket
+ {
+ public const int MAX_LENGTH = 32; // MD5 Hash length as a hexadecimal string
+ public const string DEFAULT_NOTES = "_hello_i_am_using_default_notes_";
+ public const float MIN_NOTE_SIZE = 0.4f;
+ public const float MAX_NOTE_SIZE = 2f;
+
+ private string _noteHash = DEFAULT_NOTES;
+ public string NoteHash
+ {
+ get => _noteHash;
+ set
+ {
+ if (value.Length < MAX_LENGTH)
+ {
+ _noteHash = value.PadRight(MAX_LENGTH);
+ }
+ else if (value.Length > MAX_LENGTH)
+ {
+ _noteHash = value.Truncate(MAX_LENGTH);
+ }
+ else
+ {
+ _noteHash = value;
+ }
+ }
+ }
+
+ private float _noteScale = 1f;
+ public float NoteScale
+ {
+ get => _noteScale;
+ set
+ {
+ _noteScale = Mathf.Max(Mathf.Min(value, MAX_NOTE_SIZE), MIN_NOTE_SIZE);
+ }
+ }
+
+ public void Release() => ThreadStaticPacketPool.pool.Release(this);
+
+ public void Serialize(NetDataWriter writer)
+ {
+ writer.Put(NoteHash, MAX_LENGTH);
+ writer.Put(NoteScale);
+ }
+
+ public void Deserialize(NetDataReader reader)
+ {
+ NoteHash = reader.GetString(MAX_LENGTH);
+ NoteScale = reader.GetFloat();
+ }
+
+ public CustomNotesPacket Init(string hash, float noteScale = 1f)
+ {
+ NoteHash = hash;
+ NoteScale = noteScale;
+
+ return this;
+ }
+
+ public static CustomNotesPacket CreatePacket(string hash, float noteScale = 1f) => ThreadStaticPacketPool.pool.Obtain().Init(hash, noteScale);
+ }
+}
diff --git a/CustomNotes/Providers/ConnectedPlayerNotePoolProvider.cs b/CustomNotes/Providers/ConnectedPlayerNotePoolProvider.cs
new file mode 100644
index 0000000..1128321
--- /dev/null
+++ b/CustomNotes/Providers/ConnectedPlayerNotePoolProvider.cs
@@ -0,0 +1,180 @@
+using CustomNotes.Data;
+using CustomNotes.Interfaces;
+using CustomNotes.Managers;
+using CustomNotes.Settings.Utilities;
+using CustomNotes.Utilities;
+using SiraUtil.Objects;
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+using System.Text;
+using UnityEngine;
+using Zenject;
+
+namespace CustomNotes.Providers
+{
+ internal class ConnectedPlayerNotePoolProvider : IInitializable, IDisposable
+ {
+ private DiContainer _container;
+ private PluginConfig _pluginConfig;
+ private NoteAssetLoader _noteAssetLoader;
+ private ICustomNoteNetworkPacketManager _noteHashPacketManager;
+
+ private readonly CustomMultiplayerNoteProvider _customMultiplayerNoteProvider;
+ private readonly CustomMultiplayerBombProvider _customMultiplayerBombProvider;
+
+ private IMultiplayerSessionManager _multiplayerSessionManager;
+
+ private Dictionary _connectedPlayerPoolIDs;
+
+ private static MD5 _staticMd5Hasher = MD5.Create();
+
+ [Inject]
+ internal ConnectedPlayerNotePoolProvider(DiContainer Container,
+ PluginConfig pluginConfig,
+ NoteAssetLoader noteAssetLoader,
+ ICustomNoteNetworkPacketManager noteHashPacketManager,
+ CustomMultiplayerNoteProvider customMultiplayerNoteProvider,
+ CustomMultiplayerBombProvider customMultiplayerBombProvider,
+ [InjectOptional] IMultiplayerSessionManager multiplayerSessionManager)
+ {
+ _container = Container;
+ _pluginConfig = pluginConfig;
+ _noteAssetLoader = noteAssetLoader;
+ _noteHashPacketManager = noteHashPacketManager;
+
+ _customMultiplayerNoteProvider = customMultiplayerNoteProvider;
+ _customMultiplayerBombProvider = customMultiplayerBombProvider;
+
+ _multiplayerSessionManager = multiplayerSessionManager;
+ }
+
+ public void Initialize()
+ {
+ if (_multiplayerSessionManager == null) return;
+
+ _connectedPlayerPoolIDs = new Dictionary();
+
+ if (_pluginConfig.OtherPlayerMultiplayerNotes)
+ {
+ _customMultiplayerNoteProvider.Priority = 300;
+ _customMultiplayerBombProvider.Priority = 300;
+ }
+ else
+ {
+ _customMultiplayerNoteProvider.Priority = -1;
+ _customMultiplayerBombProvider.Priority = -1;
+ return;
+ }
+
+ foreach (IConnectedPlayer connectedPlayer in _multiplayerSessionManager.connectedPlayers)
+ {
+ CreateNotePoolsForPlayer(connectedPlayer);
+ }
+ }
+
+ private void CreateNotePoolsForPlayer(IConnectedPlayer connectedPlayer)
+ {
+ Logger.log.Debug($"Creating note pools for player \"{connectedPlayer.userName}\" - \"{connectedPlayer.userId}\" ...");
+
+ CustomNote note = null;
+ float noteScale = 1f;
+ if (_pluginConfig.SyncNotesInMultiplayer && _noteHashPacketManager.HasDataFromPlayer(connectedPlayer))
+ {
+ Logger.log.Debug($"Preparing note from hash for player \"{connectedPlayer.userName}\"");
+
+ var multiplayerNoteData = _noteHashPacketManager.GetData(connectedPlayer);
+
+ note = _noteAssetLoader.GetNoteByHash(multiplayerNoteData.NoteHash);
+ noteScale = multiplayerNoteData.NoteScale;
+ }
+
+ if (note == null && _pluginConfig.RandomMultiplayerNotes)
+ {
+ System.Random rng;
+
+ if (_pluginConfig.RandomnessIsConsistentPerPlayer)
+ {
+ // Set the Random seed based on player ID -> same random number every time for the same player
+ var hashed = _staticMd5Hasher.ComputeHash(Encoding.UTF8.GetBytes(connectedPlayer?.userId));
+ var ivalue = BitConverter.ToInt32(hashed, 0);
+ rng = new System.Random(ivalue);
+ }
+ else
+ {
+ rng = new System.Random();
+ }
+
+ note = _pluginConfig.RandomMultiplayerNotes ? _noteAssetLoader.CustomNoteObjects[rng.Next(1, _noteAssetLoader.CustomNoteObjects.Count)] : _noteAssetLoader.CustomNoteObjects[_noteAssetLoader.SelectedNote];
+ }
+ else if(note == null)
+ {
+ Logger.log.Debug("using same notes as local player ...");
+ note = _noteAssetLoader.CustomNoteObjects[_noteAssetLoader.SelectedNote];
+ }
+
+ _container.Bind().WithId($"cn.multi.{connectedPlayer.userId}.scale").FromInstance(noteScale);
+
+ foreach (KeyValuePair entry in _connectedPlayerPoolIDs)
+ {
+ if (entry.Value.Equals(note.MD5Hash))
+ {
+ Logger.log.Debug($"Reusing old note pool for \"{note.FileName}\"");
+ _connectedPlayerPoolIDs.Add(connectedPlayer, note.MD5Hash);
+ return;
+ }
+
+ }
+
+ Logger.log.Debug($"Creating new pool for note \"{note.FileName}\" with hash \"{note.MD5Hash}\"");
+
+ MaterialSwapper.GetMaterials();
+ MaterialSwapper.ReplaceMaterialsForGameObject(note.NoteLeft);
+ MaterialSwapper.ReplaceMaterialsForGameObject(note.NoteRight);
+ MaterialSwapper.ReplaceMaterialsForGameObject(note.NoteDotLeft);
+ MaterialSwapper.ReplaceMaterialsForGameObject(note.NoteDotRight);
+ MaterialSwapper.ReplaceMaterialsForGameObject(note.NoteBomb);
+ Utils.AddMaterialPropertyBlockController(note.NoteLeft);
+ Utils.AddMaterialPropertyBlockController(note.NoteRight);
+ Utils.AddMaterialPropertyBlockController(note.NoteDotLeft);
+ Utils.AddMaterialPropertyBlockController(note.NoteDotRight);
+
+ _container.Bind().WithId($"cn.multi.{note.MD5Hash}.note").FromInstance(note);
+ _container.BindMemoryPool().WithId($"cn.multi.{note.MD5Hash}.left.arrow").WithInitialSize(25).FromComponentInNewPrefab(NotePrefabContainer(note.NoteLeft));
+ _container.BindMemoryPool().WithId($"cn.multi.{note.MD5Hash}.right.arrow").WithInitialSize(25).FromComponentInNewPrefab(NotePrefabContainer(note.NoteRight));
+ if (note.NoteDotLeft != null)
+ {
+ _container.BindMemoryPool().WithId($"cn.multi.{note.MD5Hash}.left.dot").WithInitialSize(10).FromComponentInNewPrefab(NotePrefabContainer(note.NoteDotLeft));
+ }
+ if (note.NoteDotRight != null)
+ {
+ _container.BindMemoryPool().WithId($"cn.multi.{note.MD5Hash}.right.dot").WithInitialSize(10).FromComponentInNewPrefab(NotePrefabContainer(note.NoteDotRight));
+ }
+ if (note.NoteBomb)
+ {
+ _container.BindMemoryPool().WithId($"cn.multi.{note.MD5Hash}.bomb").WithInitialSize(10).FromComponentInNewPrefab(NotePrefabContainer(note.NoteBomb));
+ }
+
+ _connectedPlayerPoolIDs.Add(connectedPlayer, note.MD5Hash);
+ }
+
+ public string GetPoolIDForPlayer(IConnectedPlayer connectedPlayer)
+ {
+ if (_connectedPlayerPoolIDs.TryGetValue(connectedPlayer, out string id))
+ return id;
+ return string.Empty;
+ }
+
+ private SiraPrefabContainer NotePrefabContainer(GameObject initialPrefab)
+ {
+ var prefab = new GameObject("CustomNotes_Multi" + initialPrefab.name).AddComponent();
+ prefab.Prefab = initialPrefab;
+ return prefab;
+ }
+
+ public void Dispose()
+ {
+
+ }
+ }
+}
diff --git a/CustomNotes/Providers/CustomBombNoteProvider.cs b/CustomNotes/Providers/CustomBombNoteProvider.cs
index 4dd2d9c..d4341a3 100644
--- a/CustomNotes/Providers/CustomBombNoteProvider.cs
+++ b/CustomNotes/Providers/CustomBombNoteProvider.cs
@@ -1,10 +1,10 @@
-using System;
-using Zenject;
-using UnityEngine;
-using SiraUtil.Objects;
-using SiraUtil.Interfaces;
-using CustomNotes.Managers;
+using CustomNotes.Managers;
using CustomNotes.Utilities;
+using SiraUtil.Interfaces;
+using SiraUtil.Objects;
+using System;
+using UnityEngine;
+using Zenject;
namespace CustomNotes.Providers
{
diff --git a/CustomNotes/Providers/CustomGameNoteProvider.cs b/CustomNotes/Providers/CustomGameNoteProvider.cs
index 4c88b10..fe00607 100644
--- a/CustomNotes/Providers/CustomGameNoteProvider.cs
+++ b/CustomNotes/Providers/CustomGameNoteProvider.cs
@@ -1,10 +1,10 @@
-using System;
-using Zenject;
-using UnityEngine;
-using SiraUtil.Objects;
-using SiraUtil.Interfaces;
-using CustomNotes.Managers;
+using CustomNotes.Managers;
using CustomNotes.Utilities;
+using SiraUtil.Interfaces;
+using SiraUtil.Objects;
+using System;
+using UnityEngine;
+using Zenject;
namespace CustomNotes.Providers
{
diff --git a/CustomNotes/Providers/CustomMultiplayerBombProvider.cs b/CustomNotes/Providers/CustomMultiplayerBombProvider.cs
new file mode 100644
index 0000000..aba5814
--- /dev/null
+++ b/CustomNotes/Providers/CustomMultiplayerBombProvider.cs
@@ -0,0 +1,36 @@
+using CustomNotes.Managers;
+using CustomNotes.Settings.Utilities;
+using SiraUtil.Interfaces;
+using System;
+using Zenject;
+
+namespace CustomNotes.Providers
+{
+ internal class CustomMultiplayerBombProvider : IModelProvider
+ {
+ public Type Type => typeof(CustomMultiplayerBombDecorator);
+ public int Priority { get; set; } = 300;
+
+ internal class CustomMultiplayerBombDecorator : IPrefabProvider
+ {
+ public bool Chain => true;
+ public bool CanSetup { get; private set; }
+
+ [Inject]
+ public void Construct(PluginConfig pluginConfig, GameplayCoreSceneSetupData sceneSetupData)
+ {
+ CanSetup = !(sceneSetupData.gameplayModifiers.ghostNotes || sceneSetupData.gameplayModifiers.disappearingArrows) && pluginConfig.OtherPlayerMultiplayerNotes;
+ }
+
+ public MultiplayerConnectedPlayerBombNoteController Modify(MultiplayerConnectedPlayerBombNoteController original)
+ {
+ if (!CanSetup) return original;
+ original.gameObject.AddComponent();
+ return original;
+ }
+
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/CustomNotes/Providers/CustomMultiplayerNoteProvider.cs b/CustomNotes/Providers/CustomMultiplayerNoteProvider.cs
new file mode 100644
index 0000000..c3cc4ee
--- /dev/null
+++ b/CustomNotes/Providers/CustomMultiplayerNoteProvider.cs
@@ -0,0 +1,38 @@
+using CustomNotes.Managers;
+using CustomNotes.Settings.Utilities;
+using SiraUtil.Interfaces;
+using System;
+using Zenject;
+
+namespace CustomNotes.Providers
+{
+ internal class CustomMultiplayerNoteProvider : IModelProvider
+ {
+ public Type Type => typeof(CustomMultiplayerNoteDecorator);
+ public int Priority { get; set; } = 300;
+
+ internal class CustomMultiplayerNoteDecorator : IPrefabProvider
+ {
+ public bool Chain => true;
+ public bool CanSetup { get; private set; }
+
+ [Inject]
+ public void Construct(DiContainer Container, GameplayCoreSceneSetupData sceneSetupData, PluginConfig pluginConfig)
+ {
+ //bool isMultiplayer = Container.HasBinding();
+ CanSetup = !(sceneSetupData.gameplayModifiers.ghostNotes || sceneSetupData.gameplayModifiers.disappearingArrows) && pluginConfig.OtherPlayerMultiplayerNotes;
+
+ Container.BindInterfacesAndSelfTo().AsSingle();
+ }
+
+ public MultiplayerConnectedPlayerGameNoteController Modify(MultiplayerConnectedPlayerGameNoteController original)
+ {
+ if (!CanSetup) return original;
+ original.gameObject.AddComponent();
+ return original;
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/CustomNotes/Settings/UI/NoteDetailsViewController.cs b/CustomNotes/Settings/UI/NoteDetailsViewController.cs
index 84c112c..3d4ec43 100644
--- a/CustomNotes/Settings/UI/NoteDetailsViewController.cs
+++ b/CustomNotes/Settings/UI/NoteDetailsViewController.cs
@@ -8,6 +8,10 @@
using Zenject;
using System;
using CustomNotes.Settings.UI;
+using System.Collections.Generic;
+using System.Linq;
+using CustomNotes.Utilities.MultiplayerCustomNoteModeExtensions;
+using BeatSaberMarkupLanguage.Parser;
namespace CustomNotes.Settings
{
@@ -18,6 +22,9 @@ internal class NoteDetailsViewController : BSMLResourceViewController
private PluginConfig _pluginConfig;
private NoteListViewController _listViewController;
+ [UIParams]
+ BSMLParserParams parserParams = null;
+
[UIComponent("note-description")]
public TextPageScrollView noteDescription = null;
@@ -51,5 +58,37 @@ public float noteSize
_listViewController.ScalePreviewNotes(value);
}
}
+
+ [UIValue("multi-note-mode-list-options")]
+ private List