void LoadJsonObj(Dictionary <string, object> jObject) { object jSaveVersion = null; if (!jObject.TryGetValue("inkSaveVersion", out jSaveVersion)) { throw new Exception("ink save format incorrect, can't load."); } else if ((int)jSaveVersion < kMinCompatibleLoadVersion) { throw new Exception("Ink save format isn't compatible with the current version (saw '" + jSaveVersion + "', but minimum is " + kMinCompatibleLoadVersion + "), so can't load."); } // Flows: Always exists in latest format (even if there's just one default) // but this dictionary doesn't exist in prev format object flowsObj = null; if (jObject.TryGetValue("flows", out flowsObj)) { var flowsObjDict = (Dictionary <string, object>)flowsObj; // Single default flow if (flowsObjDict.Count == 1) { _namedFlows = null; } // Multi-flow, need to create flows dict else if (_namedFlows == null) { _namedFlows = new Dictionary <string, Flow>(); } // Multi-flow, already have a flows dict else { _namedFlows.Clear(); } // Load up each flow (there may only be one) foreach (var namedFlowObj in flowsObjDict) { var name = namedFlowObj.Key; var flowObj = (Dictionary <string, object>)namedFlowObj.Value; // Load up this flow using JSON data var flow = new Flow(name, story, flowObj); if (flowsObjDict.Count == 1) { _currentFlow = new Flow(name, story, flowObj); } else { _namedFlows[name] = flow; } } if (_namedFlows != null && _namedFlows.Count > 1) { var currFlowName = (string)jObject["currentFlowName"]; _currentFlow = _namedFlows[currFlowName]; } } // Old format: individually load up callstack, output stream, choices in current/default flow else { _namedFlows = null; _currentFlow.name = kDefaultFlowName; _currentFlow.callStack.SetJsonToken((Dictionary <string, object>)jObject ["callstackThreads"], story); _currentFlow.outputStream = Json.JArrayToRuntimeObjList((List <object>)jObject ["outputStream"]); _currentFlow.currentChoices = Json.JArrayToRuntimeObjList <Choice>((List <object>)jObject ["currentChoices"]); object jChoiceThreadsObj = null; jObject.TryGetValue("choiceThreads", out jChoiceThreadsObj); _currentFlow.LoadFlowChoiceThreads((Dictionary <string, object>)jChoiceThreadsObj, story); } OutputStreamDirty(); _aliveFlowNamesDirty = true; variablesState.SetJsonToken((Dictionary <string, object>)jObject["variablesState"]); variablesState.callStack = _currentFlow.callStack; evaluationStack = Json.JArrayToRuntimeObjList((List <object>)jObject ["evalStack"]); object currentDivertTargetPath; if (jObject.TryGetValue("currentDivertTarget", out currentDivertTargetPath)) { var divertPath = new Path(currentDivertTargetPath.ToString()); divertedPointer = story.PointerAtPath(divertPath); } _visitCounts = Json.JObjectToIntDictionary((Dictionary <string, object>)jObject["visitCounts"]); _turnIndices = Json.JObjectToIntDictionary((Dictionary <string, object>)jObject["turnIndices"]); currentTurnIndex = (int)jObject ["turnIdx"]; storySeed = (int)jObject ["storySeed"]; // Not optional, but bug in inkjs means it's actually missing in inkjs saves object previousRandomObj = null; if (jObject.TryGetValue("previousRandom", out previousRandomObj)) { previousRandom = (int)previousRandomObj; } else { previousRandom = 0; } }