diff --git a/HKMP/Game/Client/ClientManager.cs b/HKMP/Game/Client/ClientManager.cs index a03ea54..5f2089a 100644 --- a/HKMP/Game/Client/ClientManager.cs +++ b/HKMP/Game/Client/ClientManager.cs @@ -6,6 +6,7 @@ using Hkmp.Eventing; using Hkmp.Fsm; using Hkmp.Game.Client.Entity; +using Hkmp.Game.Client.Save; using Hkmp.Game.Command.Client; using Hkmp.Game.Server; using Hkmp.Game.Settings; @@ -179,7 +180,8 @@ ModSettings modSettings _mapManager = new MapManager(netClient, serverSettings); _entityManager = new EntityManager(netClient); - + + new SaveManager(netClient, packetManager, _entityManager).Initialize(); new PauseManager(netClient).RegisterHooks(); new FsmPatcher().RegisterHooks(); diff --git a/HKMP/Game/Client/Entity/EntityManager.cs b/HKMP/Game/Client/Entity/EntityManager.cs index e4e7db3..63a29ad 100644 --- a/HKMP/Game/Client/Entity/EntityManager.cs +++ b/HKMP/Game/Client/Entity/EntityManager.cs @@ -31,12 +31,12 @@ internal class EntityManager { /// /// Whether the scene host is determined for this scene locally. /// - private bool _isSceneHostDetermined; + public bool IsSceneHostDetermined { get; private set; } /// /// Whether the client user is the scene host. /// - private bool _isSceneHost; + public bool IsSceneHost { get; private set; } /// /// Queue of entity updates that have not been applied yet because of a missing entity. @@ -64,13 +64,13 @@ public EntityManager(NetClient netClient) { public void InitializeSceneHost() { Logger.Info("We are scene host, releasing control of all registered entities"); - _isSceneHost = true; + IsSceneHost = true; foreach (var entity in _entities.Values) { entity.InitializeHost(); } - _isSceneHostDetermined = true; + IsSceneHostDetermined = true; CheckReceivedUpdates(); } @@ -81,9 +81,9 @@ public void InitializeSceneHost() { public void InitializeSceneClient() { Logger.Info("We are scene client, taking control of all registered entities"); - _isSceneHost = false; + IsSceneHost = false; - _isSceneHostDetermined = true; + IsSceneHostDetermined = true; CheckReceivedUpdates(); } @@ -94,7 +94,7 @@ public void InitializeSceneClient() { public void BecomeSceneHost() { Logger.Info("Becoming scene host"); - _isSceneHost = true; + IsSceneHost = true; foreach (var entity in _entities.Values) { entity.MakeHost(); @@ -136,7 +136,7 @@ public void SpawnEntity(ushort id, EntityType spawningType, EntityType spawnedTy var processor = new EntityProcessor { GameObject = spawnedObject, - IsSceneHost = _isSceneHost, + IsSceneHost = IsSceneHost, LateLoad = true, SpawnedId = id }.Process(); @@ -152,12 +152,12 @@ public void SpawnEntity(ushort id, EntityType spawningType, EntityType spawnedTy /// The entity update to handle. /// Whether this is the update from the already in scene packet. public bool HandleEntityUpdate(EntityUpdate entityUpdate, bool alreadyInSceneUpdate = false) { - if (_isSceneHost) { + if (IsSceneHost) { return true; } - if (!_entities.TryGetValue(entityUpdate.Id, out var entity) || !_isSceneHostDetermined) { - if (_isSceneHostDetermined) { + if (!_entities.TryGetValue(entityUpdate.Id, out var entity) || !IsSceneHostDetermined) { + if (IsSceneHostDetermined) { Logger.Debug($"Could not find entity ({entityUpdate.Id}) to apply update for; storing update for now"); } else { Logger.Debug("Scene host is not determined yet to apply update; storing update for now"); @@ -193,12 +193,12 @@ public bool HandleEntityUpdate(EntityUpdate entityUpdate, bool alreadyInSceneUpd /// The reliable entity update to handle. /// Whether this is the update from the already in scene packet. public bool HandleReliableEntityUpdate(ReliableEntityUpdate entityUpdate, bool alreadyInSceneUpdate = false) { - if (_isSceneHost) { + if (IsSceneHost) { return true; } - if (!_entities.TryGetValue(entityUpdate.Id, out var entity) || !_isSceneHostDetermined) { - if (_isSceneHostDetermined) { + if (!_entities.TryGetValue(entityUpdate.Id, out var entity) || !IsSceneHostDetermined) { + if (IsSceneHostDetermined) { Logger.Debug($"Could not find entity ({entityUpdate.Id}) to apply update for; storing update for now"); } else { Logger.Debug("Scene host is not determined yet to apply update; storing update for now"); @@ -237,7 +237,7 @@ private bool OnGameObjectSpawned(EntitySpawnDetails details) { var processor = new EntityProcessor { GameObject = details.GameObject, - IsSceneHost = _isSceneHost, + IsSceneHost = IsSceneHost, LateLoad = true }.Process(); @@ -245,7 +245,7 @@ private bool OnGameObjectSpawned(EntitySpawnDetails details) { return false; } - if (!_isSceneHost) { + if (!IsSceneHost) { Logger.Warn("Game object was spawned while not scene host, this shouldn't happen"); return false; } @@ -335,7 +335,7 @@ private void OnSceneChanged(Scene oldScene, Scene newScene) { // those entities CheckReceivedUpdates(); - _isSceneHostDetermined = false; + IsSceneHostDetermined = false; } /// @@ -443,7 +443,7 @@ private void FindEntitiesInScene(Scene scene, bool lateLoad) { foreach (var obj in objectsToCheck) { new EntityProcessor { GameObject = obj, - IsSceneHost = _isSceneHost, + IsSceneHost = IsSceneHost, LateLoad = lateLoad }.Process(); } diff --git a/HKMP/Game/Client/Save/SaveManager.cs b/HKMP/Game/Client/Save/SaveManager.cs new file mode 100644 index 0000000..f3c476e --- /dev/null +++ b/HKMP/Game/Client/Save/SaveManager.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hkmp.Collection; +using Hkmp.Game.Client.Entity; +using Hkmp.Networking.Client; +using Hkmp.Networking.Packet; +using Hkmp.Networking.Packet.Data; +using Hkmp.Util; +using Modding; +using UnityEngine; +using Logger = Hkmp.Logging.Logger; + +namespace Hkmp.Game.Client.Save; + +/// +/// Class that manages save data synchronisation. +/// +internal class SaveManager { + /// + /// The file path of the embedded resource file for save data. + /// + private const string SaveDataFilePath = "Hkmp.Resource.save-data.json"; + + /// + /// The net client instance to send save updates. + /// + private readonly NetClient _netClient; + /// + /// The packet manager instance to register a callback for when save updates are received. + /// + private readonly PacketManager _packetManager; + /// + /// The entity manager to check whether we are scene host. + /// + private readonly EntityManager _entityManager; + + /// + /// Dictionary mapping save data values to booleans indicating whether they should be synchronised. + /// + private Dictionary _saveDataValues; + + /// + /// Bi-directional lookup that maps save data names and their indices. + /// + private BiLookup _saveDataIndices; + + public SaveManager(NetClient netClient, PacketManager packetManager, EntityManager entityManager) { + _netClient = netClient; + _packetManager = packetManager; + _entityManager = entityManager; + } + + /// + /// Initializes the save manager by loading the save data json. + /// + public void Initialize() { + _saveDataValues = FileUtil.LoadObjectFromEmbeddedJson>(SaveDataFilePath); + if (_saveDataValues == null) { + Logger.Warn("Could not load save data json"); + return; + } + + _saveDataIndices = new BiLookup(); + ushort index = 0; + foreach (var saveDataName in _saveDataValues.Keys) { + Logger.Info($"Saving ({saveDataName}, {index}) in bi-lookup"); + _saveDataIndices.Add(saveDataName, index++); + } + + ModHooks.SetPlayerBoolHook += OnSetPlayerBoolHook; + ModHooks.SetPlayerFloatHook += OnSetPlayerFloatHook; + ModHooks.SetPlayerIntHook += OnSetPlayerIntHook; + ModHooks.SetPlayerStringHook += OnSetPlayerStringHook; + ModHooks.SetPlayerVariableHook += OnSetPlayerVariableHook; + ModHooks.SetPlayerVector3Hook += OnSetPlayerVector3Hook; + + _packetManager.RegisterClientPacketHandler(ClientPacketId.SaveUpdate, UpdateSaveWithData); + } + + /// + /// 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) { + CheckSendSaveUpdate(name, () => { + return new[] { (byte) (orig ? 0 : 1) }; + }); + + 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) { + CheckSendSaveUpdate(name, () => BitConverter.GetBytes(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) { + CheckSendSaveUpdate(name, () => BitConverter.GetBytes(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) { + CheckSendSaveUpdate(name, () => { + var byteEncodedString = Encoding.UTF8.GetBytes(res); + + if (byteEncodedString.Length > ushort.MaxValue) { + throw new Exception($"Could not encode string of length: {byteEncodedString.Length}"); + } + + var value = BitConverter.GetBytes((ushort) byteEncodedString.Length) + .Concat(byteEncodedString) + .ToArray(); + return value; + }); + + return res; + } + + /// + /// Callback method for when an object is set in the player data. + /// + /// The type of the object. + /// Name of the object variable. + /// The original value of the object. + private object OnSetPlayerVariableHook(Type type, string name, object value) { + throw new NotImplementedException($"Object with type: {value.GetType()} could not be encoded"); + } + + /// + /// 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) { + CheckSendSaveUpdate(name, () => + BitConverter.GetBytes(orig.x) + .Concat(BitConverter.GetBytes(orig.y)) + .Concat(BitConverter.GetBytes(orig.z)) + .ToArray() + ); + + return orig; + } + + /// + /// Checks if a save update should be sent and send it using the encode function to encode the value of the + /// changed variable. + /// + /// The name of the variable that was changed. + /// Function that encodes the value of the variable into a byte array. + private void CheckSendSaveUpdate(string name, Func encodeFunc) { + if (!_entityManager.IsSceneHost) { + Logger.Info($"Not scene host, not sending save update ({name})"); + return; + } + + if (!_saveDataValues.TryGetValue(name, out var value) || !value) { + Logger.Info($"Not in save data values or false in save data values, not sending save update ({name})"); + return; + } + + if (!_saveDataIndices.TryGetValue(name, out var index)) { + Logger.Info($"Cannot find save data index, not sending save update ({name})"); + return; + } + + Logger.Info($"Sending \"{name}\" as save update"); + + _netClient.UpdateManager.SetSaveUpdate( + index, + encodeFunc.Invoke() + ); + } + + /// + /// Callback method for when a save update is received. + /// + /// The save update that was received. + private void UpdateSaveWithData(SaveUpdate saveUpdate) { + if (!_saveDataIndices.TryGetValue(saveUpdate.SaveDataIndex, out var name)) { + Logger.Warn($"Received save update with unknown index: {saveUpdate.SaveDataIndex}"); + return; + } + + Logger.Info($"Received save update for index: {saveUpdate.SaveDataIndex}"); + + var pd = PlayerData.instance; + + var fieldInfo = typeof(PlayerData).GetField(name); + var type = fieldInfo.FieldType; + var valueLength = saveUpdate.Value.Length; + + if (type == typeof(bool)) { + if (valueLength != 1) { + Logger.Warn($"Received save update with incorrect value length for bool: {valueLength}"); + } + + var value = saveUpdate.Value[0] == 1; + + pd.SetBoolInternal(name, value); + } else if (type == typeof(float)) { + if (valueLength != 4) { + Logger.Warn($"Received save update with incorrect value length for float: {valueLength}"); + } + + var value = BitConverter.ToSingle(saveUpdate.Value, 0); + + pd.SetFloatInternal(name, value); + } else if (type == typeof(int)) { + if (valueLength != 4) { + Logger.Warn($"Received save update with incorrect value length for int: {valueLength}"); + } + + var value = BitConverter.ToInt32(saveUpdate.Value, 0); + + pd.SetIntInternal(name, value); + } else if (type == typeof(string)) { + var value = Encoding.UTF8.GetString(saveUpdate.Value); + + pd.SetStringInternal(name, value); + } else if (type == typeof(Vector3)) { + if (valueLength != 12) { + Logger.Warn($"Received save update with incorrect value length for vector3: {valueLength}"); + } + + var value = new Vector3( + BitConverter.ToSingle(saveUpdate.Value, 0), + BitConverter.ToSingle(saveUpdate.Value, 4), + BitConverter.ToSingle(saveUpdate.Value, 8) + ); + + pd.SetVector3Internal(name, value); + } + } +} diff --git a/HKMP/Game/Server/ServerManager.cs b/HKMP/Game/Server/ServerManager.cs index 6b5b9e4..9dd73f0 100644 --- a/HKMP/Game/Server/ServerManager.cs +++ b/HKMP/Game/Server/ServerManager.cs @@ -147,6 +147,7 @@ PacketManager packetManager packetManager.RegisterServerPacketHandler(ServerPacketId.PlayerSkinUpdate, OnPlayerSkinUpdate); packetManager.RegisterServerPacketHandler(ServerPacketId.ChatMessage, OnChatMessage); + packetManager.RegisterServerPacketHandler(ServerPacketId.SaveUpdate, OnSaveUpdate); // Register a timeout handler _netServer.ClientTimeoutEvent += OnClientTimeout; @@ -1277,6 +1278,34 @@ private void OnChatMessage(ushort id, ChatMessage chatMessage) { } } + /// + /// Callback method for when a save update is received from a player. + /// + /// The ID of the player. + /// The SaveUpdate packet data. + private void OnSaveUpdate(ushort id, SaveUpdate packet) { + if (!_playerData.TryGetValue(id, out var playerData)) { + Logger.Debug($"Could not process save update from unknown player ID: {id}"); + return; + } + + Logger.Info($"Save update from ({id}, {playerData.Username}), index: {packet.SaveDataIndex}"); + + if (!playerData.IsSceneHost) { + Logger.Info(" Player is not scene host, not broadcasting update"); + return; + } + + foreach (var idPlayerDataPair in _playerData) { + var otherId = idPlayerDataPair.Key; + if (id == otherId) { + continue; + } + + _netServer.GetUpdateManagerForClient(otherId).SetSaveUpdate(packet.SaveDataIndex, packet.Value); + } + } + #endregion #region IServerManager methods diff --git a/HKMP/HKMP.csproj b/HKMP/HKMP.csproj index b30b825..700d882 100644 --- a/HKMP/HKMP.csproj +++ b/HKMP/HKMP.csproj @@ -21,6 +21,7 @@ + diff --git a/HKMP/Networking/Client/ClientUpdateManager.cs b/HKMP/Networking/Client/ClientUpdateManager.cs index 6299bfc..ccc8d64 100644 --- a/HKMP/Networking/Client/ClientUpdateManager.cs +++ b/HKMP/Networking/Client/ClientUpdateManager.cs @@ -438,4 +438,27 @@ public void SetChatMessage(string message) { }); } } + + /// + /// Set save update data. + /// + /// The index of the save data entry. + /// The array of bytes that represents the changed value. + public void SetSaveUpdate(ushort index, byte[] value) { + lock (Lock) { + PacketDataCollection saveUpdateCollection; + + if (CurrentUpdatePacket.TryGetSendingPacketData(ServerPacketId.SaveUpdate, out var packetData)) { + saveUpdateCollection = (PacketDataCollection) packetData; + } else { + saveUpdateCollection = new PacketDataCollection(); + CurrentUpdatePacket.SetSendingPacketData(ServerPacketId.SaveUpdate, saveUpdateCollection); + } + + saveUpdateCollection.DataInstances.Add(new SaveUpdate { + SaveDataIndex = index, + Value = value + }); + } + } } diff --git a/HKMP/Networking/Packet/Data/SaveUpdate.cs b/HKMP/Networking/Packet/Data/SaveUpdate.cs new file mode 100644 index 0000000..b0b8054 --- /dev/null +++ b/HKMP/Networking/Packet/Data/SaveUpdate.cs @@ -0,0 +1,44 @@ +namespace Hkmp.Networking.Packet.Data; + +/// +/// Packet data for when values in the save update. +/// +internal class SaveUpdate : IPacketData { + /// + public bool IsReliable => true; + + /// + public bool DropReliableDataIfNewerExists => false; + + /// + /// The index of the save data entry that got updated. + /// + public ushort SaveDataIndex { get; set; } + + /// + /// The encoded value of the save data in a byte array. + /// + public byte[] Value { get; set; } + + /// + public void WriteData(IPacket packet) { + packet.Write(SaveDataIndex); + + var length = (byte) System.Math.Min(Value.Length, byte.MaxValue); + packet.Write(length); + for (var i = 0; i < length; i++) { + packet.Write(Value[i]); + } + } + + /// + public void ReadData(IPacket packet) { + SaveDataIndex = packet.ReadUShort(); + + var length = packet.ReadByte(); + Value = new byte[length]; + for (var i = 0; i < length; i++) { + Value[i] = packet.ReadByte(); + } + } +} diff --git a/HKMP/Networking/Packet/PacketId.cs b/HKMP/Networking/Packet/PacketId.cs index a6568ac..d89e21d 100644 --- a/HKMP/Networking/Packet/PacketId.cs +++ b/HKMP/Networking/Packet/PacketId.cs @@ -12,92 +12,97 @@ internal enum ClientPacketId { /// /// A response to the HelloServer after a succeeding login. /// - HelloClient, + HelloClient = 1, /// /// Indicating that a client has connected. /// - PlayerConnect, + PlayerConnect = 2, /// /// Indicating that a client is disconnecting. /// - PlayerDisconnect, + PlayerDisconnect = 3, /// /// Indicating the client is (forcefully) disconnected from the server. /// - ServerClientDisconnect, + ServerClientDisconnect = 4, /// /// Notify that a player has entered the current scene. /// - PlayerEnterScene, + PlayerEnterScene = 5, /// /// Notify that a player is already in the scene we just entered. /// - PlayerAlreadyInScene, + PlayerAlreadyInScene = 6, /// /// Notify that a player has left the current scene. /// - PlayerLeaveScene, + PlayerLeaveScene = 7, /// /// Update of realtime player values. /// - PlayerUpdate, + PlayerUpdate = 8, /// /// Update of player map position. /// - PlayerMapUpdate, + PlayerMapUpdate = 9, /// /// Notify that an entity has spawned. /// - EntitySpawn, + EntitySpawn = 10, /// /// Update of realtime entity values. /// - EntityUpdate, + EntityUpdate = 11, /// /// Update of realtime reliable entity values. /// - ReliableEntityUpdate, + ReliableEntityUpdate = 12, /// /// Notify that the player becomes scene host of their current scene. /// - SceneHostTransfer, + SceneHostTransfer = 13, /// /// Notify that a player has died. /// - PlayerDeath, + PlayerDeath = 14, /// /// Notify that a player has changed teams. /// - PlayerTeamUpdate, + PlayerTeamUpdate = 15, /// /// Notify that a player has changed skins. /// - PlayerSkinUpdate, + PlayerSkinUpdate = 16, /// /// Notify that the gameplay settings have updated. /// - ServerSettingsUpdated, + ServerSettingsUpdated = 17, /// /// Player sent chat message. /// - ChatMessage = 18 + ChatMessage = 18, + + /// + /// Value in the save file has updated. + /// + SaveUpdate = 19, } /// @@ -112,65 +117,70 @@ public enum ServerPacketId { /// /// Initial hello, sent when login succeeds. /// - HelloServer, + HelloServer = 1, /// /// Indicating that a client is disconnecting. /// - PlayerDisconnect, + PlayerDisconnect = 2, /// /// Update of realtime player values. /// - PlayerUpdate, + PlayerUpdate = 3, /// /// Update of player map position. /// - PlayerMapUpdate, + PlayerMapUpdate = 4, /// /// Notify that an entity has spawned. /// - EntitySpawn, + EntitySpawn = 5, /// /// Update of realtime entity values. /// - EntityUpdate, + EntityUpdate = 6, /// /// Update of realtime reliable entity values. /// - ReliableEntityUpdate, + ReliableEntityUpdate = 7, /// /// Notify that the player has entered a new scene. /// - PlayerEnterScene, + PlayerEnterScene = 8, /// /// Notify that the player has left their current scene. /// - PlayerLeaveScene, + PlayerLeaveScene = 9, /// /// Notify that a player has died. /// - PlayerDeath, + PlayerDeath = 10, /// /// Notify that a player has changed teams. /// - PlayerTeamUpdate, + PlayerTeamUpdate = 11, /// /// Notify that a player has changed skins. /// - PlayerSkinUpdate, + PlayerSkinUpdate = 12, /// /// Player sent chat message. /// - ChatMessage = 13 + ChatMessage = 13, + + /// + /// Value in the save file has updated. + /// + SaveUpdate = 14, } diff --git a/HKMP/Networking/Packet/UpdatePacket.cs b/HKMP/Networking/Packet/UpdatePacket.cs index ce4776b..7263775 100644 --- a/HKMP/Networking/Packet/UpdatePacket.cs +++ b/HKMP/Networking/Packet/UpdatePacket.cs @@ -879,6 +879,8 @@ protected override IPacketData InstantiatePacketDataFromId(ServerPacketId packet return new ServerPlayerSkinUpdate(); case ServerPacketId.ChatMessage: return new ChatMessage(); + case ServerPacketId.SaveUpdate: + return new PacketDataCollection(); default: return new EmptyData(); } @@ -936,6 +938,8 @@ protected override IPacketData InstantiatePacketDataFromId(ClientPacketId packet return new ServerSettingsUpdate(); case ClientPacketId.ChatMessage: return new PacketDataCollection(); + case ClientPacketId.SaveUpdate: + return new PacketDataCollection(); default: return new EmptyData(); } diff --git a/HKMP/Networking/Server/ServerUpdateManager.cs b/HKMP/Networking/Server/ServerUpdateManager.cs index dece373..f3cddd3 100644 --- a/HKMP/Networking/Server/ServerUpdateManager.cs +++ b/HKMP/Networking/Server/ServerUpdateManager.cs @@ -552,4 +552,27 @@ public void AddChatMessage(string message) { }); } } + + /// + /// Set save update data. + /// + /// The index of the save data entry. + /// The array of bytes that represents the changed value. + public void SetSaveUpdate(ushort index, byte[] value) { + lock (Lock) { + PacketDataCollection saveUpdateCollection; + + if (CurrentUpdatePacket.TryGetSendingPacketData(ClientPacketId.SaveUpdate, out var packetData)) { + saveUpdateCollection = (PacketDataCollection) packetData; + } else { + saveUpdateCollection = new PacketDataCollection(); + CurrentUpdatePacket.SetSendingPacketData(ClientPacketId.SaveUpdate, saveUpdateCollection); + } + + saveUpdateCollection.DataInstances.Add(new SaveUpdate { + SaveDataIndex = index, + Value = value + }); + } + } } diff --git a/HKMP/Resource/save-data.json b/HKMP/Resource/save-data.json new file mode 100644 index 0000000..93269f9 --- /dev/null +++ b/HKMP/Resource/save-data.json @@ -0,0 +1,1276 @@ +{ + "heartPieces": false, + "heartPieceMax": false, + "geo": false, + "vesselFragments": false, + "vesselFragmentMax": false, + "dreamgateMapPos": false, + "geoPool": false, + "hasSpell": false, + "fireballLevel": false, + "quakeLevel": false, + "screamLevel": false, + "hasNailArt": false, + "hasCyclone": false, + "hasDashSlash": false, + "hasUpwardSlash": false, + "hasAllNailArts": false, + "hasDreamNail": false, + "hasDreamGate": false, + "dreamNailUpgraded": false, + "dreamOrbs": false, + "dreamOrbsSpent": false, + "dreamGateScene": false, + "dreamGateX": false, + "dreamGateY": false, + "hasDash": false, + "hasWalljump": false, + "hasSuperDash": false, + "hasShadowDash": false, + "hasAcidArmour": false, + "hasDoubleJump": false, + "hasLantern": false, + "hasTramPass": false, + "hasQuill": false, + "hasCityKey": false, + "hasSlykey": false, + "gaveSlykey": false, + "hasWhiteKey": false, + "usedWhiteKey": false, + "hasMenderKey": true, + "hasWaterwaysKey": false, + "hasSpaKey": false, + "hasLoveKey": false, + "hasKingsBrand": false, + "hasXunFlower": false, + "ghostCoins": false, + "ore": false, + "foundGhostCoin": false, + "trinket1": false, + "foundTrinket1": false, + "trinket2": false, + "foundTrinket2": false, + "trinket3": false, + "foundTrinket3": false, + "trinket4": false, + "foundTrinket4": false, + "noTrinket1": false, + "noTrinket2": false, + "noTrinket3": false, + "noTrinket4": false, + "soldTrinket1": false, + "soldTrinket2": false, + "soldTrinket3": false, + "soldTrinket4": false, + "simpleKeys": false, + "rancidEggs": false, + "notchShroomOgres": false, + "notchFogCanyon": false, + "gotLurkerKey": false, + "guardiansDefeated": false, + "lurienDefeated": true, + "hegemolDefeated": true, + "monomonDefeated": true, + "maskBrokenLurien": true, + "maskBrokenHegemol": true, + "maskBrokenMonomon": true, + "maskToBreak": false, + "elderbug": false, + "metElderbug": false, + "elderbugReintro": false, + "elderbugHistory": false, + "elderbugHistory1": false, + "elderbugHistory2": false, + "elderbugHistory3": false, + "elderbugSpeechSly": false, + "elderbugSpeechStation": false, + "elderbugSpeechEggTemple": false, + "elderbugSpeechMapShop": false, + "elderbugSpeechBretta": false, + "elderbugSpeechJiji": false, + "elderbugSpeechMinesLift": false, + "elderbugSpeechKingsPass": false, + "elderbugSpeechInfectedCrossroads": false, + "elderbugSpeechFinalBossDoor": false, + "elderbugRequestedFlower": false, + "elderbugGaveFlower": false, + "elderbugFirstCall": false, + "metQuirrel": true, + "quirrelEggTemple": true, + "quirrelSlugShrine": true, + "quirrelRuins": true, + "quirrelMines": true, + "quirrelLeftStation": true, + "quirrelLeftEggTemple": true, + "quirrelCityEncountered": true, + "quirrelCityLeft": true, + "quirrelMinesEncountered": true, + "quirrelMinesLeft": true, + "quirrelMantisEncountered": true, + "enteredMantisLordArea": true, + "visitedDeepnestSpa": true, + "quirrelSpaReady": true, + "quirrelSpaEncountered": true, + "quirrelArchiveEncountered": true, + "quirrelEpilogueCompleted": true, + "metRelicDealer": false, + "metRelicDealerShop": false, + "marmOutside": true, + "marmOutsideConvo": false, + "marmConvo1": false, + "marmConvo2": false, + "marmConvo3": false, + "marmConvoNailsmith": false, + "cornifer": true, + "metCornifer": false, + "corniferIntroduced": false, + "corniferAtHome": true, + "corn_crossroadsEncountered": true, + "corn_crossroadsLeft": true, + "corn_greenpathEncountered": true, + "corn_greenpathLeft": true, + "corn_fogCanyonEncountered": true, + "corn_fogCanyonLeft": true, + "corn_fungalWastesEncountered": true, + "corn_fungalWastesLeft": true, + "corn_cityEncountered": true, + "corn_cityLeft": true, + "corn_waterwaysEncountered": true, + "corn_waterwaysLeft": true, + "corn_minesEncountered": true, + "corn_minesLeft": true, + "corn_cliffsEncountered": true, + "corn_cliffsLeft": true, + "corn_deepnestEncountered": true, + "corn_deepnestLeft": true, + "corn_deepnestMet1": true, + "corn_deepnestMet2": true, + "corn_outskirtsEncountered": true, + "corn_outskirtsLeft": true, + "corn_royalGardensEncountered": true, + "corn_royalGardensLeft": true, + "corn_abyssEncountered": true, + "corn_abyssLeft": true, + "metIselda": false, + "iseldaCorniferHomeConvo": false, + "iseldaConvo1": false, + "brettaRescued": true, + "brettaPosition": true, + "brettaState": true, + "brettaSeenBench": true, + "brettaSeenBed": true, + "brettaSeenBenchDiary": true, + "brettaSeenBedDiary": true, + "brettaLeftTown": true, + "slyRescued": true, + "slyBeta": true, + "metSlyShop": false, + "gotSlyCharm": false, + "slyShellFrag1": false, + "slyShellFrag2": false, + "slyShellFrag3": false, + "slyShellFrag4": false, + "slyVesselFrag1": false, + "slyVesselFrag2": false, + "slyVesselFrag3": false, + "slyVesselFrag4": false, + "slyNotch1": false, + "slyNotch2": false, + "slySimpleKey": false, + "slyRancidEgg": false, + "slyConvoNailArt": false, + "slyConvoMapper": false, + "slyConvoNailHoned": false, + "jijiDoorUnlocked": true, + "jijiMet": false, + "jijiShadeOffered": false, + "jijiShadeCharmConvo": false, + "metJinn": false, + "jinnConvo1": false, + "jinnConvo2": false, + "jinnConvo3": false, + "jinnConvoKingBrand": false, + "jinnConvoShadeCharm": false, + "jinnEggsSold": false, + "zote": true, + "zoteRescuedBuzzer": true, + "zoteDead": true, + "zoteDeathPos": true, + "zoteSpokenCity": true, + "zoteLeftCity": true, + "zoteTrappedDeepnest": true, + "zoteRescuedDeepnest": true, + "zoteDefeated": true, + "zoteSpokenColosseum": true, + "zotePrecept": false, + "zoteTownConvo": false, + "shaman": true, + "shamanScreamConvo": false, + "shamanQuakeConvo": false, + "shamanFireball2Convo": false, + "shamanScream2Convo": false, + "shamanQuake2Convo": false, + "metMiner": false, + "miner": true, + "minerEarly": false, + "hornetGreenpath": true, + "hornetFung": true, + "hornet_f19": true, + "hornetFountainEncounter": false, + "hornetCityBridge_ready": true, + "hornetCityBridge_completed": true, + "hornetAbyssEncounter": true, + "hornetDenEncounter": true, + "metMoth": false, + "ignoredMoth": false, + "gladeDoorOpened": true, + "mothDeparted": false, + "completedRGDreamPlant": false, + "dreamReward1": false, + "dreamReward2": false, + "dreamReward3": false, + "dreamReward4": false, + "dreamReward5": false, + "dreamReward5b": false, + "dreamReward6": false, + "dreamReward7": false, + "dreamReward8": false, + "dreamReward9": false, + "dreamMothConvo1": false, + "bankerAccountPurchased": false, + "metBanker": false, + "bankerBalance": false, + "bankerDeclined": false, + "bankerTheftCheck": true, + "bankerTheft": true, + "bankerSpaMet": false, + "metGiraffe": false, + "metCharmSlug": false, + "salubraNotch1": false, + "salubraNotch2": false, + "salubraNotch3": false, + "salubraNotch4": false, + "salubraBlessing": false, + "salubraConvoCombo": false, + "salubraConvoOvercharm": false, + "salubraConvoTruth": false, + "cultistTransformed": true, + "metNailsmith": false, + "nailSmithUpgrades": false, + "honedNail": false, + "nailsmithCliff": false, + "nailsmithKilled": false, + "nailsmithSpared": false, + "nailsmithKillSpeech": false, + "nailsmithSheo": true, + "nailsmithConvoArt": true, + "metNailmasterMato": false, + "metNailmasterSheo": false, + "metNailmasterOro": false, + "matoConvoSheo": false, + "matoConvoOro": false, + "matoConvoSly": false, + "sheoConvoMato": false, + "sheoConvoOro": false, + "sheoConvoSly": false, + "sheoConvoNailsmith": false, + "oroConvoSheo": false, + "oroConvoMato": false, + "oroConvoSly": false, + "hunterRoared": true, + "metHunter": false, + "hunterRewardOffered": false, + "huntersMarkOffered": false, + "hasHuntersMark": false, + "metLegEater": false, + "paidLegEater": false, + "refusedLegEater": false, + "legEaterConvo1": false, + "legEaterConvo2": false, + "legEaterConvo3": false, + "legEaterBrokenConvo": false, + "legEaterDungConvo": false, + "legEaterInfectedCrossroadConvo": false, + "legEaterBoughtConvo": false, + "legEaterGoldConvo": false, + "legEaterLeft": true, + "tukMet": false, + "tukEggPrice": false, + "tukDungEgg": false, + "metEmilitia": false, + "emilitiaKingsBrandConvo": false, + "metCloth": false, + "clothEnteredTramRoom": true, + "savedCloth": true, + "clothEncounteredQueensGarden": true, + "clothKilled": true, + "clothInTown": true, + "clothLeftTown": true, + "clothGhostSpoken": true, + "bigCatHitTail": false, + "bigCatHitTailConvo": false, + "bigCatMeet": false, + "bigCatTalk1": false, + "bigCatTalk2": false, + "bigCatTalk3": false, + "bigCatKingsBrandConvo": false, + "bigCatShadeConvo": false, + "tisoEncounteredTown": true, + "tisoEncounteredBench": true, + "tisoEncounteredLake": true, + "tisoEncounteredColosseum": true, + "tisoDead": true, + "tisoShieldConvo": true, + "mossCultist": true, + "maskmakerMet": false, + "maskmakerConvo1": false, + "maskmakerConvo2": false, + "maskmakerUnmasked1": false, + "maskmakerUnmasked2": false, + "maskmakerShadowDash": false, + "maskmakerKingsBrand": false, + "dungDefenderConvo1": false, + "dungDefenderConvo2": false, + "dungDefenderConvo3": false, + "dungDefenderCharmConvo": false, + "dungDefenderIsmaConvo": false, + "dungDefenderAwoken": true, + "dungDefenderLeft": true, + "dungDefenderAwakeConvo": false, + "midwifeMet": false, + "midwifeConvo1": false, + "midwifeConvo2": false, + "metQueen": false, + "queenTalk1": false, + "queenTalk2": false, + "queenDung1": false, + "queenDung2": false, + "queenHornet": false, + "queenTalkExtra": false, + "gotQueenFragment": false, + "queenConvo_grimm1": false, + "queenConvo_grimm2": false, + "gotKingFragment": false, + "metXun": false, + "xunFailedConvo1": false, + "xunFailedConvo2": false, + "xunFlowerBroken": false, + "xunFlowerBrokeTimes": false, + "xunFlowerGiven": false, + "xunRewardGiven": false, + "menderState": true, + "menderSignBroken": true, + "allBelieverTabletsDestroyed": true, + "mrMushroomState": true, + "openedMapperShop": true, + "openedSlyShop": true, + "metStag": false, + "stagPosition": true, + "stationsOpened": true, + "stagConvoTram": false, + "stagConvoTiso": false, + "stagRemember1": false, + "stagRemember2": false, + "stagRemember3": false, + "stagEggInspected": false, + "stagHopeConvo": false, + "littleFoolMet": false, + "ranAway": false, + "seenColosseumTitle": false, + "colosseumBronzeOpened": false, + "colosseumBronzeCompleted": false, + "colosseumSilverOpened": false, + "colosseumSilverCompleted": false, + "colosseumGoldOpened": false, + "colosseumGoldCompleted": false, + "openedTown": true, + "openedTownBuilding": true, + "openedCrossroads": true, + "openedGreenpath": true, + "openedRuins1": true, + "openedRuins2": true, + "openedFungalWastes": true, + "openedRoyalGardens": true, + "openedRestingGrounds": true, + "openedDeepnest": true, + "openedStagNest": true, + "openedHiddenStation": true, + "charmSlots": false, + "charmsOwned": false, + "gotCharm_1": false, + "gotCharm_2": false, + "gotCharm_3": false, + "gotCharm_4": false, + "gotCharm_5": false, + "gotCharm_6": false, + "gotCharm_7": false, + "gotCharm_8": false, + "gotCharm_9": false, + "gotCharm_10": false, + "gotCharm_11": false, + "gotCharm_12": false, + "gotCharm_13": false, + "gotCharm_14": false, + "gotCharm_15": false, + "gotCharm_16": false, + "gotCharm_17": false, + "gotCharm_18": false, + "gotCharm_19": false, + "gotCharm_20": false, + "gotCharm_21": false, + "gotCharm_22": false, + "gotCharm_23": false, + "gotCharm_24": false, + "gotCharm_25": false, + "gotCharm_26": false, + "gotCharm_27": false, + "gotCharm_28": false, + "gotCharm_29": false, + "gotCharm_30": false, + "gotCharm_31": false, + "gotCharm_32": false, + "gotCharm_33": false, + "gotCharm_34": false, + "gotCharm_35": false, + "gotCharm_36": false, + "gotCharm_37": false, + "gotCharm_38": false, + "gotCharm_39": false, + "gotCharm_40": false, + "fragileHealth_unbreakable": false, + "fragileGreed_unbreakable": false, + "fragileStrength_unbreakable": false, + "royalCharmState": false, + "hasJournal": false, + "seenJournalMsg": false, + "seenHunterMsg": false, + "fillJournal": false, + "journalEntriesCompleted": true, + "journalNotesCompleted": true, + "journalEntriesTotal": true, + "killedCrawler": true, + "killsCrawler": true, + "newDataCrawler": true, + "killedBuzzer": true, + "killsBuzzer": true, + "newDataBuzzer": true, + "killedBouncer": true, + "killsBouncer": true, + "newDataBouncer": true, + "killedClimber": true, + "killsClimber": true, + "newDataClimber": true, + "killedHopper": true, + "killsHopper": true, + "newDataHopper": true, + "killedWorm": true, + "killsWorm": true, + "newDataWorm": true, + "killedSpitter": true, + "killsSpitter": true, + "newDataSpitter": true, + "killedHatcher": true, + "killsHatcher": true, + "newDataHatcher": true, + "killedHatchling": true, + "killsHatchling": true, + "newDataHatchling": true, + "killedZombieRunner": true, + "killsZombieRunner": true, + "newDataZombieRunner": true, + "killedZombieHornhead": true, + "killsZombieHornhead": true, + "newDataZombieHornhead": true, + "killedZombieLeaper": true, + "killsZombieLeaper": true, + "newDataZombieLeaper": true, + "killedZombieBarger": true, + "killsZombieBarger": true, + "newDataZombieBarger": true, + "killedZombieShield": true, + "killsZombieShield": true, + "newDataZombieShield": true, + "killedZombieGuard": true, + "killsZombieGuard": true, + "newDataZombieGuard": true, + "killedBigBuzzer": true, + "killsBigBuzzer": true, + "newDataBigBuzzer": true, + "killedBigFly": true, + "killsBigFly": true, + "newDataBigFly": true, + "killedMawlek": true, + "killsMawlek": true, + "newDataMawlek": true, + "killedFalseKnight": true, + "killsFalseKnight": true, + "newDataFalseKnight": true, + "killedRoller": true, + "killsRoller": true, + "newDataRoller": true, + "killedBlocker": true, + "killsBlocker": true, + "newDataBlocker": true, + "killedPrayerSlug": true, + "killsPrayerSlug": true, + "newDataPrayerSlug": true, + "killedMenderBug": true, + "killsMenderBug": true, + "newDataMenderBug": true, + "killedMossmanRunner": true, + "killsMossmanRunner": true, + "newDataMossmanRunner": true, + "killedMossmanShaker": true, + "killsMossmanShaker": true, + "newDataMossmanShaker": true, + "killedMosquito": true, + "killsMosquito": true, + "newDataMosquito": true, + "killedBlobFlyer": true, + "killsBlobFlyer": true, + "newDataBlobFlyer": true, + "killedFungifiedZombie": true, + "killsFungifiedZombie": true, + "newDataFungifiedZombie": true, + "killedPlantShooter": true, + "killsPlantShooter": true, + "newDataPlantShooter": true, + "killedMossCharger": true, + "killsMossCharger": true, + "newDataMossCharger": true, + "killedMegaMossCharger": true, + "killsMegaMossCharger": true, + "newDataMegaMossCharger": true, + "killedSnapperTrap": true, + "killsSnapperTrap": true, + "newDataSnapperTrap": true, + "killedMossKnight": true, + "killsMossKnight": true, + "newDataMossKnight": true, + "killedGrassHopper": true, + "killsGrassHopper": true, + "newDataGrassHopper": true, + "killedAcidFlyer": true, + "killsAcidFlyer": true, + "newDataAcidFlyer": true, + "killedAcidWalker": true, + "killsAcidWalker": true, + "newDataAcidWalker": true, + "killedMossFlyer": true, + "killsMossFlyer": true, + "newDataMossFlyer": true, + "killedMossKnightFat": true, + "killsMossKnightFat": true, + "newDataMossKnightFat": true, + "killedMossWalker": true, + "killsMossWalker": true, + "newDataMossWalker": true, + "killedInfectedKnight": true, + "killsInfectedKnight": true, + "newDataInfectedKnight": true, + "killedLazyFlyer": true, + "killsLazyFlyer": true, + "newDataLazyFlyer": true, + "killedZapBug": true, + "killsZapBug": true, + "newDataZapBug": true, + "killedJellyfish": true, + "killsJellyfish": true, + "newDataJellyfish": true, + "killedJellyCrawler": true, + "killsJellyCrawler": true, + "newDataJellyCrawler": true, + "killedMegaJellyfish": true, + "killsMegaJellyfish": true, + "newDataMegaJellyfish": true, + "killedFungoonBaby": true, + "killsFungoonBaby": true, + "newDataFungoonBaby": true, + "killedMushroomTurret": true, + "killsMushroomTurret": true, + "newDataMushroomTurret": true, + "killedMantis": true, + "killsMantis": true, + "newDataMantis": true, + "killedMushroomRoller": true, + "killsMushroomRoller": true, + "newDataMushroomRoller": true, + "killedMushroomBrawler": true, + "killsMushroomBrawler": true, + "newDataMushroomBrawler": true, + "killedMushroomBaby": true, + "killsMushroomBaby": true, + "newDataMushroomBaby": true, + "killedMantisFlyerChild": true, + "killsMantisFlyerChild": true, + "newDataMantisFlyerChild": true, + "killedFungusFlyer": true, + "killsFungusFlyer": true, + "newDataFungusFlyer": true, + "killedFungCrawler": true, + "killsFungCrawler": true, + "newDataFungCrawler": true, + "killedMantisLord": true, + "killsMantisLord": true, + "newDataMantisLord": true, + "killedBlackKnight": true, + "killsBlackKnight": true, + "newDataBlackKnight": true, + "killedElectricMage": true, + "killsElectricMage": true, + "newDataElectricMage": true, + "killedMage": true, + "killsMage": true, + "newDataMage": true, + "killedMageKnight": true, + "killsMageKnight": true, + "newDataMageKnight": true, + "killedRoyalDandy": true, + "killsRoyalDandy": true, + "newDataRoyalDandy": true, + "killedRoyalCoward": true, + "killsRoyalCoward": true, + "newDataRoyalCoward": true, + "killedRoyalPlumper": true, + "killsRoyalPlumper": true, + "newDataRoyalPlumper": true, + "killedFlyingSentrySword": true, + "killsFlyingSentrySword": true, + "newDataFlyingSentrySword": true, + "killedFlyingSentryJavelin": true, + "killsFlyingSentryJavelin": true, + "newDataFlyingSentryJavelin": true, + "killedSentry": true, + "killsSentry": true, + "newDataSentry": true, + "killedSentryFat": true, + "killsSentryFat": true, + "newDataSentryFat": true, + "killedMageBlob": true, + "killsMageBlob": true, + "newDataMageBlob": true, + "killedGreatShieldZombie": true, + "killsGreatShieldZombie": true, + "newDataGreatShieldZombie": true, + "killedJarCollector": true, + "killsJarCollector": true, + "newDataJarCollector": true, + "killedMageBalloon": true, + "killsMageBalloon": true, + "newDataMageBalloon": true, + "killedMageLord": true, + "killsMageLord": true, + "newDataMageLord": true, + "killedGorgeousHusk": true, + "killsGorgeousHusk": true, + "newDataGorgeousHusk": true, + "killedFlipHopper": true, + "killsFlipHopper": true, + "newDataFlipHopper": true, + "killedFlukeman": true, + "killsFlukeman": true, + "newDataFlukeman": true, + "killedInflater": true, + "killsInflater": true, + "newDataInflater": true, + "killedFlukefly": true, + "killsFlukefly": true, + "newDataFlukefly": true, + "killedFlukeMother": true, + "killsFlukeMother": true, + "newDataFlukeMother": true, + "killedDungDefender": true, + "killsDungDefender": true, + "newDataDungDefender": true, + "killedCrystalCrawler": true, + "killsCrystalCrawler": true, + "newDataCrystalCrawler": true, + "killedCrystalFlyer": true, + "killsCrystalFlyer": true, + "newDataCrystalFlyer": true, + "killedLaserBug": true, + "killsLaserBug": true, + "newDataLaserBug": true, + "killedBeamMiner": true, + "killsBeamMiner": true, + "newDataBeamMiner": true, + "killedZombieMiner": true, + "killsZombieMiner": true, + "newDataZombieMiner": true, + "killedMegaBeamMiner": true, + "killsMegaBeamMiner": true, + "newDataMegaBeamMiner": true, + "killedMinesCrawler": true, + "killsMinesCrawler": true, + "newDataMinesCrawler": true, + "killedAngryBuzzer": true, + "killsAngryBuzzer": true, + "newDataAngryBuzzer": true, + "killedBurstingBouncer": true, + "killsBurstingBouncer": true, + "newDataBurstingBouncer": true, + "killedBurstingZombie": true, + "killsBurstingZombie": true, + "newDataBurstingZombie": true, + "killedSpittingZombie": true, + "killsSpittingZombie": true, + "newDataSpittingZombie": true, + "killedBabyCentipede": true, + "killsBabyCentipede": true, + "newDataBabyCentipede": true, + "killedBigCentipede": true, + "killsBigCentipede": true, + "newDataBigCentipede": true, + "killedCentipedeHatcher": true, + "killsCentipedeHatcher": true, + "newDataCentipedeHatcher": true, + "killedLesserMawlek": true, + "killsLesserMawlek": true, + "newDataLesserMawlek": true, + "killedSlashSpider": true, + "killsSlashSpider": true, + "newDataSlashSpider": true, + "killedSpiderCorpse": true, + "killsSpiderCorpse": true, + "newDataSpiderCorpse": true, + "killedShootSpider": true, + "killsShootSpider": true, + "newDataShootSpider": true, + "killedMiniSpider": true, + "killsMiniSpider": true, + "newDataMiniSpider": true, + "killedSpiderFlyer": true, + "killsSpiderFlyer": true, + "newDataSpiderFlyer": true, + "killedMimicSpider": true, + "killsMimicSpider": true, + "newDataMimicSpider": true, + "killedBeeHatchling": true, + "killsBeeHatchling": true, + "newDataBeeHatchling": true, + "killedBeeStinger": true, + "killsBeeStinger": true, + "newDataBeeStinger": true, + "killedBigBee": true, + "killsBigBee": true, + "newDataBigBee": true, + "killedHiveKnight": true, + "killsHiveKnight": true, + "newDataHiveKnight": true, + "killedBlowFly": true, + "killsBlowFly": true, + "newDataBlowFly": true, + "killedCeilingDropper": true, + "killsCeilingDropper": true, + "newDataCeilingDropper": true, + "killedGiantHopper": true, + "killsGiantHopper": true, + "newDataGiantHopper": true, + "killedGrubMimic": true, + "killsGrubMimic": true, + "newDataGrubMimic": true, + "killedMawlekTurret": true, + "killsMawlekTurret": true, + "newDataMawlekTurret": true, + "killedOrangeScuttler": true, + "killsOrangeScuttler": true, + "newDataOrangeScuttler": true, + "killedHealthScuttler": true, + "killsHealthScuttler": true, + "newDataHealthScuttler": true, + "killedPigeon": true, + "killsPigeon": true, + "newDataPigeon": true, + "killedZombieHive": true, + "killsZombieHive": true, + "newDataZombieHive": true, + "killedDreamGuard": true, + "killsDreamGuard": true, + "newDataDreamGuard": true, + "killedHornet": true, + "killsHornet": true, + "newDataHornet": true, + "killedAbyssCrawler": true, + "killsAbyssCrawler": true, + "newDataAbyssCrawler": true, + "killedSuperSpitter": true, + "killsSuperSpitter": true, + "newDataSuperSpitter": true, + "killedSibling": true, + "killsSibling": true, + "newDataSibling": true, + "killedPalaceFly": true, + "killsPalaceFly": true, + "newDataPalaceFly": true, + "killedEggSac": true, + "killsEggSac": true, + "newDataEggSac": true, + "killedMummy": true, + "killsMummy": true, + "newDataMummy": true, + "killedOrangeBalloon": true, + "killsOrangeBalloon": true, + "newDataOrangeBalloon": true, + "killedAbyssTendril": true, + "killsAbyssTendril": true, + "newDataAbyssTendril": true, + "killedHeavyMantis": true, + "killsHeavyMantis": true, + "newDataHeavyMantis": true, + "killedTraitorLord": true, + "killsTraitorLord": true, + "newDataTraitorLord": true, + "killedMantisHeavyFlyer": true, + "killsMantisHeavyFlyer": true, + "newDataMantisHeavyFlyer": true, + "killedGardenZombie": true, + "killsGardenZombie": true, + "newDataGardenZombie": true, + "killedRoyalGuard": true, + "killsRoyalGuard": true, + "newDataRoyalGuard": true, + "killedWhiteRoyal": true, + "killsWhiteRoyal": true, + "newDataWhiteRoyal": true, + "openedPalaceGrounds": true, + "killedOblobble": true, + "killsOblobble": true, + "newDataOblobble": true, + "killedZote": true, + "killsZote": true, + "newDataZote": true, + "killedBlobble": true, + "killsBlobble": true, + "newDataBlobble": true, + "killedColMosquito": true, + "killsColMosquito": true, + "newDataColMosquito": true, + "killedColRoller": true, + "killsColRoller": true, + "newDataColRoller": true, + "killedColFlyingSentry": true, + "killsColFlyingSentry": true, + "newDataColFlyingSentry": true, + "killedColMiner": true, + "killsColMiner": true, + "newDataColMiner": true, + "killedColShield": true, + "killsColShield": true, + "newDataColShield": true, + "killedColWorm": true, + "killsColWorm": true, + "newDataColWorm": true, + "killedColHopper": true, + "killsColHopper": true, + "newDataColHopper": true, + "killedLobsterLancer": true, + "killsLobsterLancer": true, + "newDataLobsterLancer": true, + "killedGhostAladar": true, + "killsGhostAladar": true, + "newDataGhostAladar": true, + "killedGhostXero": true, + "killsGhostXero": true, + "newDataGhostXero": true, + "killedGhostHu": true, + "killsGhostHu": true, + "newDataGhostHu": true, + "killedGhostMarmu": true, + "killsGhostMarmu": true, + "newDataGhostMarmu": true, + "killedGhostNoEyes": true, + "killsGhostNoEyes": true, + "newDataGhostNoEyes": true, + "killedGhostMarkoth": true, + "killsGhostMarkoth": true, + "newDataGhostMarkoth": true, + "killedGhostGalien": true, + "killsGhostGalien": true, + "newDataGhostGalien": true, + "killedWhiteDefender": true, + "killsWhiteDefender": true, + "newDataWhiteDefender": true, + "killedGreyPrince": true, + "killsGreyPrince": true, + "newDataGreyPrince": true, + "killedZotelingBalloon": true, + "killsZotelingBalloon": true, + "newDataZotelingBalloon": true, + "killedZotelingHopper": true, + "killsZotelingHopper": true, + "newDataZotelingHopper": true, + "killedZotelingBuzzer": true, + "killsZotelingBuzzer": true, + "newDataZotelingBuzzer": true, + "killedHollowKnight": true, + "killsHollowKnight": true, + "newDataHollowKnight": true, + "killedFinalBoss": true, + "killsFinalBoss": true, + "newDataFinalBoss": true, + "killedHunterMark": true, + "killsHunterMark": true, + "newDataHunterMark": true, + "killedFlameBearerSmall": true, + "killsFlameBearerSmall": true, + "newDataFlameBearerSmall": true, + "killedFlameBearerMed": true, + "killsFlameBearerMed": true, + "newDataFlameBearerMed": true, + "killedFlameBearerLarge": true, + "killsFlameBearerLarge": true, + "newDataFlameBearerLarge": true, + "killedGrimm": true, + "killsGrimm": true, + "newDataGrimm": true, + "killedNightmareGrimm": true, + "killsNightmareGrimm": true, + "newDataNightmareGrimm": true, + "killedBindingSeal": true, + "killsBindingSeal": true, + "newDataBindingSeal": true, + "killedFatFluke": true, + "killsFatFluke": true, + "newDataFatFluke": true, + "killedPaleLurker": true, + "killsPaleLurker": true, + "newDataPaleLurker": true, + "killedNailBros": true, + "killsNailBros": true, + "newDataNailBros": true, + "killedPaintmaster": true, + "killsPaintmaster": true, + "newDataPaintmaster": true, + "killedNailsage": true, + "killsNailsage": true, + "newDataNailsage": true, + "killedHollowKnightPrime": true, + "killsHollowKnightPrime": true, + "newDataHollowKnightPrime": true, + "killedGodseekerMask": true, + "killsGodseekerMask": true, + "newDataGodseekerMask": true, + "killedVoidIdol_1": true, + "killsVoidIdol_1": true, + "newDataVoidIdol_1": true, + "killedVoidIdol_2": true, + "killsVoidIdol_2": true, + "newDataVoidIdol_2": true, + "killedVoidIdol_3": true, + "killsVoidIdol_3": true, + "newDataVoidIdol_3": true, + "grubsCollected": true, + "grubRewards": false, + "finalGrubRewardCollected": false, + "fatGrubKing": false, + "falseKnightDefeated": true, + "falseKnightDreamDefeated": true, + "falseKnightOrbsCollected": false, + "mawlekDefeated": true, + "giantBuzzerDefeated": true, + "giantFlyDefeated": true, + "blocker1Defeated": true, + "blocker2Defeated": true, + "hornet1Defeated": true, + "collectorDefeated": true, + "hornetOutskirtsDefeated": true, + "mageLordDreamDefeated": true, + "mageLordOrbsCollected": false, + "infectedKnightDreamDefeated": true, + "infectedKnightOrbsCollected": false, + "whiteDefenderDefeated": true, + "whiteDefenderOrbsCollected": false, + "whiteDefenderDefeats": true, + "greyPrinceDefeats": true, + "greyPrinceDefeated": true, + "greyPrinceOrbsCollected": false, + "aladarSlugDefeated": true, + "xeroDefeated": true, + "elderHuDefeated": true, + "mumCaterpillarDefeated": true, + "noEyesDefeated": true, + "markothDefeated": true, + "galienDefeated": true, + "XERO_encountered": false, + "ALADAR_encountered": false, + "HU_encountered": false, + "MUMCAT_encountered": false, + "NOEYES_encountered": false, + "MARKOTH_encountered": false, + "GALIEN_encountered": false, + "xeroPinned": true, + "aladarPinned": true, + "huPinned": true, + "mumCaterpillarPinned": true, + "noEyesPinned": true, + "markothPinned": true, + "galienPinned": true, + "scenesVisited": true, + "scenesMapped": true, + "scenesEncounteredBench": true, + "scenesGrubRescued": true, + "scenesFlameCollected": true, + "scenesEncounteredCocoon": true, + "scenesEncounteredDreamPlant": true, + "scenesEncounteredDreamPlantC": false, + "hasMap": false, + "mapDirtmouth": false, + "mapCrossroads": false, + "mapGreenpath": false, + "mapFogCanyon": false, + "mapRoyalGardens": false, + "mapFungalWastes": false, + "mapCity": false, + "mapWaterways": false, + "mapMines": false, + "mapDeepnest": false, + "mapCliffs": false, + "mapOutskirts": false, + "mapRestingGrounds": false, + "mapAbyss": false, + "mapZoneBools": true, + "hasPin": false, + "hasPinBench": false, + "hasPinCocoon": false, + "hasPinDreamPlant": false, + "hasPinGuardian": false, + "hasPinBlackEgg": false, + "hasPinShop": false, + "hasPinSpa": false, + "hasPinStag": false, + "hasPinTram": false, + "hasPinGhost": false, + "hasPinGrub": false, + "hasMarker": false, + "hasMarker_r": false, + "hasMarker_b": false, + "hasMarker_y": false, + "hasMarker_w": false, + "openedTramLower": false, + "openedTramRestingGrounds": false, + "tramLowerPosition": true, + "tramRestingGroundsPosition": true, + "mineLiftOpened": true, + "menderDoorOpened": true, + "vesselFragStagNest": false, + "shamanPillar": true, + "crossroadsMawlekWall": true, + "eggTempleVisited": false, + "crossroadsInfected": true, + "falseKnightFirstPlop": true, + "falseKnightWallRepaired": true, + "falseKnightWallBroken": true, + "falseKnightGhostDeparted": true, + "spaBugsEncountered": true, + "hornheadVinePlat": true, + "infectedKnightEncountered": true, + "megaMossChargerEncountered": true, + "megaMossChargerDefeated": true, + "dreamerScene1": true, + "slugEncounterComplete": true, + "defeatedDoubleBlockers": true, + "oneWayArchive": true, + "defeatedMegaJelly": true, + "summonedMonomon": true, + "sawWoundedQuirrel": true, + "encounteredMegaJelly": true, + "defeatedMantisLords": true, + "encounteredGatekeeper": true, + "deepnestWall": true, + "queensStationNonDisplay": true, + "cityBridge1": true, + "cityBridge2": true, + "cityLift1": true, + "cityLift1_isUp": true, + "liftArrival": true, + "openedMageDoor": true, + "openedMageDoor_v2": true, + "brokenMageWindow": true, + "brokenMageWindowGlass": true, + "mageLordEncountered": true, + "mageLordEncountered_2": true, + "mageLordDefeated": true, + "ruins1_5_tripleDoor": true, + "openedCityGate": true, + "cityGateClosed": true, + "bathHouseOpened": true, + "bathHouseWall": true, + "cityLift2": true, + "cityLift2_isUp": true, + "city2_sewerDoor": true, + "openedLoveDoor": true, + "watcherChandelier": true, + "completedQuakeArea": true, + "kingsStationNonDisplay": true, + "tollBenchCity": true, + "waterwaysGate": true, + "defeatedDungDefender": true, + "dungDefenderEncounterReady": true, + "flukeMotherEncountered": true, + "flukeMotherDefeated": true, + "openedWaterwaysManhole": true, + "waterwaysAcidDrained": true, + "dungDefenderWallBroken": true, + "dungDefenderSleeping": true, + "defeatedMegaBeamMiner": true, + "defeatedMegaBeamMiner2": true, + "brokeMinersWall": true, + "encounteredMimicSpider": true, + "steppedBeyondBridge": true, + "deepnestBridgeCollapsed": true, + "spiderCapture": false, + "deepnest26b_switch": true, + "openedRestingGrounds02": true, + "restingGroundsCryptWall": true, + "dreamNailConvo": false, + "gladeGhostsKilled": true, + "openedGardensStagStation": true, + "extendedGramophone": true, + "tollBenchQueensGardens": true, + "blizzardEnded": true, + "encounteredHornet": true, + "savedByHornet": true, + "outskirtsWall": true, + "abyssGateOpened": true, + "abyssLighthouse": true, + "blueVineDoor": true, + "gotShadeCharm": true, + "tollBenchAbyss": true, + "fountainGeo": false, + "fountainVesselSummoned": false, + "openedBlackEggPath": true, + "enteredDreamWorld": false, + "duskKnightDefeated": true, + "whitePalaceOrb_1": true, + "whitePalaceOrb_2": true, + "whitePalaceOrb_3": true, + "whitePalace05_lever": true, + "whitePalaceMidWarp": true, + "whitePalaceSecretRoomVisited": true, + "tramOpenedDeepnest": true, + "tramOpenedCrossroads": true, + "openedBlackEggDoor": true, + "unchainedHollowKnight": true, + "flamesCollected": true, + "flamesRequired": true, + "nightmareLanternAppeared": true, + "nightmareLanternLit": true, + "troupeInTown": true, + "divineInTown": true, + "grimmChildLevel": true, + "elderbugConvoGrimm": false, + "slyConvoGrimm": false, + "iseldaConvoGrimm": false, + "midwifeWeaverlingConvo": false, + "metGrimm": true, + "foughtGrimm": true, + "metBrum": false, + "defeatedNightmareGrimm": true, + "grimmchildAwoken": true, + "gotBrummsFlame": true, + "brummBrokeBrazier": true, + "destroyedNightmareLantern": true, + "gotGrimmNotch": false, + "nymmInTown": true, + "nymmSpoken": false, + "nymmCharmConvo": false, + "nymmFinalConvo": false, + "elderbugNymmConvo": false, + "slyNymmConvo": false, + "iseldaNymmConvo": false, + "nymmMissedEggOpen": false, + "elderbugTroupeLeftConvo": false, + "elderbugBrettaLeft": false, + "jijiGrimmConvo": false, + "metDivine": false, + "divineFinalConvo": false, + "gaveFragileHeart": false, + "gaveFragileGreed": false, + "gaveFragileStrength": false, + "divineEatenConvos": false, + "pooedFragileHeart": false, + "pooedFragileGreed": false, + "pooedFragileStrength": false, + "completionPercentage": false, + "unlockedCompletionRate": false, + "newDatTraitorLord": true, + "bossDoorStateTier1": true, + "bossDoorStateTier2": true, + "bossDoorStateTier3": true, + "bossDoorStateTier4": true, + "bossDoorStateTier5": true, + "bossStatueTargetLevel": false, + "statueStateGruzMother": true, + "statueStateVengefly": true, + "statueStateBroodingMawlek": true, + "statueStateFalseKnight": true, + "statueStateFailedChampion": true, + "statueStateHornet1": true, + "statueStateHornet2": true, + "statueStateMegaMossCharger": true, + "statueStateMantisLords": true, + "statueStateOblobbles": true, + "statueStateGreyPrince": true, + "statueStateBrokenVessel": true, + "statueStateLostKin": true, + "statueStateNosk": true, + "statueStateFlukemarm": true, + "statueStateCollector": true, + "statueStateWatcherKnights": true, + "statueStateSoulMaster": true, + "statueStateSoulTyrant": true, + "statueStateGodTamer": true, + "statueStateCrystalGuardian1": true, + "statueStateCrystalGuardian2": true, + "statueStateUumuu": true, + "statueStateDungDefender": true, + "statueStateWhiteDefender": true, + "statueStateHiveKnight": true, + "statueStateTraitorLord": true, + "statueStateGrimm": true, + "statueStateNightmareGrimm": true, + "statueStateHollowKnight": true, + "statueStateElderHu": true, + "statueStateGalien": true, + "statueStateMarkoth": true, + "statueStateMarmu": true, + "statueStateNoEyes": true, + "statueStateXero": true, + "statueStateGorb": true, + "statueStateRadiance": true, + "statueStateSly": true, + "statueStateNailmasters": true, + "statueStateMageKnight": true, + "statueStatePaintmaster": true, + "statueStateZote": true, + "statueStateNoskHornet": true, + "statueStateMantisLordsExtra": true, + "godseekerUnlocked": true, + "bossDoorCageUnlocked": true, + "blueRoomDoorUnlocked": true, + "blueRoomActivated": true, + "finalBossDoorUnlocked": true, + "hasGodfinder": false, + "unlockedNewBossStatue": true, + "scaredFlukeHermitEncountered": false, + "scaredFlukeHermitReturned": false, + "enteredGGAtrium": false, + "extraFlowerAppear": true, + "givenGodseekerFlower": true, + "givenOroFlower": true, + "givenWhiteLadyFlower": true, + "givenEmilitiaFlower": true, + "unlockedBossScenes": false, + "queuedGodfinderIcon": false, + "godseekerSpokenAwake": false, + "nailsmithCorpseAppeared": true, + "godseekerWaterwaysSeenState": true, + "godseekerWaterwaysSpoken1": false, + "godseekerWaterwaysSpoken2": false, + "godseekerWaterwaysSpoken3": false, + "bossDoorEntranceTextSeen": false, + "seenDoor4Finale": true, + "zoteStatueWallBroken": true, + "seenGGWastes": false, + "ordealAchieved": true +} \ No newline at end of file