From 0e7d956076a136a02cb5b01c0b747c77cf5feafb Mon Sep 17 00:00:00 2001 From: Extremelyd1 <10898310+Extremelyd1@users.noreply.github.com> Date: Sat, 27 Jul 2024 00:11:38 +0200 Subject: [PATCH] Partial refactor of save synchronisation --- HKMP/Game/Client/Save/SaveDataMapping.cs | 42 ++--- HKMP/Game/Client/Save/SaveManager.cs | 212 ++++++++++++----------- HKMP/Game/Server/ServerManager.cs | 33 +++- HKMP/Resource/save-data.json | 5 + 4 files changed, 160 insertions(+), 132 deletions(-) diff --git a/HKMP/Game/Client/Save/SaveDataMapping.cs b/HKMP/Game/Client/Save/SaveDataMapping.cs index cefa5fb..96cad60 100644 --- a/HKMP/Game/Client/Save/SaveDataMapping.cs +++ b/HKMP/Game/Client/Save/SaveDataMapping.cs @@ -40,7 +40,7 @@ public static SaveDataMapping Instance { /// Dictionary mapping player data values to booleans indicating whether they should be synchronised. /// [JsonProperty("playerData")] - public Dictionary PlayerDataBools { get; private set; } + public Dictionary PlayerDataSyncProperties { get; private set; } /// /// Bi-directional lookup that maps save data names and their indices. @@ -60,13 +60,13 @@ public static SaveDataMapping Instance { /// Dictionary mapping geo rock data values to booleans indicating whether they should be synchronised. /// [JsonIgnore] - public Dictionary GeoRockDataBools { get; private set; } + public Dictionary GeoRockBools { get; private set; } /// /// Bi-directional lookup that maps geo rock names and their indices. /// [JsonIgnore] - public BiLookup GeoRockDataIndices { get; private set; } + public BiLookup GeoRockIndices { get; private set; } /// /// Deserialized key-value pairs for the persistent bool data in the JSON. @@ -80,13 +80,13 @@ public static SaveDataMapping Instance { /// Dictionary mapping persistent bool data values to booleans indicating whether they should be synchronised. /// [JsonIgnore] - public Dictionary PersistentBoolDataBools { get; private set; } + public Dictionary PersistentBoolSyncProperties { get; private set; } /// /// Bi-directional lookup that maps persistent bool names and their indices. /// [JsonIgnore] - public BiLookup PersistentBoolDataIndices { get; private set; } + public BiLookup PersistentBoolIndices { get; private set; } /// /// Deserialized key-value pairs for the persistent int data in the JSON. @@ -100,13 +100,13 @@ public static SaveDataMapping Instance { /// Dictionary mapping persistent int data values to booleans indicating whether they should be synchronised. /// [JsonIgnore] - public Dictionary PersistentIntDataBools { get; private set; } + public Dictionary PersistentIntSyncProperties { get; private set; } /// /// Bi-directional lookup that maps persistent int names and their indices. /// [JsonIgnore] - public BiLookup PersistentIntDataIndices { get; private set; } + public BiLookup PersistentIntIndices { get; private set; } /// /// Deserialized list of strings that represent variable names with the type of a string list. @@ -130,7 +130,7 @@ public static SaveDataMapping Instance { /// Initializes the class by converting the deserialized data fields into the various dictionaries and lookups. /// public void Initialize() { - if (PlayerDataBools == null) { + if (PlayerDataSyncProperties == null) { Logger.Warn("Player data bools for save data is null"); return; } @@ -152,26 +152,26 @@ public void Initialize() { PlayerDataIndices = new BiLookup(); ushort index = 0; - foreach (var playerDataBool in PlayerDataBools.Keys) { + foreach (var playerDataBool in PlayerDataSyncProperties.Keys) { PlayerDataIndices.Add(playerDataBool, index++); } - GeoRockDataBools = _geoRockDataValues.ToDictionary(kv => kv.Key, kv => kv.Value); - GeoRockDataIndices = new BiLookup(); - foreach (var geoRockData in GeoRockDataBools.Keys) { - GeoRockDataIndices.Add(geoRockData, index++); + GeoRockBools = _geoRockDataValues.ToDictionary(kv => kv.Key, kv => kv.Value); + GeoRockIndices = new BiLookup(); + foreach (var geoRockData in GeoRockBools.Keys) { + GeoRockIndices.Add(geoRockData, index++); } - PersistentBoolDataBools = _persistentBoolsDataValues.ToDictionary(kv => kv.Key, kv => kv.Value); - PersistentBoolDataIndices = new BiLookup(); - foreach (var persistentBoolData in PersistentBoolDataBools.Keys) { - PersistentBoolDataIndices.Add(persistentBoolData, index++); + PersistentBoolSyncProperties = _persistentBoolsDataValues.ToDictionary(kv => kv.Key, kv => kv.Value); + PersistentBoolIndices = new BiLookup(); + foreach (var persistentBoolData in PersistentBoolSyncProperties.Keys) { + PersistentBoolIndices.Add(persistentBoolData, index++); } - PersistentIntDataBools = _persistentIntDataValues.ToDictionary(kv => kv.Key, kv => kv.Value); - PersistentIntDataIndices = new BiLookup(); - foreach (var persistentIntData in PersistentIntDataBools.Keys) { - PersistentIntDataIndices.Add(persistentIntData, index++); + PersistentIntSyncProperties = _persistentIntDataValues.ToDictionary(kv => kv.Key, kv => kv.Value); + PersistentIntIndices = new BiLookup(); + foreach (var persistentIntData in PersistentIntSyncProperties.Keys) { + PersistentIntIndices.Add(persistentIntData, index++); } } diff --git a/HKMP/Game/Client/Save/SaveManager.cs b/HKMP/Game/Client/Save/SaveManager.cs index 4620fcf..4099c11 100644 --- a/HKMP/Game/Client/Save/SaveManager.cs +++ b/HKMP/Game/Client/Save/SaveManager.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; +using GlobalEnums; using Hkmp.Collection; using Hkmp.Game.Client.Entity; using Hkmp.Networking.Client; @@ -64,6 +66,13 @@ internal class SaveManager { /// private readonly Dictionary _bsCompHashes; + private readonly List _playerDataSyncFields; + + /// + /// PlayerData instance that contains the last values of the currently used PlayerData for comparison checking. + /// + private PlayerData _lastPlayerData; + /// /// Whether the player is hosting the server, which means that player specific save data is not networked /// to the server. @@ -80,24 +89,89 @@ public SaveManager(NetClient netClient, PacketManager packetManager, EntityManag _stringListHashes = new Dictionary(); _bsdCompHashes = new Dictionary(); _bsCompHashes = new Dictionary(); + _playerDataSyncFields = new List(); } /// /// Initializes the save manager by loading the save data json. /// public void Initialize() { - ModHooks.SetPlayerBoolHook += OnSetPlayerBoolHook; - ModHooks.SetPlayerFloatHook += OnSetPlayerFloatHook; - ModHooks.SetPlayerIntHook += OnSetPlayerIntHook; - ModHooks.SetPlayerStringHook += OnSetPlayerStringHook; - ModHooks.SetPlayerVector3Hook += OnSetPlayerVector3Hook; - + On.GameManager.StartNewGame += (orig, self, mode, rushMode) => { + orig(self, mode, rushMode); + ResetLastPlayerData(); + MonoBehaviourUtil.Instance.OnUpdateEvent += OnUpdatePlayerData; + }; + On.GameManager.ContinueGame += (orig, self) => { + orig(self); + ResetLastPlayerData(); + MonoBehaviourUtil.Instance.OnUpdateEvent += OnUpdatePlayerData; + }; + On.UIManager.GoToMainMenu += (orig, self) => { + MonoBehaviourUtil.Instance.OnUpdateEvent -= OnUpdatePlayerData; + return orig(self); + }; + UnityEngine.SceneManagement.SceneManager.activeSceneChanged += OnSceneChanged; MonoBehaviourUtil.Instance.OnUpdateEvent += OnUpdatePersistents; MonoBehaviourUtil.Instance.OnUpdateEvent += OnUpdateCompounds; _packetManager.RegisterClientPacketHandler(ClientPacketId.SaveUpdate, UpdateSaveWithData); + + foreach (var field in typeof(PlayerData).GetFields()) { + var fieldName = field.Name; + if (SaveDataMapping.PlayerDataSyncProperties.TryGetValue(fieldName, out var syncProps) && syncProps.Sync) { + _playerDataSyncFields.Add(field); + } + } + } + + /// + /// Resets the PlayerData instance that stores the last values of all synchronised fields. + /// + private void ResetLastPlayerData() { + var pd = PlayerData.instance; + + var pdConstructor = typeof(PlayerData).GetConstructor( + BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.Instance, + null, + [], + null + ); + if (pdConstructor == null) { + Logger.Error("Could not find protected constructor of PlayerData"); + return; + } + + _lastPlayerData = (PlayerData) pdConstructor.Invoke([]); + + foreach (var field in _playerDataSyncFields) { + var value = field.GetValue(pd); + field.SetValue(_lastPlayerData, value); + } + } + + /// + /// Update hook to check for changes in the PlayerData instance. + /// + private void OnUpdatePlayerData() { + var pd = PlayerData.instance; + if (_lastPlayerData == null) { + return; + } + + foreach (var field in _playerDataSyncFields) { + var currentValue = field.GetValue(pd); + var lastValue = field.GetValue(_lastPlayerData); + + if (!currentValue.Equals(lastValue)) { + Logger.Debug($"PlayerData value changed from: {lastValue} to {currentValue}"); + + field.SetValue(_lastPlayerData, currentValue); + + CheckSendSaveUpdate(field.Name, () => EncodeValue(currentValue)); + } + } } /// @@ -185,83 +259,11 @@ byte[] EncodeString(string stringValue) { return [EncodeUtil.GetByte(bools)]; } - throw new NotImplementedException($"No encoding implementation for type: {value.GetType()}"); - } - - /// - /// Callback method for when a boolean is set in the player data. - /// - /// Name of the boolean variable. - /// The original value of the boolean. - private bool OnSetPlayerBoolHook(string name, bool orig) { - if (PlayerData.instance.GetBool(name) == orig) { - return orig; - } - - CheckSendSaveUpdate(name, () => EncodeValue(orig)); - - return orig; - } - - /// - /// Callback method for when a float is set in the player data. - /// - /// Name of the float variable. - /// The original value of the float. - private float OnSetPlayerFloatHook(string name, float orig) { - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (PlayerData.instance.GetFloat(name) == orig) { - return orig; + if (value is MapZone mapZone) { + return [(byte) mapZone]; } - CheckSendSaveUpdate(name, () => EncodeValue(orig)); - - return orig; - } - - /// - /// Callback method for when a int is set in the player data. - /// - /// Name of the int variable. - /// The original value of the int. - private int OnSetPlayerIntHook(string name, int orig) { - if (PlayerData.instance.GetInt(name) == orig) { - return orig; - } - - CheckSendSaveUpdate(name, () => EncodeValue(orig)); - - return orig; - } - - /// - /// Callback method for when a string is set in the player data. - /// - /// Name of the string variable. - /// The original value of the boolean. - private string OnSetPlayerStringHook(string name, string res) { - if (PlayerData.instance.GetString(name) == res) { - return res; - } - - CheckSendSaveUpdate(name, () => EncodeValue(res)); - - return res; - } - - /// - /// Callback method for when a vector3 is set in the player data. - /// - /// Name of the vector3 variable. - /// The original value of the vector3. - private Vector3 OnSetPlayerVector3Hook(string name, Vector3 orig) { - if (PlayerData.instance.GetVector3(name) == orig) { - return orig; - } - - CheckSendSaveUpdate(name, () => EncodeValue(orig)); - - return orig; + throw new NotImplementedException($"No encoding implementation for type: {value.GetType()}"); } /// @@ -393,7 +395,7 @@ private void CheckSendSaveUpdate(string name, Func encodeFunc) { return; } - if (!SaveDataMapping.PlayerDataBools.TryGetValue(name, out var syncProps)) { + if (!SaveDataMapping.PlayerDataSyncProperties.TryGetValue(name, out var syncProps)) { Logger.Info($"Not in save data values, not sending save update ({name})"); return; } @@ -455,14 +457,14 @@ private void OnUpdatePersistents() { continue; } - if (SaveDataMapping.GeoRockDataBools.TryGetValue(itemData, out var shouldSync) && shouldSync) { + if (SaveDataMapping.GeoRockBools.TryGetValue(itemData, out var shouldSync) && shouldSync) { if (!_entityManager.IsSceneHost) { Logger.Info( $"Not scene host, not sending geo rock save update ({itemData.Id}, {itemData.SceneName})"); continue; } - if (!SaveDataMapping.GeoRockDataIndices.TryGetValue(itemData, out var index)) { + if (!SaveDataMapping.GeoRockIndices.TryGetValue(itemData, out var index)) { Logger.Info( $"Cannot find geo rock save data index, not sending save update ({itemData.Id}, {itemData.SceneName})"); continue; @@ -475,7 +477,7 @@ private void OnUpdatePersistents() { new[] { (byte) value } ); } else if ( - SaveDataMapping.PersistentIntDataBools.TryGetValue(itemData, out var syncProps) && + SaveDataMapping.PersistentIntSyncProperties.TryGetValue(itemData, out var syncProps) && syncProps.Sync ) { // If we should do the scene host check and the player is not scene host, skip sending @@ -490,7 +492,7 @@ private void OnUpdatePersistents() { continue; } - if (!SaveDataMapping.PersistentIntDataIndices.TryGetValue(itemData, out var index)) { + if (!SaveDataMapping.PersistentIntIndices.TryGetValue(itemData, out var index)) { Logger.Info( $"Cannot find persistent int save data index, not sending save update ({itemData.Id}, {itemData.SceneName})"); continue; @@ -521,7 +523,7 @@ private void OnUpdatePersistents() { continue; } - if (!SaveDataMapping.PersistentBoolDataBools.TryGetValue(itemData, out var syncProps) || + if (!SaveDataMapping.PersistentBoolSyncProperties.TryGetValue(itemData, out var syncProps) || !syncProps.Sync) { Logger.Info( $"Not in persistent bool save data values or false in sync props, not sending save update ({itemData.Id}, {itemData.SceneName})"); @@ -540,7 +542,7 @@ private void OnUpdatePersistents() { continue; } - if (!SaveDataMapping.PersistentBoolDataIndices.TryGetValue(itemData, out var index)) { + if (!SaveDataMapping.PersistentBoolIndices.TryGetValue(itemData, out var index)) { Logger.Info( $"Cannot find persistent bool save data index, not sending save update ({itemData.Id}, {itemData.SceneName})"); continue; @@ -583,7 +585,7 @@ Func changeFunc checkDict[varName] = newCheck; - if (!SaveDataMapping.PlayerDataBools.TryGetValue(varName, out var syncProps)) { + if (!SaveDataMapping.PlayerDataSyncProperties.TryGetValue(varName, out var syncProps)) { continue; } @@ -696,7 +698,7 @@ private void UpdateSaveWithData(ushort index, byte[] encodedValue) { var sceneData = SceneData.instance; if (SaveDataMapping.PlayerDataIndices.TryGetValue(index, out var name)) { - if (CheckPlayerSpecificHosting(SaveDataMapping.PlayerDataBools, name)) { + if (CheckPlayerSpecificHosting(SaveDataMapping.PlayerDataSyncProperties, name)) { return; } @@ -805,6 +807,12 @@ private void UpdateSaveWithData(ushort index, byte[] encodedValue) { _bsCompHashes[name] = bsComp; pd.SetVariableInternal(name, bsComp); + } else if (type == typeof(MapZone)) { + if (valueLength != 1) { + Logger.Warn($"Received save update with incorrect value length for MapZone: {valueLength}"); + } + + pd.SetVariableInternal(name, (MapZone) encodedValue[0]); } else { throw new NotImplementedException($"Could not decode type: {type}"); } @@ -812,7 +820,7 @@ private void UpdateSaveWithData(ushort index, byte[] encodedValue) { _saveChanges.ApplyPlayerDataSaveChange(name); } - if (SaveDataMapping.GeoRockDataIndices.TryGetValue(index, out var itemData)) { + if (SaveDataMapping.GeoRockIndices.TryGetValue(index, out var itemData)) { var value = encodedValue[0]; Logger.Info($"Received geo rock save update: {itemData.Id}, {itemData.SceneName}, {value}"); @@ -831,8 +839,8 @@ private void UpdateSaveWithData(ushort index, byte[] encodedValue) { sceneName = itemData.SceneName, hitsLeft = value }); - } else if (SaveDataMapping.PersistentBoolDataIndices.TryGetValue(index, out itemData)) { - if (CheckPlayerSpecificHosting(SaveDataMapping.PersistentBoolDataBools, itemData)) { + } else if (SaveDataMapping.PersistentBoolIndices.TryGetValue(index, out itemData)) { + if (CheckPlayerSpecificHosting(SaveDataMapping.PersistentBoolSyncProperties, itemData)) { return; } @@ -857,8 +865,8 @@ private void UpdateSaveWithData(ushort index, byte[] encodedValue) { }); _saveChanges.ApplyPersistentValueSaveChange(itemData); - } else if (SaveDataMapping.PersistentIntDataIndices.TryGetValue(index, out itemData)) { - if (CheckPlayerSpecificHosting(SaveDataMapping.PersistentIntDataBools, itemData)) { + } else if (SaveDataMapping.PersistentIntIndices.TryGetValue(index, out itemData)) { + if (CheckPlayerSpecificHosting(SaveDataMapping.PersistentIntSyncProperties, itemData)) { return; } @@ -971,7 +979,7 @@ Func valueFunc AddToSaveData( typeof(PlayerData).GetFields(), fieldInfo => fieldInfo.Name, - SaveDataMapping.PlayerDataBools, + SaveDataMapping.PlayerDataSyncProperties, SaveDataMapping.PlayerDataIndices, fieldInfo => fieldInfo.GetValue(pd) ); @@ -982,8 +990,8 @@ Func valueFunc Id = geoRock.id, SceneName = geoRock.sceneName }, - SaveDataMapping.GeoRockDataBools, - SaveDataMapping.GeoRockDataIndices, + SaveDataMapping.GeoRockBools, + SaveDataMapping.GeoRockIndices, geoRock => geoRock.hitsLeft ); @@ -993,8 +1001,8 @@ Func valueFunc Id = boolData.id, SceneName = boolData.sceneName }, - SaveDataMapping.PersistentBoolDataBools, - SaveDataMapping.PersistentBoolDataIndices, + SaveDataMapping.PersistentBoolSyncProperties, + SaveDataMapping.PersistentBoolIndices, boolData => boolData.activated ); @@ -1004,8 +1012,8 @@ Func valueFunc Id = intData.id, SceneName = intData.sceneName }, - SaveDataMapping.PersistentIntDataBools, - SaveDataMapping.PersistentIntDataIndices, + SaveDataMapping.PersistentIntSyncProperties, + SaveDataMapping.PersistentIntIndices, intData => intData.value ); diff --git a/HKMP/Game/Server/ServerManager.cs b/HKMP/Game/Server/ServerManager.cs index 89a7102..67b7b18 100644 --- a/HKMP/Game/Server/ServerManager.cs +++ b/HKMP/Game/Server/ServerManager.cs @@ -1336,8 +1336,10 @@ protected virtual void OnSaveUpdate(ushort id, SaveUpdate packet) { // Find the properties for syncing this save update, based on whether it is a geo rock, player data or // persistent bool/int item SaveDataMapping.SyncProperties syncProps; - if (SaveDataMapping.Instance.GeoRockDataIndices.TryGetValue(packet.SaveDataIndex, out var persistentItemData)) { - if (!SaveDataMapping.Instance.GeoRockDataBools.TryGetValue(persistentItemData, out _)) { + if (SaveDataMapping.Instance.GeoRockIndices.TryGetValue(packet.SaveDataIndex, out var persistentItemData)) { + Logger.Debug($" Found GeoRockData: {persistentItemData.Id}, {persistentItemData.SceneName}"); + + if (!SaveDataMapping.Instance.GeoRockBools.TryGetValue(persistentItemData, out _)) { return; } @@ -1347,42 +1349,55 @@ protected virtual void OnSaveUpdate(ushort id, SaveUpdate packet) { IgnoreSceneHost = false }; } else if (SaveDataMapping.Instance.PlayerDataIndices.TryGetValue(packet.SaveDataIndex, out var name)) { - if (!SaveDataMapping.Instance.PlayerDataBools.TryGetValue(name, out syncProps)) { + Logger.Debug($" Found PlayerData: {name}"); + + if (!SaveDataMapping.Instance.PlayerDataSyncProperties.TryGetValue(name, out syncProps)) { return; } - } else if (SaveDataMapping.Instance.PersistentBoolDataIndices.TryGetValue( + } else if (SaveDataMapping.Instance.PersistentBoolIndices.TryGetValue( packet.SaveDataIndex, out persistentItemData) ) { - if (!SaveDataMapping.Instance.PersistentBoolDataBools.TryGetValue(persistentItemData, out syncProps)) { + Logger.Debug($" Found PersistentBoolData: {persistentItemData.Id}, {persistentItemData.SceneName}"); + + if (!SaveDataMapping.Instance.PersistentBoolSyncProperties.TryGetValue(persistentItemData, out syncProps)) { return; } - } else if (SaveDataMapping.Instance.PersistentIntDataIndices.TryGetValue( + } else if (SaveDataMapping.Instance.PersistentIntIndices.TryGetValue( packet.SaveDataIndex, out persistentItemData) ) { - if (!SaveDataMapping.Instance.PersistentIntDataBools.TryGetValue(persistentItemData, out syncProps)) { + Logger.Debug($" Found PersistentIntData: {persistentItemData.Id}, {persistentItemData.SceneName}"); + + if (!SaveDataMapping.Instance.PersistentIntSyncProperties.TryGetValue(persistentItemData, out syncProps)) { return; } } else { - Logger.Info(" Could not find sync props for save update"); + Logger.Debug(" Could not find sync props for save update"); return; } // Check whether this save update requires the player to be scene host and do the check for it if (!syncProps.IgnoreSceneHost && !playerData.IsSceneHost) { - Logger.Info(" Player is not scene host, but should be for update, not broadcasting"); + Logger.Debug(" Player is not scene host, but should be for update, not broadcasting"); return; } if (syncProps.SyncType == SaveDataMapping.SyncType.Player) { + Logger.Debug(" SyncType is Player"); + if (!ServerSaveData.PlayerSaveData.TryGetValue(playerData.AuthKey, out var playerSaveData)) { + Logger.Debug(" No PlayerSaveData for player yet, creating one"); playerSaveData = new Dictionary(); ServerSaveData.PlayerSaveData[playerData.AuthKey] = playerSaveData; } + + Logger.Debug(" Storing player data"); playerSaveData[packet.SaveDataIndex] = packet.Value; } else if (syncProps.SyncType == SaveDataMapping.SyncType.Server) { + Logger.Debug(" SyncType is Server, broadcasting save update"); + ServerSaveData.GlobalSaveData[packet.SaveDataIndex] = packet.Value; foreach (var idPlayerDataPair in _playerData) { diff --git a/HKMP/Resource/save-data.json b/HKMP/Resource/save-data.json index 9079fb9..468681c 100644 --- a/HKMP/Resource/save-data.json +++ b/HKMP/Resource/save-data.json @@ -45,6 +45,11 @@ "SyncType": "Player", "IgnoreSceneHost": true }, + "mapZone": { + "Sync": true, + "SyncType": "Player", + "IgnoreSceneHost": true + }, "shadeScene": { "Sync": true, "SyncType": "Player",