2023-10-12 01:09:31 +00:00
// Copyright (c) Pixel Crushers. All rights reserved.
using System.Collections ;
using System.Collections.Generic ;
using System.IO ;
using System.Text ;
using System.Text.RegularExpressions ;
using UnityEngine ;
namespace PixelCrushers.DialogueSystem
{
public delegate string GetCustomSaveDataDelegate ( ) ;
/// <summary>
/// A static class for saving and loading game data using the Dialogue System's
/// Lua environment. It allows you to save or load a game with a single line of code.
///
/// For more information, see @ref saveLoadSystem
/// </summary>
public static class PersistentDataManager
{
#region Variables
/// <summary>
/// Set <c>true</c> to include actor data in save data, <c>false</c> to exclude.
/// </summary>
public static bool includeActorData = true ;
/// <summary>
/// Set this <c>true</c> to include all item fields in saved-game data. This is
/// <c>false</c> by default to minimize the size of the saved-game data by only
/// recording State and Track (for quests).
/// </summary>
public static bool includeAllItemData = false ;
/// <summary>
/// Set <c>true</c> to include location data in save data, <c>false</c> to exclude.
/// </summary>
public static bool includeLocationData = false ;
/// <summary>
/// Set <c>true</c> to include all conversation fields, <c>false</c> to exclude.
/// </summary>
public static bool includeAllConversationFields = false ;
/// <summary>
/// Set this <c>true</c> to exclude Conversation[#].Dialog[#].SimStatus values from
/// saved-game data. If you don't use SimStatus in your Lua conditions, there's no
/// need to save it.
/// </summary>
public static bool includeSimStatus = false ;
/// <summary>
/// Optional field to use when saving a conversation's SimStatus info (e.g., Title).
/// This feature is handy if you can't guarantee that conversation IDs will be the
/// same across saved games. If set, saves the conversation's SimStatus info into
/// a field. If blank, uses conversation ID.
/// </summary>
public static string saveConversationSimStatusWithField = string . Empty ;
/// <summary>
/// Optional field to use when saving a dialogue entry's SimStatus info (e.g,. Title).
/// This feature is handy if you can't guarantee that dialogue entry IDs will be the
/// same across saved games. If set, saves the entry's SimStatus value into a field.
/// If blank, uses entry's ID.
/// </summary>
public static string saveDialogueEntrySimStatusWithField = string . Empty ;
/// <summary>
/// Set <c>true</c> to include the status & relationship tables in save data,
/// <c>false</c> to exclude.
/// </summary>
public static bool includeRelationshipAndStatusData = true ;
/// <summary>
/// Initialize variables and quests that were added to database after saved game.")]
/// </summary>
public static bool initializeNewVariables = true ;
/// <summary>
/// Initialize new SimStatus values for entries that were added to database after saved game.
/// </summary>
public static bool initializeNewSimStatus = true ;
/// <summary>
/// PersistentDataManager will call this delegate (if set) to add custom data
/// to the saved-game data string. The custom data should be valid Lua code.
/// </summary>
public static GetCustomSaveDataDelegate GetCustomSaveData = null ;
public enum RecordPersistentDataOn
{
/// <summary>
/// Inform all components on all GameObjects in the scene to record their persistent data
/// if supported.
/// </summary>
AllGameObjects ,
/// <summary>
/// Inform only components that have registered to receive notifications to record their
/// persistent data.
/// </summary>
OnlyRegisteredGameObjects ,
/// <summary>
/// Entirely skip informing any GameObjects to record their persistent data.
/// </summary>
NoGameObjects
}
public static RecordPersistentDataOn recordPersistentDataOn = RecordPersistentDataOn . AllGameObjects ;
private static HashSet < GameObject > listeners = new HashSet < GameObject > ( ) ;
#if UNITY_2019_3_OR_NEWER & & UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void InitStaticVariables ( )
{
GetCustomSaveData = null ;
listeners = new HashSet < GameObject > ( ) ;
}
#endif
#endregion
#region Register Persistent Data Components
/// <summary>
///
/// </summary>
/// <param name="go">GameObject that should receive notifications.</param>
public static void RegisterPersistentData ( GameObject go )
{
if ( go = = null | | ! Application . isPlaying ) return ;
listeners . Add ( go ) ;
}
/// <summary>
///
/// </summary>
/// <param name="go">GameObject that should no longer receive notifications.</param>
public static void UnregisterPersistentData ( GameObject go )
{
if ( ! Application . isPlaying ) return ;
listeners . Remove ( go ) ;
}
#endregion
#region Main Data Management Methods
/// <summary>
/// Resets the Lua environment -- for example, when starting a new game.
/// </summary>
/// <param name='databaseResetOptions'>
/// The database reset options can be:
///
/// - RevertToDefault: Removes all but the default database, then resets it.
/// - KeepAllLoaded: Keeps all loaded databases in memory and just resets them.
/// </param>
public static void Reset ( DatabaseResetOptions databaseResetOptions )
{
DialogueManager . ResetDatabase ( databaseResetOptions ) ;
}
/// <summary>
/// Resets the Lua environment -- for example, when starting a new game -- keeping all loaded database
/// in memory and just resetting them.
/// </summary>
public static void Reset ( )
{
Reset ( DatabaseResetOptions . KeepAllLoaded ) ;
}
/// <summary>
/// Sends the OnRecordPersistentData message to all GameObjects in the scene to give them
/// an opportunity to record their state in the Lua environment. You can limit which GameObjects
/// receive messages by changing recordPersistentDataOn.
/// </summary>
public static void Record ( )
{
if ( DialogueDebug . LogInfo ) Debug . Log ( string . Format ( "{0}: Recording persistent data to Lua environment." , new System . Object [ ] { DialogueDebug . Prefix } ) ) ;
SendPersistentDataMessage ( "OnRecordPersistentData" ) ;
}
/// <summary>
/// Sends the OnApplyPersistentData message to all game objects in the scene to give them an
/// opportunity to retrieve their state from the Lua environment. If calling this after loading
/// a new scene, you may want to wait one frame to allow other GameObject's Start methods
/// to complete first. You can limit which GameObjects receive messages by changing
/// recordPersistentDataOn.
/// </summary>
public static void Apply ( )
{
if ( DialogueDebug . LogInfo ) Debug . Log ( string . Format ( "{0}: Applying persistent data from Lua environment." , new System . Object [ ] { DialogueDebug . Prefix } ) ) ;
SendPersistentDataMessage ( "OnApplyPersistentData" ) ;
DialogueManager . SendUpdateTracker ( ) ; // Update quest tracker HUD.
}
private static void SendPersistentDataMessage ( string message )
{
switch ( recordPersistentDataOn )
{
case RecordPersistentDataOn . AllGameObjects :
Tools . SendMessageToEveryone ( message ) ;
break ;
case RecordPersistentDataOn . OnlyRegisteredGameObjects :
2024-01-04 04:53:13 +00:00
var gos = new List < GameObject > ( listeners ) ; // listeners may change during loop.
for ( int i = gos . Count - 1 ; i > = 0 ; i - - )
2023-10-12 01:09:31 +00:00
{
2024-01-04 04:53:13 +00:00
var go = gos [ i ] ;
2023-10-12 01:09:31 +00:00
if ( go ! = null ) go . SendMessage ( message , SendMessageOptions . DontRequireReceiver ) ;
}
break ;
default :
break ;
}
}
/// <summary>
/// Sends the OnLevelWillBeUnloaded message to all game objects in the scene in case they
/// need to change their behavior. For example, scripts that do something special when
/// destroyed during play may not want to do the same thing when being destroyed by a
/// level unload.
/// </summary>
public static void LevelWillBeUnloaded ( )
{
if ( DialogueDebug . LogInfo ) Debug . Log ( string . Format ( "{0}: Broadcasting that level will be unloaded." , new System . Object [ ] { DialogueDebug . Prefix } ) ) ;
SendPersistentDataMessage ( "OnLevelWillBeUnloaded" ) ;
}
/// <summary>
/// Loads a saved game by applying a saved-game string.
/// </summary>
/// <param name='saveData'>
/// A saved-game string previously returned by GetSaveData().
/// </param>
/// <param name='databaseResetOptions'>
/// Database reset options.
/// </param>
public static void ApplySaveData ( string saveData , DatabaseResetOptions databaseResetOptions = DatabaseResetOptions . KeepAllLoaded )
{
if ( DialogueDebug . LogInfo ) Debug . Log ( string . Format ( "{0}: Resetting Lua environment." , new System . Object [ ] { DialogueDebug . Prefix } ) ) ;
DialogueManager . ResetDatabase ( databaseResetOptions ) ;
if ( DialogueDebug . LogInfo ) Debug . Log ( string . Format ( "{0}: Updating Lua environment with saved data." , new System . Object [ ] { DialogueDebug . Prefix } ) ) ;
ApplyLuaInternal ( saveData ) ;
Apply ( ) ;
}
/// <summary>
/// Loads data into the Lua environment.
/// </summary>
/// <param name="saveData"></param>
/// <param name="allowExceptions"></param>
public static void ApplyLuaInternal ( string saveData , bool allowExceptions = false )
{
if ( ! string . IsNullOrEmpty ( saveData ) )
{
EnsureConversationTablesExistForAllSimX ( saveData ) ;
EnsureQuestsExist ( saveData ) ;
Lua . Run ( saveData , DialogueDebug . LogInfo ) ;
ExpandCompressedSimStatusData ( ) ;
RefreshRelationshipAndStatusTablesFromLua ( ) ;
if ( initializeNewVariables )
{
InitializeNewVariablesFromDatabase ( ) ;
InitializeNewActorFieldsFromDatabase ( ) ;
InitializeNewQuestEntriesFromDatabase ( ) ;
InitializeNewSimStatusFromDatabase ( ) ;
}
}
}
#if USE_NLUA
/// <summary>
/// If using SimStatus, make sure Conversation[#] elements exist for all onversations in
/// the saved game data.
/// </summary>
/// <param name="saveData"></param>
private static void EnsureConversationTablesExistForAllSimX ( string saveData )
{
if ( ! ( includeSimStatus & & DialogueManager . Instance . includeSimStatus ) ) return ;
var conversationTable = Lua . Run ( "return Conversation" ) . asTable ;
if ( conversationTable = = null ) return ;
var keysHashSet = new HashSet < string > ( conversationTable . keys ) ;
var preLength = "Conversation[" . Length ;
foreach ( Match match in Regex . Matches ( saveData , @"Conversation\[\d+\]" ) )
{
var idString = match . Value . Substring ( preLength , match . Value . Length - ( preLength + 1 ) ) ;
if ( ! keysHashSet . Contains ( idString ) ) Lua . Run ( "Conversation[" + idString + "] = {}" ) ;
}
}
/// <summary>
/// If only saving quest states (and not all item/quest data), make sure Item["x"] elements
/// exist for all quests in the saved game data.
/// </summary>
/// <param name="saveData"></param>
private static void EnsureQuestsExist ( string saveData )
{
if ( includeAllItemData | | DialogueManager . Instance . persistentDataSettings . includeAllItemData ) return ;
var itemTable = Lua . Run ( "return Item" ) . asTable ;
if ( itemTable = = null ) return ;
var keysHashSet = new HashSet < string > ( itemTable . keys ) ;
var preLength = "Item[" . Length ;
var postLength = "].State" . Length ;
foreach ( Match match in Regex . Matches ( saveData , @"Item\[[^\]]+\].State" ) )
{
var s = match . Value . Substring ( preLength + 1 , match . Value . Length - ( preLength + postLength + 2 ) ) ;
if ( ! keysHashSet . Contains ( s ) ) Lua . Run ( "Item[" + s + "] = { Name='" + s . Replace ( "\"" , "\\\"" ) + "', State='unassigned' }" ) ;
}
}
#else
/// <summary>
/// If using SimStatus, make sure Conversation[#] elements exist for all onversations in
/// the saved game data.
/// </summary>
/// <param name="saveData"></param>
private static void EnsureConversationTablesExistForAllSimX ( string saveData )
{
if ( ! ( includeSimStatus & & DialogueManager . Instance . includeSimStatus ) ) return ;
var conversationTable = Lua . Environment . GetValue ( "Conversation" ) as Language . Lua . LuaTable ;
if ( conversationTable = = null ) return ;
var preLength = "Conversation[" . Length ;
foreach ( Match match in Regex . Matches ( saveData , @"Conversation\[\d+\]" ) )
{
var idString = match . Value . Substring ( preLength , match . Value . Length - ( preLength + 1 ) ) ;
var key = new Language . Lua . LuaNumber ( SafeConvert . ToInt ( idString ) ) ;
if ( ! conversationTable . ContainsKey ( key ) )
{
conversationTable . SetKeyValue ( key , new Language . Lua . LuaTable ( ) ) ;
}
}
}
/// <summary>
/// If only saving quest states (and not all item/quest data), make sure Item["x"] elements
/// exist for all quests in the saved game data.
/// </summary>
/// <param name="saveData"></param>
private static void EnsureQuestsExist ( string saveData )
{
if ( includeAllItemData | | DialogueManager . Instance . persistentDataSettings . includeAllItemData ) return ;
var itemTable = Lua . Environment . GetValue ( "Item" ) as Language . Lua . LuaTable ;
if ( itemTable = = null ) return ;
var preLength = "Item[" . Length ;
var postLength = "].State" . Length ;
foreach ( Match match in Regex . Matches ( saveData , @"Item\[[^\]]+\].State" ) )
{
var s = match . Value . Substring ( preLength + 1 , match . Value . Length - ( preLength + postLength + 2 ) ) ;
if ( itemTable . GetKey ( s ) = = Language . Lua . LuaNil . Nil )
{
var questKey = new Language . Lua . LuaString ( s ) ;
var table = new Language . Lua . LuaTable ( ) ;
table . RawSetValue ( "Name" , new Language . Lua . LuaString ( s ) ) ;
table . RawSetValue ( "State" , new Language . Lua . LuaString ( "unassigned" ) ) ;
itemTable . SetKeyValue ( questKey , table ) ;
}
}
}
#endif
/// <summary>
/// Saves a game by retrieving the Lua environment and returning it as a saved-game string.
/// This method calls Record() to allow all game objects in the scene to record their state
/// to the Lua environment first. The returned string is human-readable Lua code.
/// </summary>
/// <returns>
/// The saved-game data.
/// </returns>
/// <remarks>
/// To reduce saved-game data size, only the following information is recorded from the
/// Chat Mapper tables (Item[], Actor[], etc):
///
/// - <c>Actor[]</c>: all data
/// - <c>Item[]</c>: only <c>State</c> (for quest log system)
/// - <c>Location[]</c>: nothing
/// - <c>Variable[]</c>: current value of each variable
/// - <c>Conversation[]</c>: SimStatus
/// - Relationship and status information is recorded
/// </remarks>
public static string GetSaveData ( )
{
Record ( ) ;
string saveData ;
var sb = new StringBuilder ( ) ;
AppendDialogueSystemData ( sb ) ;
saveData = sb . ToString ( ) ;
if ( DialogueDebug . LogInfo ) Debug . Log ( string . Format ( "{0}: Saved data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , saveData } ) ) ;
return saveData ;
}
#endregion
#region Save ( Non - Conversation Data )
public static void AppendDialogueSystemData ( StringBuilder sb )
{
if ( sb = = null ) return ;
AppendVariableData ( sb ) ;
AppendItemData ( sb ) ;
AppendLocationData ( sb ) ;
if ( includeActorData ) AppendActorData ( sb ) ;
AppendConversationData ( sb ) ;
if ( includeRelationshipAndStatusData ) AppendRelationshipAndStatusTables ( sb ) ;
if ( GetCustomSaveData ! = null ) sb . Append ( GetCustomSaveData ( ) ) ;
}
/// <summary>
/// Appends the user variable table to a (saved-game) string.
/// </summary>
public static void AppendVariableData ( StringBuilder sb )
{
try
{
LuaTableWrapper variableTable = Lua . Run ( "return Variable" ) . AsTable ;
if ( variableTable = = null )
{
if ( DialogueDebug . LogErrors ) Debug . LogError ( string . Format ( "{0}: Persistent Data Manager couldn't access Lua Variable[] table" , new System . Object [ ] { DialogueDebug . Prefix } ) ) ;
return ;
}
sb . Append ( "Variable={" ) ;
var first = true ;
foreach ( var key in variableTable . Keys )
{
if ( string . IsNullOrEmpty ( key ) ) continue ;
if ( ! first ) sb . Append ( ", " ) ;
first = false ;
var value = variableTable [ key . ToString ( ) ] ;
sb . AppendFormat ( "{0}={1}" , new System . Object [ ] { GetFieldKeyString ( key ) , GetFieldValueString ( value ) } ) ;
}
sb . Append ( "}; " ) ;
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: GetSaveData() failed to get variable data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
}
/// <summary>
/// Appends the item table to a (saved-game) string.
/// </summary>
public static void AppendItemData ( StringBuilder sb )
{
try
{
LuaTableWrapper itemTable = Lua . Run ( "return Item" ) . AsTable ;
if ( itemTable = = null )
{
if ( DialogueDebug . LogErrors ) Debug . LogError ( string . Format ( "{0}: Persistent Data Manager couldn't access Lua Item[] table" , new System . Object [ ] { DialogueDebug . Prefix } ) ) ;
return ;
}
// Cache titles of items in database:
HashSet < string > itemsInDatabase = new HashSet < string > ( ) ;
if ( ! includeAllItemData )
{
var database = DialogueManager . masterDatabase ;
for ( int i = 0 ; i < database . items . Count ; i + + )
{
itemsInDatabase . Add ( DialogueLua . StringToTableIndex ( database . items [ i ] . Name ) ) ;
}
}
// Process all items:
foreach ( var title in itemTable . Keys )
{
LuaTableWrapper fields = itemTable [ title . ToString ( ) ] as LuaTableWrapper ;
bool onlySaveQuestData = ! includeAllItemData & & itemsInDatabase . Contains ( title ) ; //---Was: (DialogueManager.MasterDatabase.items.Find(i => string.Equals(DialogueLua.StringToTableIndex(i.Name), title)) != null);
if ( fields ! = null )
{
if ( onlySaveQuestData )
{
// If in the database, just record quest statuses and tracking:
foreach ( var fieldKey in fields . Keys )
{
if ( string . IsNullOrEmpty ( fieldKey ) ) continue ;
string fieldTitle = fieldKey . ToString ( ) ;
if ( fieldTitle . EndsWith ( "State" ) )
{
sb . AppendFormat ( "Item[\"{0}\"].{1}=\"{2}\"; " , new System . Object [ ] { DialogueLua . StringToTableIndex ( title ) , ( System . Object ) fieldTitle , ( System . Object ) fields [ fieldTitle ] } ) ;
}
else if ( string . Equals ( fieldTitle , "Track" ) | | string . Equals ( fieldTitle , "Viewed" ) )
{
sb . AppendFormat ( "Item[\"{0}\"].{1}={2}; " , new System . Object [ ] { DialogueLua . StringToTableIndex ( title ) , fieldTitle , fields [ fieldTitle ] . ToString ( ) . ToLower ( ) } ) ;
}
}
}
else
{
// If saving all data or item is not in the database, record all fields:
sb . AppendFormat ( "Item[\"{0}\"]=" , new System . Object [ ] { DialogueLua . StringToTableIndex ( title ) } ) ;
AppendFields ( sb , fields ) ;
}
}
}
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: GetSaveData() failed to get item data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
}
private static void AppendFields ( StringBuilder sb , LuaTableWrapper fields )
{
sb . Append ( "{" ) ;
try
{
if ( fields ! = null )
{
foreach ( var key in fields . Keys )
{
if ( string . IsNullOrEmpty ( key ) ) continue ;
var value = fields [ key ] ;
var valueString = GetFieldValueString ( value ) ;
if ( string . Equals ( key , "Pictures" ) ) valueString = valueString . Replace ( "\\" , "/" ) ; // Sanitize backslashes in Pictures.
sb . AppendFormat ( "{0}={1}, " , new System . Object [ ] { GetFieldKeyString ( key ) , valueString } ) ;
}
}
}
finally
{
sb . Append ( "}; " ) ;
}
}
// Faster to check manually than use Regex:
private static string GetFieldKeyString ( string key )
{
key = DialogueLua . StringToTableIndex ( key ) ;
return IsValidVarName ( key ) ? key : ( "[\"" + key + "\"]" ) ;
}
private static bool IsValidVarName ( string key )
{
if ( string . IsNullOrEmpty ( key ) ) return false ;
char firstChar = key [ 0 ] ;
if ( ! ( firstChar = = '_' | | ( 'a' < = firstChar & & firstChar < = 'z' ) | | ( 'A' < = firstChar & & firstChar < = 'Z' ) ) ) return false ;
for ( int i = 1 ; i < key . Length ; i + + )
{
var c = key [ i ] ;
if ( ! ( c = = '_' | | ( 'a' < = c & & c < = 'z' ) | | ( 'A' < = c & & c < = 'Z' ) | | ( '0' < = c & & c < = '9' ) ) ) return false ;
}
return true ;
}
private static string GetFieldValueString ( object o )
{
if ( o = = null )
{
return "nil" ;
}
else
{
System . Type type = o . GetType ( ) ;
if ( type = = typeof ( string ) )
{
return string . Format ( "\"{0}\"" , new System . Object [ ] { DialogueLua . DoubleQuotesToSingle ( o . ToString ( ) . Replace ( "\n" , "\\n" ) . Replace ( "\\ " , "/ " ) ) } ) ;
}
else if ( type = = typeof ( bool ) )
{
return o . ToString ( ) . ToLower ( ) ;
}
else if ( type = = typeof ( float ) | | type = = typeof ( double ) )
{
return ( ( float ) o ) . ToString ( System . Globalization . CultureInfo . InvariantCulture ) ;
}
else if ( type = = typeof ( LuaTableWrapper ) )
{
StringBuilder sb = new StringBuilder ( ) ;
AppendFields ( sb , ( LuaTableWrapper ) o ) ;
return "{" + sb . ToString ( ) + "}" ;
}
else
{
return o . ToString ( ) ;
}
}
}
/// <summary>
/// Appends the location table to a (saved-game) string. Currently doesn't save anything unless
/// includeLocationData is true.
/// </summary>
public static void AppendLocationData ( StringBuilder sb )
{
if ( ! includeLocationData ) return ;
try
{
LuaTableWrapper locationTable = Lua . Run ( "return Location" ) . AsTable ;
if ( locationTable = = null )
{
if ( DialogueDebug . LogErrors ) Debug . LogError ( string . Format ( "{0}: Persistent Data Manager couldn't access Lua Location[] table" , new System . Object [ ] { DialogueDebug . Prefix } ) ) ;
return ;
}
sb . Append ( "Location={" ) ;
var first = true ;
foreach ( var key in locationTable . Keys )
{
if ( string . IsNullOrEmpty ( key ) ) continue ;
LuaTableWrapper fields = locationTable [ key ] as LuaTableWrapper ;
if ( ! first ) sb . Append ( ", " ) ;
first = false ;
sb . Append ( GetFieldKeyString ( key ) ) ;
sb . Append ( "={" ) ;
try
{
AppendAssetFieldData ( sb , fields ) ;
}
finally
{
sb . Append ( "}" ) ;
}
}
sb . Append ( "}; " ) ;
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: GetSaveData() failed to get location data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
}
/// <summary>
/// Appends the actor table to a (saved-game) string.
/// </summary>
public static void AppendActorData ( StringBuilder sb )
{
try
{
LuaTableWrapper actorTable = Lua . Run ( "return Actor" ) . AsTable ;
if ( actorTable = = null )
{
if ( DialogueDebug . LogErrors ) Debug . LogError ( string . Format ( "{0}: Persistent Data Manager couldn't access Lua Actor[] table" , new System . Object [ ] { DialogueDebug . Prefix } ) ) ;
return ;
}
sb . Append ( "Actor={" ) ;
var first = true ;
foreach ( var key in actorTable . Keys )
{
if ( string . IsNullOrEmpty ( key ) ) continue ;
LuaTableWrapper fields = actorTable [ key ] as LuaTableWrapper ;
if ( ! first ) sb . Append ( ", " ) ;
first = false ;
sb . Append ( GetFieldKeyString ( key ) ) ;
sb . Append ( "={" ) ;
try
{
AppendAssetFieldData ( sb , fields ) ;
}
finally
{
sb . Append ( "}" ) ;
}
}
sb . Append ( "}; " ) ;
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: GetSaveData() failed to get actor data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
}
/// <summary>
/// Appends an actor record to a saved-game string.
/// </summary>
private static void AppendAssetFieldData ( StringBuilder sb , LuaTableWrapper fields )
{
if ( fields = = null ) return ;
var first = true ;
foreach ( var key in fields . Keys )
{
if ( string . IsNullOrEmpty ( key ) ) continue ;
if ( ! first ) sb . Append ( ", " ) ;
first = false ;
var value = fields [ key ] ;
sb . AppendFormat ( "{0}={1}" , new System . Object [ ] { GetFieldKeyString ( key ) , GetFieldValueString ( value ) } ) ;
}
}
/// <summary>
/// Appends the relationship and status tables to a (saved-game) string.
/// </summary>
/// <param name="sb">StringBuilder to append to.</param>
public static void AppendRelationshipAndStatusTables ( StringBuilder sb )
{
try
{
sb . Append ( DialogueLua . GetStatusTableAsLua ( ) ) ;
sb . Append ( DialogueLua . GetRelationshipTableAsLua ( ) ) ;
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: GetSaveData() failed to get relationship and status data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
}
/// <summary>
/// Instructs the Dialogue System to refresh its internal relationship and status tables
/// from the values in the Lua environment. Call this after putting new values in the
/// Lua environment, such as when loading a saved game.
/// </summary>
public static void RefreshRelationshipAndStatusTablesFromLua ( )
{
DialogueLua . RefreshStatusTableFromLua ( ) ;
DialogueLua . RefreshRelationshipTableFromLua ( ) ;
}
#endregion
#region Save ( Conversation Data )
/// <summary>
/// Appends the conversation table to a (saved-game) string. To conserve space, only the
/// SimStatus is recorded. If includeSimStatus is <c>false</c>, nothing is recorded.
/// The exception is if includeAllConversationFields is true.
/// </summary>
public static void AppendConversationData ( StringBuilder sb )
{
if ( includeAllConversationFields | | DialogueManager . Instance . persistentDataSettings . includeAllConversationFields )
{
AppendAllConversationFields ( sb ) ;
}
if ( includeSimStatus & & DialogueManager . Instance . includeSimStatus )
{
AppendSimStatus ( sb ) ;
}
}
/// <summary>
/// Appends all conversation fields to a saved-game string. Note that this doesn't
/// append the fields inside each dialogue entry, just the fields in the conversation
/// objects themselves.
/// </summary>
private static void AppendAllConversationFields ( StringBuilder sb )
{
try
{
LuaTableWrapper conversationTable = Lua . Run ( "return Conversation" ) . AsTable ;
if ( conversationTable = = null )
{
if ( DialogueDebug . LogErrors ) Debug . LogError ( string . Format ( "{0}: Persistent Data Manager couldn't access Lua Conversation[] table" , new System . Object [ ] { DialogueDebug . Prefix } ) ) ;
return ;
}
foreach ( var convIndex in conversationTable . Keys ) // Loop through conversations:
{
LuaTableWrapper fields = Lua . Run ( "return Conversation[" + convIndex + "]" ) . AsTable ;
if ( fields = = null ) continue ;
sb . Append ( "Conversation[" + convIndex + "]={" ) ;
try
{
var first = true ;
foreach ( var key in fields . Keys )
{
if ( string . IsNullOrEmpty ( key ) ) continue ;
if ( string . Equals ( key , "Dialog" ) ) continue ;
if ( ! first ) sb . Append ( ", " ) ;
first = false ;
var value = fields [ key . ToString ( ) ] ;
sb . AppendFormat ( "{0}={1}" , new System . Object [ ] { GetFieldKeyString ( key ) , GetFieldValueString ( value ) } ) ;
}
}
finally
{
sb . Append ( "}; " ) ;
}
}
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: GetSaveData() failed to get conversation data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
}
#if USE_NLUA
/// <summary>
/// Appends SimStatus for all conversations.
/// </summary>
private static void AppendSimStatus ( StringBuilder sb )
{
try
{
var useConversationID = string . IsNullOrEmpty ( saveConversationSimStatusWithField ) ;
var useEntryID = string . IsNullOrEmpty ( saveDialogueEntrySimStatusWithField ) ;
foreach ( var conversation in DialogueManager . MasterDatabase . conversations )
{
if ( useConversationID )
{
sb . AppendFormat ( "Conversation[{0}].SimX=\"" , conversation . id ) ;
}
else
{
var fieldValue = DialogueLua . StringToTableIndex ( conversation . LookupValue ( saveConversationSimStatusWithField ) ) ;
if ( string . IsNullOrEmpty ( fieldValue ) ) fieldValue = conversation . id . ToString ( ) ;
sb . AppendFormat ( "Variable[\"Conversation_SimX_{0}\"]=\"" , fieldValue ) ;
}
var dialogTable = Lua . Run ( "return Conversation[" + conversation . id + "].Dialog" ) . asTable ;
var first = true ;
for ( int i = 0 ; i < conversation . dialogueEntries . Count ; i + + )
{
var entry = conversation . dialogueEntries [ i ] ;
var entryID = entry . id ;
var dialogFields = dialogTable [ entryID ] as NLua . LuaTable ;
if ( dialogFields ! = null )
{
if ( ! first ) sb . Append ( ";" ) ;
first = false ;
sb . Append ( useEntryID ? entryID . ToString ( ) : Field . LookupValue ( entry . fields , saveDialogueEntrySimStatusWithField ) ) ;
sb . Append ( ";" ) ;
var simStatus = dialogFields [ DialogueLua . SimStatus ] . ToString ( ) ;
sb . Append ( SimStatusToChar ( simStatus ) ) ;
}
}
sb . Append ( "\"; " ) ;
}
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: GetSaveData() failed to get conversation data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
}
private static void ExpandCompressedSimStatusData ( )
{
if ( ! ( includeSimStatus & & DialogueManager . Instance . includeSimStatus ) ) return ;
try
{
var useConversationID = string . IsNullOrEmpty ( saveConversationSimStatusWithField ) ;
var useEntryID = string . IsNullOrEmpty ( saveDialogueEntrySimStatusWithField ) ;
var entryDict = new Dictionary < string , DialogueEntry > ( ) ;
foreach ( var conversation in DialogueManager . MasterDatabase . conversations )
{
// If saving dialogue entries' SimStatus with value of a field, make a lookup table:
if ( ! useEntryID )
{
entryDict . Clear ( ) ;
for ( int i = 0 ; i < conversation . dialogueEntries . Count ; i + + )
{
var entry = conversation . dialogueEntries [ i ] ;
var entryFieldValue = Field . LookupValue ( entry . fields , saveDialogueEntrySimStatusWithField ) ;
if ( ! entryDict . ContainsKey ( entryFieldValue ) ) entryDict . Add ( entryFieldValue , entry ) ;
}
}
var sb = new StringBuilder ( ) ;
string simX ;
if ( useConversationID )
{
simX = Lua . Run ( "return Conversation[" + conversation . id + "].SimX" ) . AsString ;
}
else
{
var fieldValue = DialogueLua . StringToTableIndex ( conversation . LookupValue ( saveConversationSimStatusWithField ) ) ;
if ( string . IsNullOrEmpty ( fieldValue ) ) fieldValue = conversation . id . ToString ( ) ;
simX = Lua . Run ( "return Variable[\"Conversation_SimX_" + fieldValue + "\"]" ) . AsString ;
}
if ( string . IsNullOrEmpty ( simX ) | | string . Equals ( simX , "nil" ) ) continue ;
var clearSimXCommand = useConversationID ? ( "Conversation[" + conversation . id + "].SimX=nil;" )
: ( "Variable[\"Conversation_SimX_" + DialogueLua . StringToTableIndex ( conversation . LookupValue ( saveConversationSimStatusWithField ) ) + "\"]=nil;" ) ;
sb . Append ( "Conversation[" ) ;
sb . Append ( conversation . id ) ;
sb . Append ( "].Dialog={}; " ) ;
var simXFields = simX . Split ( ';' ) ;
var numFields = simXFields . Length / 2 ;
for ( int i = 0 ; i < numFields ; i + + )
{
var simXEntryIDValue = simXFields [ 2 * i ] ;
string entryID ;
if ( useEntryID )
{
entryID = simXEntryIDValue ;
}
else
{
entryID = entryDict . ContainsKey ( simXEntryIDValue ) ? entryDict [ simXEntryIDValue ] . id . ToString ( ) : "-1" ;
}
var simStatus = CharToSimStatus ( simXFields [ ( 2 * i ) + 1 ] [ 0 ] ) ;
sb . Append ( "Conversation[" ) ;
sb . Append ( conversation . id ) ;
sb . Append ( "].Dialog[" ) ;
sb . Append ( entryID ) ;
sb . Append ( "]={SimStatus='" ) ;
sb . Append ( simStatus ) ;
sb . Append ( "'}; " ) ;
}
sb . Append ( clearSimXCommand ) ;
Lua . Run ( sb . ToString ( ) ) ;
}
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: ApplySaveData() failed to re-expand compressed SimStatus data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
}
#else
// Used by SimStatus methods:
private static bool useConversationID = true ;
private static bool useEntryID = true ;
/// <summary>
/// Appends SimStatus for all conversations.
/// </summary>
private static void AppendSimStatus ( StringBuilder sb )
{
try
{
useConversationID = string . IsNullOrEmpty ( saveConversationSimStatusWithField ) ;
useEntryID = string . IsNullOrEmpty ( saveDialogueEntrySimStatusWithField ) ;
var conversationTable = Lua . Environment . GetValue ( "Conversation" ) as Language . Lua . LuaTable ;
if ( conversationTable = = null ) return ;
for ( int i = 0 ; i < conversationTable . List . Count ; i + + )
{
var conversationID = i + 1 ;
var fieldTable = conversationTable . List [ i ] as Language . Lua . LuaTable ;
AppendSimStatusForConversation ( sb , conversationTable , conversationID , fieldTable ) ;
}
foreach ( var kvp in conversationTable . Dict )
{
if ( kvp . Key = = null | | kvp . Value = = null | | ! ( kvp . Value is Language . Lua . LuaTable ) ) continue ;
var conversationID = Tools . StringToInt ( kvp . Key . ToString ( ) ) ;
var fieldTable = kvp . Value as Language . Lua . LuaTable ;
AppendSimStatusForConversation ( sb , conversationTable , conversationID , fieldTable ) ;
}
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: GetSaveData() failed to get conversation data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
}
private static Dictionary < int , string > s_dialogueEntrySimStatusFieldLookupTable = new Dictionary < int , string > ( ) ;
/// <summary>
/// Appends the SimStatus info for a single conversation.
/// </summary>
/// <returns>Returns the number of dialogue entries in the conversation.</returns>
private static int AppendSimStatusForConversation ( StringBuilder sb , Language . Lua . LuaTable conversationTable , int conversationID , Language . Lua . LuaTable fieldTable )
{
if ( sb = = null | | conversationTable = = null | | fieldTable = = null ) return 0 ;
var dialogTable = fieldTable . GetValue ( "Dialog" ) as Language . Lua . LuaTable ;
if ( dialogTable = = null ) return 0 ;
var conversation = DialogueManager . MasterDatabase . GetConversation ( conversationID ) ;
if ( conversation = = null ) return 0 ;
if ( useConversationID )
{
sb . AppendFormat ( "Conversation[{0}].SimX=\"" , conversationID ) ;
}
else
{
sb . AppendFormat ( "Variable[\"Conversation_SimX_{0}\"]=\"" , DialogueLua . StringToTableIndex ( conversation . LookupValue ( saveConversationSimStatusWithField ) ) ) ;
}
var first = true ;
for ( int i = 0 ; i < dialogTable . List . Count ; i + + )
{
var entryID = i + 1 ;
var entryIDString = entryID . ToString ( ) ;
var simStatusTable = dialogTable . List [ i ] as Language . Lua . LuaTable ;
if ( ! first ) sb . Append ( ";" ) ;
first = false ;
if ( useEntryID )
{
sb . Append ( entryIDString ) ;
}
else
{
var entry = conversation . GetDialogueEntry ( entryID ) ;
var fieldName = ( entry ! = null ) ? Field . LookupValue ( entry . fields , saveDialogueEntrySimStatusWithField ) : entryIDString ;
sb . Append ( fieldName ) ;
}
sb . Append ( ";" ) ;
var simStatus = simStatusTable . GetValue ( DialogueLua . SimStatus ) . ToString ( ) ;
sb . Append ( SimStatusToChar ( simStatus ) ) ;
}
if ( ! useEntryID )
{
// Create a lookup table to speed up lookups of each dialogue entry's SimStatus field:
s_dialogueEntrySimStatusFieldLookupTable . Clear ( ) ;
for ( int i = 0 ; i < conversation . dialogueEntries . Count ; i + + )
{
var entry = conversation . dialogueEntries [ i ] ;
var field = Field . Lookup ( entry . fields , saveDialogueEntrySimStatusWithField ) ;
var fieldName = ( field ! = null ) ? field . value : entry . id . ToString ( ) ;
s_dialogueEntrySimStatusFieldLookupTable . Add ( entry . id , fieldName ) ;
}
}
foreach ( var kvp2 in dialogTable . KeyValuePairs )
{
var entryIDString = kvp2 . Key . ToString ( ) ;
var simStatusTable = kvp2 . Value as Language . Lua . LuaTable ;
if ( ! first ) sb . Append ( ";" ) ;
first = false ;
if ( useEntryID )
{
sb . Append ( entryIDString ) ;
}
else
{
var entryID = Tools . StringToInt ( entryIDString ) ;
sb . Append ( s_dialogueEntrySimStatusFieldLookupTable [ entryID ] ) ;
}
sb . Append ( ";" ) ;
var simStatus = simStatusTable . GetValue ( DialogueLua . SimStatus ) . ToString ( ) ;
sb . Append ( SimStatusToChar ( simStatus ) ) ;
}
sb . Append ( "\"; " ) ;
s_dialogueEntrySimStatusFieldLookupTable . Clear ( ) ;
return conversation . dialogueEntries . Count ;
}
/// <summary>
/// When reapplying saved data, expands compress SimX info into conversations' SimStatus tables.
/// </summary>
private static void ExpandCompressedSimStatusData ( )
{
if ( ! ( includeSimStatus & & DialogueManager . Instance . includeSimStatus ) ) return ;
// Track conversations so we know which ones were added after the saved game:
var conversationsLeft = new HashSet < int > ( ) ;
var conversations = DialogueManager . MasterDatabase . conversations ;
for ( int i = 0 ; i < conversations . Count ; i + + )
{
conversationsLeft . Add ( conversations [ i ] . id ) ;
}
// Reusable dialogue entry cache used by ExpandSimStatusForConversation:
var dialogueEntryCache = new Dictionary < int , DialogueEntry > ( ) ;
var luaStringSimX = new Language . Lua . LuaString ( "SimX" ) ;
useConversationID = string . IsNullOrEmpty ( saveConversationSimStatusWithField ) ;
useEntryID = string . IsNullOrEmpty ( saveDialogueEntrySimStatusWithField ) ;
var conversationTable = Lua . Environment . GetValue ( "Conversation" ) as Language . Lua . LuaTable ;
if ( conversationTable = = null ) return ;
var sb = new StringBuilder ( 16384 , System . Int32 . MaxValue ) ;
for ( int i = 0 ; i < conversationTable . List . Count ; i + + )
{
var conversationID = i + 1 ;
var fieldTable = conversationTable . List [ i ] as Language . Lua . LuaTable ;
if ( ExpandSimStatusForConversation ( sb , conversationID , conversationID . ToString ( ) , fieldTable , luaStringSimX , dialogueEntryCache ) )
{
conversationsLeft . Remove ( conversationID ) ;
}
}
foreach ( var kvp in conversationTable . Dict )
{
if ( kvp . Key = = null | | kvp . Value = = null | | ! ( kvp . Value is Language . Lua . LuaTable ) ) continue ;
var conversationIDString = kvp . Key . ToString ( ) ;
var conversationID = Tools . StringToInt ( conversationIDString ) ;
var fieldTable = kvp . Value as Language . Lua . LuaTable ;
if ( ExpandSimStatusForConversation ( sb , conversationID , conversationIDString , fieldTable , luaStringSimX , dialogueEntryCache ) )
{
conversationsLeft . Remove ( conversationID ) ;
}
}
Lua . Run ( sb . ToString ( ) ) ;
// Add SimStatus for new conversations:
if ( conversationsLeft . Count > 0 )
{
var enumerator = conversationsLeft . GetEnumerator ( ) ;
while ( enumerator . MoveNext ( ) )
{
var conversationID = enumerator . Current ;
var conversation = DialogueManager . MasterDatabase . GetConversation ( conversationID ) ;
if ( conversation = = null ) continue ;
#if SAFE_SIMSTATUS
if ( DialogueDebug . logInfo ) Debug . Log ( "DEBUG: Add SimStatus for new conversation [" + conversationID + "]: " + conversation . Title ) ;
#endif
DialogueLua . AddToConversationTable ( conversationTable , conversation , true ) ;
}
}
}
/// <summary>
/// Expands SimX for a conversation.
/// </summary>
private static bool ExpandSimStatusForConversation ( StringBuilder sb , int conversationID , string conversationIDString , Language . Lua . LuaTable fieldTable , Language . Lua . LuaString luaStringSimX , Dictionary < int , DialogueEntry > dialogueEntryCache )
{
// Find our Lua Dialog[] table and conversation asset:
var dialogTable = fieldTable . GetValue ( "Dialog" ) as Language . Lua . LuaTable ;
if ( dialogTable = = null )
{
dialogTable = new Language . Lua . LuaTable ( ) ;
fieldTable . AddRaw ( "Dialog" , dialogTable ) ;
}
dialogTable . List . Clear ( ) ;
dialogTable . Dict . Clear ( ) ;
var conversation = DialogueManager . MasterDatabase . GetConversation ( conversationID ) ;
if ( conversation = = null ) return false ;
// Get the compressed SimStatus string:
string simX ;
if ( useConversationID )
{
var simXLuaValue = fieldTable . GetValue ( luaStringSimX ) ;
if ( simXLuaValue = = null ) return false ;
simX = simXLuaValue . ToString ( ) ;
sb . AppendFormat ( "Conversation[{0}].SimX=nil;" , conversationIDString ) ;
}
else
{
var fieldValue = DialogueLua . StringToTableIndex ( conversation . LookupValue ( saveConversationSimStatusWithField ) ) ;
if ( string . IsNullOrEmpty ( fieldValue ) ) fieldValue = conversation . id . ToString ( ) ;
simX = Lua . Run ( "return Variable[\"Conversation_SimX_" + fieldValue + "\"]" ) . AsString ;
sb . Append ( "Variable[\"Conversation_SimX_" + fieldValue + "\"]=nil;" ) ;
}
if ( string . IsNullOrEmpty ( simX ) | | string . Equals ( simX , "nil" ) ) return false ;
var simXFields = simX . Split ( ';' ) ;
var numFields = simXFields . Length / 2 ;
// Index dialogue entries by ID: (don't worry about unused old entries; this conversation shouldn't reference them)
DialogueEntry entry ;
for ( int i = 0 ; i < conversation . dialogueEntries . Count ; i + + )
{
entry = conversation . dialogueEntries [ i ] ;
dialogueEntryCache [ entry . id ] = entry ;
}
// Make table of SimStatus fields to entry IDs.
Dictionary < string , int > simStatusFieldValueToID = null ;
if ( ! useEntryID )
{
simStatusFieldValueToID = new Dictionary < string , int > ( ) ;
for ( int i = 0 ; i < conversation . dialogueEntries . Count ; i + + )
{
entry = conversation . dialogueEntries [ i ] ;
var field = ( entry ! = null ) ? Field . Lookup ( entry . fields , saveDialogueEntrySimStatusWithField ) : null ;
simStatusFieldValueToID [ ( field ! = null ) ? field . value : entry . id . ToString ( ) ] = entry . id ;
}
}
// Iterate through fields of compressed SimStatus string:
for ( int i = 0 ; i < numFields ; i + + )
{
var simXEntryIDValue = simXFields [ 2 * i ] ;
var simStatus = CharToSimStatus ( simXFields [ ( 2 * i ) + 1 ] [ 0 ] ) ;
var simStatusTable = new Language . Lua . LuaTable ( ) ;
simStatusTable . AddRaw ( DialogueLua . SimStatus , new Language . Lua . LuaString ( simStatus ) ) ;
if ( ! useEntryID & & ! simStatusFieldValueToID . ContainsKey ( simXEntryIDValue ) ) continue ;
var entryID = useEntryID ? Tools . StringToInt ( simXEntryIDValue ) : simStatusFieldValueToID [ simXEntryIDValue ] ;
dialogueEntryCache [ entryID ] = null ; // Mark that SimStatus has been added for this entry.
if ( useEntryID )
{
dialogTable . AddRaw ( entryID , simStatusTable ) ;
}
else
{
if ( simStatusFieldValueToID . ContainsKey ( simXEntryIDValue ) )
{
dialogTable . AddRaw ( simStatusFieldValueToID [ simXEntryIDValue ] , simStatusTable ) ;
}
}
}
// Backfill any new entries that weren't included in the compressed SimStatus string:
for ( int i = 0 ; i < conversation . dialogueEntries . Count ; i + + )
{
entry = conversation . dialogueEntries [ i ] ;
if ( dialogueEntryCache [ entry . id ] ! = null )
{
#if SAFE_SIMSTATUS
if ( DialogueDebug . logInfo ) Debug . Log ( "DEBUG: Adding SimStatus for new entry [" + entry . id + "] in existing conversation [" + conversation . id + "]: " + conversation . Title ) ;
#endif
// Missing. Need to add:
var simStatusTable = new Language . Lua . LuaTable ( ) ;
simStatusTable . AddRaw ( DialogueLua . SimStatus , new Language . Lua . LuaString ( DialogueLua . Untouched ) ) ;
dialogTable . AddRaw ( entry . id , simStatusTable ) ;
}
}
return true ;
}
#endif
private static char SimStatusToChar ( string simStatus )
{
switch ( simStatus )
{
default :
return 'X' ;
case DialogueLua . Untouched :
return 'u' ;
case DialogueLua . WasDisplayed :
return 'd' ;
case DialogueLua . WasOffered :
return 'o' ;
}
}
private static string CharToSimStatus ( char c )
{
switch ( c )
{
default :
return "ERROR" ;
case 'u' :
return DialogueLua . Untouched ;
case 'd' :
return DialogueLua . WasDisplayed ;
case 'o' :
return DialogueLua . WasOffered ;
}
}
#endregion
#region Initialize New Fields After Load
/// <summary>
/// Instructs the Dialogue System to add any missing variables that are in the master
/// database but not in Lua.
/// </summary>
public static void InitializeNewVariablesFromDatabase ( )
{
try
{
LuaTableWrapper variableTable = Lua . Run ( "return Variable" ) . AsTable ;
if ( variableTable = = null )
{
if ( DialogueDebug . LogErrors ) Debug . LogError ( string . Format ( "{0}: Persistent Data Manager couldn't access Lua Variable[] table" , new System . Object [ ] { DialogueDebug . Prefix } ) ) ;
return ;
}
var database = DialogueManager . MasterDatabase ;
if ( database = = null ) return ;
var inLua = new HashSet < string > ( variableTable . Keys ) ;
for ( int i = 0 ; i < database . variables . Count ; i + + )
{
var variable = database . variables [ i ] ;
var variableName = variable . Name ;
var variableIndex = DialogueLua . StringToTableIndex ( variableName ) ;
if ( ! inLua . Contains ( variableIndex ) )
{
switch ( variable . Type )
{
case FieldType . Boolean :
DialogueLua . SetVariable ( variableName , variable . InitialBoolValue ) ;
break ;
case FieldType . Actor :
case FieldType . Item :
case FieldType . Location :
case FieldType . Number :
DialogueLua . SetVariable ( variableName , variable . InitialFloatValue ) ;
break ;
default :
DialogueLua . SetVariable ( variableName , variable . InitialValue ) ;
break ;
}
}
}
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: InitializeNewVariablesFromDatabase() failed to get variable data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
}
#if USE_NLUA
/// <summary>
/// Adds any new actors or actor fields that were not in the saved data.
/// </summary>
public static void InitializeNewActorFieldsFromDatabase ( )
{
try
{
var database = DialogueManager . MasterDatabase ;
if ( database = = null ) return ;
var actorTable = Lua . Run ( "return Actor" ) . asTable ;
if ( actorTable = = null | | ! actorTable . IsValid ) throw new System . Exception ( "Internal error: Can't access Actor table" ) ;
for ( int i = 0 ; i < database . actors . Count ; i + + )
{
var dbActor = database . actors [ i ] ;
var actorName = dbActor . Name ;
var actorNameTableIndex = DialogueLua . StringToTableIndex ( actorName ) ;
var actorRecord = Lua . Run ( "return Actor[\"" + actorNameTableIndex + "\"]" ) ;
if ( ! actorRecord . isTable )
{
// This is a new actor not in the save data. Add it:
var newActorLuaCode = "Actor[\"" + actorNameTableIndex + "\"] = {" ;
for ( int j = 0 ; j < dbActor . fields . Count ; j + + )
{
var field = dbActor . fields [ j ] ;
var fieldIndex = DialogueLua . StringToFieldName ( field . title ) ;
newActorLuaCode + = fieldIndex + DialogueLua . FieldValueAsString ( field ) + ", " ;
}
newActorLuaCode + = "}" ;
Lua . Run ( newActorLuaCode ) ;
}
else
{
// Existing actor. Add any missing fields:
var fieldTable = actorRecord . asTable ;
if ( fieldTable = = null ) continue ;
var existingFields = new HashSet < string > ( fieldTable . keys ) ;
for ( int j = 0 ; j < dbActor . fields . Count ; j + + )
{
var field = dbActor . fields [ j ] ;
var fieldTableIndex = DialogueLua . StringToFieldName ( field . title ) ;
if ( ! existingFields . Contains ( fieldTableIndex ) )
{
Lua . Run ( "Actor[\"" + actorNameTableIndex + "\"]." + fieldTableIndex + " = " + DialogueLua . FieldValueAsString ( field ) ) ;
}
}
}
}
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: InitializeNewActorFieldsFromDatabase() failed to get actor data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
}
#else
/// <summary>
/// Adds any new actors or actor fields that were not in the saved data.
/// </summary>
public static void InitializeNewActorFieldsFromDatabase ( )
{
try
{
var database = DialogueManager . MasterDatabase ;
if ( database = = null ) return ;
var actorTable = Lua . Run ( "return Actor" ) . AsTable ;
if ( actorTable = = null | | ! actorTable . IsValid ) throw new System . Exception ( "Internal error: Can't access Actor table" ) ;
for ( int i = 0 ; i < database . actors . Count ; i + + )
{
var dbActor = database . actors [ i ] ;
var actorName = dbActor . Name ;
var actorNameTableIndex = DialogueLua . StringToTableIndex ( actorName ) ;
var fieldTable = actorTable . luaTable . GetValue ( actorNameTableIndex ) as Language . Lua . LuaTable ;
if ( fieldTable = = null )
{
// This is a new actor not in the save data. Add it:
fieldTable = new Language . Lua . LuaTable ( ) ;
for ( int j = 0 ; j < dbActor . fields . Count ; j + + )
{
var field = dbActor . fields [ j ] ;
var fieldIndex = DialogueLua . StringToFieldName ( field . title ) ;
fieldTable . AddRaw ( fieldIndex , DialogueLua . GetFieldLuaValue ( field ) ) ;
}
actorTable . luaTable . AddRaw ( actorNameTableIndex , fieldTable ) ;
}
else
{
// Existing actor. Add any missing fields:
var existingFields = new HashSet < string > ( ) ;
foreach ( var key in fieldTable . Keys )
{
existingFields . Add ( key . ToString ( ) ) ;
}
for ( int j = 0 ; j < dbActor . fields . Count ; j + + )
{
var field = dbActor . fields [ j ] ;
var fieldTableIndex = DialogueLua . StringToFieldName ( field . title ) ;
if ( ! existingFields . Contains ( fieldTableIndex ) )
{
var fieldValue = DialogueLua . GetFieldLuaValue ( field ) ;
fieldTable . AddRaw ( fieldTableIndex , fieldValue ) ;
}
}
}
}
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: InitializeNewActorFieldsFromDatabase() failed to get actor data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
}
#endif
/// <summary>
/// Instructs the Dialogue System to add any missing quests and entries that are in the master
/// database but not in Lua.
/// </summary>
public static void InitializeNewQuestEntriesFromDatabase ( )
{
try
{
var luaCode = string . Empty ;
var database = DialogueManager . MasterDatabase ;
if ( database = = null ) return ;
for ( int i = 0 ; i < database . items . Count ; i + + )
{
if ( database . items [ i ] . IsItem ) continue ;
var dbQuest = database . items [ i ] ;
var questName = dbQuest . Name ;
var questNameTableIndex = DialogueLua . StringToTableIndex ( questName ) ;
// Add any missing quests:
if ( ! DialogueLua . DoesTableElementExist ( "Item" , questName ) )
{
var questCode = string . Empty ;
questCode = "Item[\"" + DialogueLua . StringToTableIndex ( questName ) + "\"] = {{" ;
for ( int j = 0 ; j < dbQuest . fields . Count ; j + + )
{
var field = dbQuest . fields [ j ] ;
questCode + = DialogueLua . StringToFieldName ( field . title ) + "=" +
DialogueLua . ValueAsString ( field . type , field . value ) + ", " ;
}
questCode + = "}}; " ;
luaCode + = questCode ;
}
// Add any missing entries:
var dbEntryCount = dbQuest . LookupInt ( "Entry Count" ) ;
var luaEntryCount = DialogueLua . GetQuestField ( questName , "Entry Count" ) . AsInt ;
if ( luaEntryCount < dbEntryCount )
{
luaCode + = "Item[\"" + questNameTableIndex + "\"].Entry_Count=" + dbEntryCount + "; " ;
for ( int j = 0 ; j < dbQuest . fields . Count ; j + + )
{
var field = dbQuest . fields [ j ] ;
if ( field . title . StartsWith ( "Entry " ) & & ! field . title . EndsWith ( " Count" ) )
{
luaCode + = "Item[\"" + questNameTableIndex + "\"]." +
DialogueLua . StringToFieldName ( field . title ) + " = " +
DialogueLua . ValueAsString ( field . type , field . value ) + "; " ;
}
}
}
Lua . Run ( luaCode ) ;
}
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: InitializeNewQuestEntriesFromDatabase() failed to get quest data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
}
/// <summary>
/// Initializes SimStatus for entries that were added to the database after the saved game.
/// </summary>
public static void InitializeNewSimStatusFromDatabase ( )
{
// For LuaInterpreter, ExpandSimStatusForConversation also initializes new SimStatus,
// so we only need this for NLua:
#if NLUA | | SAFE_SIMSTATUS
if ( ! ( includeSimStatus & & initializeNewSimStatus ) ) return ;
try
{
var database = DialogueManager . MasterDatabase ;
if ( database = = null ) return ;
var missingConversations = new List < Conversation > ( ) ;
var fakeLoadedDatabases = new List < DialogueDatabase > ( ) ;
var luaCode = string . Empty ;
for ( int i = 0 ; i < database . conversations . Count ; i + + )
{
var conversation = database . conversations [ i ] ;
var convTableIdent = "Conversation[" + conversation . id + "]" ;
var convTable = Lua . Run ( "return " + convTableIdent ) . AsTable ;
if ( ! convTable . IsValid )
{
missingConversations . Add ( conversation ) ;
}
else
{
for ( int j = 0 ; j < conversation . dialogueEntries . Count ; j + + )
{
var entry = conversation . dialogueEntries [ j ] ;
var dialogTableIdent = convTableIdent + ".Dialog[" + entry . id + "]" ;
var entryTable = Lua . Run ( "return " + dialogTableIdent ) . AsTable ;
if ( ! entryTable . IsValid )
{
luaCode + = dialogTableIdent + "={SimStatus=\"Untouched\"}; " ;
}
}
}
}
Lua . Run ( luaCode , DialogueDebug . LogInfo ) ;
DialogueLua . AddToConversationTable ( missingConversations , fakeLoadedDatabases ) ;
}
catch ( System . Exception e )
{
Debug . LogError ( "Dialogue System: InitializeNewSimStatusFromDatabase() failed: " + e . Message ) ;
}
#endif
}
#endregion
#region Async Saving
//======================================================================
// Asynchronous saving:
public static int asyncGameObjectBatchSize = 1000 ;
public static int asyncDialogueEntryBatchSize = 100 ;
public class AsyncSaveOperation
{
public bool isDone = false ;
public string content = string . Empty ;
}
public static AsyncSaveOperation GetSaveDataAsync ( )
{
var asyncOp = new AsyncSaveOperation ( ) ;
DialogueManager . Instance . StartCoroutine ( GetSaveDataAsyncCoroutine ( asyncOp ) ) ;
return asyncOp ;
}
private static IEnumerator GetSaveDataAsyncCoroutine ( AsyncSaveOperation asyncOp )
{
if ( DialogueDebug . LogInfo ) Debug . Log ( string . Format ( "{0}: Saving data asynchronously..." , new System . Object [ ] { DialogueDebug . Prefix , asyncGameObjectBatchSize } ) ) ;
switch ( recordPersistentDataOn )
{
case RecordPersistentDataOn . AllGameObjects :
yield return DialogueManager . Instance . StartCoroutine ( Tools . SendMessageToEveryoneAsync ( "OnRecordPersistentData" , asyncGameObjectBatchSize ) ) ;
break ;
case RecordPersistentDataOn . OnlyRegisteredGameObjects :
int count = 0 ;
foreach ( var go in listeners )
{
if ( go ! = null )
{
go . SendMessage ( "OnRecordPersistentData" , SendMessageOptions . DontRequireReceiver ) ;
count + + ;
if ( count > asyncGameObjectBatchSize )
{
count = 0 ;
yield return null ;
}
}
}
break ;
default :
break ;
}
StringBuilder sb = new StringBuilder ( ) ;
AppendVariableData ( sb ) ;
yield return null ;
AppendItemData ( sb ) ;
yield return null ;
AppendLocationData ( sb ) ;
yield return null ;
if ( includeActorData ) AppendActorData ( sb ) ;
yield return null ;
yield return DialogueManager . Instance . StartCoroutine ( AppendConversationDataAsync ( sb ) ) ;
yield return null ;
if ( includeRelationshipAndStatusData ) AppendRelationshipAndStatusTables ( sb ) ;
if ( GetCustomSaveData ! = null ) sb . Append ( GetCustomSaveData ( ) ) ;
string saveData = sb . ToString ( ) ;
if ( DialogueDebug . LogInfo ) Debug . Log ( string . Format ( "{0}: Saved data asynchronously: {1}" , new System . Object [ ] { DialogueDebug . Prefix , saveData } ) ) ;
asyncOp . content = saveData ;
asyncOp . isDone = true ;
}
/// <summary>
/// Sends the OnRecordPersistentData message to all game objects in the scene to give them
/// an opportunity to record their state in the Lua environment. Runs in batches specified
/// by the value of asyncGameObjectBatchSize.
/// </summary>
public static void RecordAsync ( )
{
if ( DialogueDebug . LogInfo ) Debug . Log ( string . Format ( "{0}: Recording persistent data to Lua environment in batches of {1} GameObjects." , new System . Object [ ] { DialogueDebug . Prefix , asyncGameObjectBatchSize } ) ) ;
DialogueManager . Instance . StartCoroutine ( Tools . SendMessageToEveryoneAsync ( "OnRecordPersistentData" , asyncGameObjectBatchSize ) ) ;
}
private static IEnumerator AppendConversationDataAsync ( StringBuilder sb )
{
if ( includeAllConversationFields | | DialogueManager . Instance . persistentDataSettings . includeAllConversationFields )
{
AppendAllConversationFields ( sb ) ;
}
if ( includeSimStatus & & DialogueManager . Instance . includeSimStatus )
{
var count = 0 ;
#if USE_NLUA
var useConversationID = string . IsNullOrEmpty ( saveConversationSimStatusWithField ) ;
var useEntryID = string . IsNullOrEmpty ( saveDialogueEntrySimStatusWithField ) ;
foreach ( var conversation in DialogueManager . MasterDatabase . conversations )
{
if ( useConversationID )
{
sb . AppendFormat ( "Conversation[{0}].SimX=\"" , conversation . id ) ;
}
else
{
var fieldValue = DialogueLua . StringToTableIndex ( conversation . LookupValue ( saveConversationSimStatusWithField ) ) ;
if ( string . IsNullOrEmpty ( fieldValue ) ) fieldValue = conversation . id . ToString ( ) ;
sb . AppendFormat ( "Variable[\"Conversation_SimX_{0}\"]=\"" , fieldValue ) ;
}
var dialogTable = Lua . Run ( "return Conversation[" + conversation . id + "].Dialog" ) . asTable ;
var first = true ;
for ( int i = 0 ; i < conversation . dialogueEntries . Count ; i + + )
{
try
{
var entry = conversation . dialogueEntries [ i ] ;
var entryID = entry . id ;
var dialogFields = dialogTable [ entryID ] as NLua . LuaTable ;
if ( dialogFields ! = null )
{
if ( ! first ) sb . Append ( ";" ) ;
first = false ;
sb . Append ( useEntryID ? entryID . ToString ( ) : Field . LookupValue ( entry . fields , saveDialogueEntrySimStatusWithField ) ) ;
sb . Append ( ";" ) ;
var simStatus = dialogFields [ DialogueLua . SimStatus ] . ToString ( ) ;
sb . Append ( SimStatusToChar ( simStatus ) ) ;
}
}
catch ( System . Exception e )
{
Debug . LogError ( string . Format ( "{0}: GetSaveData() failed to get conversation data: {1}" , new System . Object [ ] { DialogueDebug . Prefix , e . Message } ) ) ;
}
count + + ;
if ( count > = asyncDialogueEntryBatchSize )
{
count = 0 ;
yield return null ;
}
}
sb . Append ( "\"; " ) ;
}
#else
useConversationID = string . IsNullOrEmpty ( saveConversationSimStatusWithField ) ;
useEntryID = string . IsNullOrEmpty ( saveDialogueEntrySimStatusWithField ) ;
var conversationTable = Lua . Environment . GetValue ( "Conversation" ) as Language . Lua . LuaTable ;
if ( conversationTable = = null ) yield break ;
for ( int i = 0 ; i < conversationTable . List . Count ; i + + )
{
var conversationID = i + 1 ;
var fieldTable = conversationTable . List [ i ] as Language . Lua . LuaTable ;
count + = AppendSimStatusForConversation ( sb , conversationTable , conversationID , fieldTable ) ;
if ( count > = asyncDialogueEntryBatchSize )
{
count = 0 ;
yield return null ;
}
}
foreach ( var kvp in conversationTable . Dict )
{
if ( kvp . Key = = null | | kvp . Value = = null | | ! ( kvp . Value is Language . Lua . LuaTable ) ) continue ;
var conversationID = Tools . StringToInt ( kvp . Key . ToString ( ) ) ;
var fieldTable = kvp . Value as Language . Lua . LuaTable ;
count + = AppendSimStatusForConversation ( sb , conversationTable , conversationID , fieldTable ) ;
if ( count > = asyncDialogueEntryBatchSize )
{
count = 0 ;
yield return null ;
}
}
#endif
}
}
#endregion
#region Raw Dump
#if USE_NLUA
// Note: NLua doesn't implement raw dump. It just does a passthrough to the regular save technique.
public static byte [ ] GetRawData ( )
{
return Encoding . UTF8 . GetBytes ( GetSaveData ( ) ) ;
}
public static void ApplyRawData ( byte [ ] bytes )
{
string s = Encoding . UTF8 . GetString ( bytes ) ;
ApplySaveData ( s ) ;
}
#else
// Note: Raw dump is only implemented for LuaInterpreter (the default Lua implementation).
public class AsyncRawDataOperation
{
public bool isDone = false ;
public byte [ ] content = null ;
}
public static byte [ ] GetRawData ( )
{
Record ( ) ;
using ( var ms = new MemoryStream ( ) )
{
var writer = new BinaryWriter ( ms ) ;
var conversationTable = Lua . Run ( "return Conversation" ) . AsTable . luaTable ;
PrepSimStatusForRawData ( conversationTable ) ;
WriteValue ( writer , Lua . Run ( "return Actor" ) . AsTable . luaTable ) ;
WriteValue ( writer , Lua . Run ( "return Item" ) . AsTable . luaTable ) ;
WriteValue ( writer , Lua . Run ( "return Location" ) . AsTable . luaTable ) ;
WriteValue ( writer , Lua . Run ( "return Variable" ) . AsTable . luaTable ) ;
WriteValue ( writer , conversationTable ) ;
WriteExtraData ( writer ) ;
writer . Flush ( ) ;
return ms . GetBuffer ( ) ;
}
}
public static AsyncRawDataOperation GetRawDataAsync ( )
{
var asyncOp = new AsyncRawDataOperation ( ) ;
DialogueManager . Instance . StartCoroutine ( GetRawDataAsyncCoroutine ( asyncOp ) ) ;
return asyncOp ;
}
private static IEnumerator GetRawDataAsyncCoroutine ( AsyncRawDataOperation asyncOp )
{
if ( DialogueDebug . LogInfo ) Debug . Log ( "Dialogue System: Saving raw Lua data asynchronously..." ) ;
// Record persistent data objects async:
switch ( recordPersistentDataOn )
{
case RecordPersistentDataOn . AllGameObjects :
yield return DialogueManager . Instance . StartCoroutine ( Tools . SendMessageToEveryoneAsync ( "OnRecordPersistentData" , asyncGameObjectBatchSize ) ) ;
break ;
case RecordPersistentDataOn . OnlyRegisteredGameObjects :
int count = 0 ;
foreach ( var go in listeners )
{
if ( go ! = null )
{
go . SendMessage ( "OnRecordPersistentData" , SendMessageOptions . DontRequireReceiver ) ;
count + + ;
if ( count > asyncGameObjectBatchSize )
{
count = 0 ;
yield return null ;
}
}
}
break ;
default :
break ;
}
using ( var ms = new MemoryStream ( ) )
{
var writer = new BinaryWriter ( ms ) ;
var conversationTable = Lua . Run ( "return Conversation" ) . AsTable . luaTable ;
yield return DialogueManager . Instance . StartCoroutine ( PrepSimStatusForRawDataAsync ( conversationTable ) ) ;
WriteValue ( writer , Lua . Run ( "return Actor" ) . AsTable . luaTable ) ;
yield return null ;
WriteValue ( writer , Lua . Run ( "return Item" ) . AsTable . luaTable ) ;
yield return null ;
WriteValue ( writer , Lua . Run ( "return Location" ) . AsTable . luaTable ) ;
yield return null ;
WriteValue ( writer , Lua . Run ( "return Variable" ) . AsTable . luaTable ) ;
yield return null ;
WriteValue ( writer , conversationTable ) ;
yield return null ;
WriteExtraData ( writer ) ;
writer . Flush ( ) ;
asyncOp . content = ms . GetBuffer ( ) ;
}
asyncOp . isDone = true ;
}
private static void WriteValue ( BinaryWriter writer , Language . Lua . LuaValue value )
{
if ( value is Language . Lua . LuaTable )
{
WriteTable ( writer , value as Language . Lua . LuaTable ) ;
}
else if ( value is Language . Lua . LuaString )
{
writer . Write ( 'S' ) ;
writer . Write ( ( value as Language . Lua . LuaString ) . Text ) ;
}
else if ( value is Language . Lua . LuaNumber )
{
writer . Write ( 'N' ) ;
writer . Write ( ( value as Language . Lua . LuaNumber ) . Number ) ;
}
else if ( value is Language . Lua . LuaBoolean )
{
writer . Write ( 'B' ) ;
writer . Write ( ( value as Language . Lua . LuaBoolean ) . BoolValue ) ;
}
else if ( value is Language . Lua . LuaNil )
{
writer . Write ( 'X' ) ;
}
else
{
Debug . LogError ( "WriteValue unhandled " + value . GetType ( ) . Name + ": " + value . ToString ( ) ) ;
}
}
private static void WriteTable ( BinaryWriter writer , Language . Lua . LuaTable table )
{
writer . Write ( 'T' ) ;
if ( table . List = = null )
{
writer . Write ( ( int ) 0 ) ;
}
else
{
writer . Write ( table . List . Count ) ;
for ( int i = 0 ; i < table . List . Count ; i + + )
{
WriteValue ( writer , table . List [ i ] ) ;
}
}
if ( table . Dict = = null )
{
writer . Write ( ( int ) 0 ) ;
}
else
{
writer . Write ( table . Dict . Count ) ;
var enumerator = table . Dict . GetEnumerator ( ) ; // Enumerates manually to avoid garbage.
while ( enumerator . MoveNext ( ) )
{
WriteValue ( writer , enumerator . Current . Key ) ;
WriteValue ( writer , enumerator . Current . Value ) ;
}
}
}
public static void ApplyRawData ( byte [ ] bytes )
{
using ( var ms = new MemoryStream ( bytes ) )
{
using ( var reader = new BinaryReader ( ms ) )
{
Lua . Run ( "Actor = {}; Item = {}; Location = {}; Variable = {}; Conversation = {}" ) ;
ReadTable ( reader , Lua . Run ( "return Actor" ) . AsTable . luaTable ) ;
ReadTable ( reader , Lua . Run ( "return Item" ) . AsTable . luaTable ) ;
ReadTable ( reader , Lua . Run ( "return Location" ) . AsTable . luaTable ) ;
ReadTable ( reader , Lua . Run ( "return Variable" ) . AsTable . luaTable ) ;
ReadTable ( reader , Lua . Run ( "return Conversation" ) . AsTable . luaTable ) ;
ApplySimStatusFromRawData ( ) ;
ApplyExtraData ( reader ) ;
RefreshRelationshipAndStatusTablesFromLua ( ) ;
if ( initializeNewVariables )
{
InitializeNewVariablesFromDatabase ( ) ;
InitializeNewQuestEntriesFromDatabase ( ) ;
// Do not need this. It's done implicitly when expanding SimX: InitializeNewSimStatusFromDatabase();
}
}
}
Apply ( ) ;
}
private static Language . Lua . LuaValue ReadValue ( BinaryReader reader )
{
if ( ( char ) reader . PeekChar ( ) = = 'T' )
{
var luaTable = new Language . Lua . LuaTable ( ) ;
ReadTable ( reader , luaTable ) ;
return luaTable ;
}
var typeChar = reader . ReadChar ( ) ;
if ( typeChar = = 'S' )
{
var s = reader . ReadString ( ) ;
return new Language . Lua . LuaString ( s ) ;
}
else if ( typeChar = = 'N' )
{
var n = reader . ReadDouble ( ) ;
return new Language . Lua . LuaNumber ( n ) ;
}
else if ( typeChar = = 'B' )
{
var b = reader . ReadBoolean ( ) ;
return ( b = = true ) ? Language . Lua . LuaBoolean . True : Language . Lua . LuaBoolean . False ;
}
else if ( typeChar = = 'X' )
{
return Language . Lua . LuaNil . Nil ;
}
else
{
Debug . LogError ( "ReadValue unhandled type code " + typeChar ) ;
return Language . Lua . LuaNil . Nil ;
}
}
private static void ReadTable ( BinaryReader reader , Language . Lua . LuaTable table )
{
reader . Read ( ) ; // 'T'
int listLength = reader . ReadInt32 ( ) ;
for ( int i = 0 ; i < listLength ; i + + )
{
var value = ReadValue ( reader ) ;
table . List . Add ( value ) ;
}
int dictLength = reader . ReadInt32 ( ) ;
for ( int i = 0 ; i < dictLength ; i + + )
{
var key = ReadValue ( reader ) ;
var value = ReadValue ( reader ) ;
table . Dict . Add ( key , value ) ;
}
}
// Save relationship & status tables, and custom save data delegate.
private static void WriteExtraData ( BinaryWriter writer )
{
if ( includeRelationshipAndStatusData )
{
var sb = new StringBuilder ( ) ;
AppendRelationshipAndStatusTables ( sb ) ;
writer . Write ( sb . ToString ( ) ) ;
}
if ( GetCustomSaveData ! = null )
{
writer . Write ( GetCustomSaveData ( ) ) ;
}
}
private static void ApplyExtraData ( BinaryReader reader )
{
if ( includeRelationshipAndStatusData )
{
Lua . Run ( reader . ReadString ( ) ) ;
}
if ( GetCustomSaveData ! = null )
{
Lua . Run ( reader . ReadString ( ) , DialogueDebug . LogInfo ) ;
}
}
// If saveConversationSimStatusWithField or saveDialogueEntrySimStatusWithField are set,
// copy the current SimStatus into the specified variables/fields.
private static void PrepSimStatusForRawData ( Language . Lua . LuaTable conversationTable )
{
// Only need to do if saving to fields:
if ( ! ( includeSimStatus & & DialogueManager . Instance . includeSimStatus & & conversationTable ! = null ) ) return ;
useConversationID = string . IsNullOrEmpty ( saveConversationSimStatusWithField ) ;
useEntryID = string . IsNullOrEmpty ( saveDialogueEntrySimStatusWithField ) ;
if ( useConversationID & & useEntryID ) return ;
// Reuse these vars to reduce GC:
var dialogueEntryCache = new Dictionary < int , DialogueEntry > ( ) ;
var sb = new StringBuilder ( 16384 , System . Int32 . MaxValue ) ;
for ( int i = 0 ; i < conversationTable . List . Count ; i + + )
{
var conversationID = i + 1 ;
var fieldTable = conversationTable . List [ i ] as Language . Lua . LuaTable ;
PrepConversationSimStatusForRawData ( conversationTable , conversationID , fieldTable , dialogueEntryCache , sb ) ;
}
foreach ( var kvp in conversationTable . Dict )
{
if ( kvp . Key = = null | | kvp . Value = = null | | ! ( kvp . Value is Language . Lua . LuaTable ) ) continue ;
var conversationID = Tools . StringToInt ( kvp . Key . ToString ( ) ) ;
var fieldTable = kvp . Value as Language . Lua . LuaTable ;
PrepConversationSimStatusForRawData ( conversationTable , conversationID , fieldTable , dialogueEntryCache , sb ) ;
}
}
// Async version:
private static IEnumerator PrepSimStatusForRawDataAsync ( Language . Lua . LuaTable conversationTable )
{
// Only need to do if saving to fields:
if ( ! ( includeSimStatus & & DialogueManager . Instance . includeSimStatus & & conversationTable ! = null ) ) yield break ;
useConversationID = string . IsNullOrEmpty ( saveConversationSimStatusWithField ) ;
useEntryID = string . IsNullOrEmpty ( saveDialogueEntrySimStatusWithField ) ;
if ( useConversationID & & useEntryID ) yield break ;
// Reuse these vars to reduce GC:
var dialogueEntryCache = new Dictionary < int , DialogueEntry > ( ) ;
var sb = new StringBuilder ( 16384 , System . Int32 . MaxValue ) ;
int numEntriesDone = 0 ;
for ( int i = 0 ; i < conversationTable . List . Count ; i + + )
{
var conversationID = i + 1 ;
var fieldTable = conversationTable . List [ i ] as Language . Lua . LuaTable ;
numEntriesDone + = PrepConversationSimStatusForRawData ( conversationTable , conversationID , fieldTable , dialogueEntryCache , sb ) ;
if ( numEntriesDone > = asyncDialogueEntryBatchSize )
{
numEntriesDone = 0 ;
yield return null ;
}
}
foreach ( var kvp in conversationTable . Dict )
{
if ( kvp . Key = = null | | kvp . Value = = null | | ! ( kvp . Value is Language . Lua . LuaTable ) ) continue ;
var conversationID = Tools . StringToInt ( kvp . Key . ToString ( ) ) ;
var fieldTable = kvp . Value as Language . Lua . LuaTable ;
numEntriesDone + = PrepConversationSimStatusForRawData ( conversationTable , conversationID , fieldTable , dialogueEntryCache , sb ) ;
if ( numEntriesDone > = asyncDialogueEntryBatchSize )
{
numEntriesDone = 0 ;
yield return null ;
}
}
}
private static int PrepConversationSimStatusForRawData ( Language . Lua . LuaTable conversationTable , int conversationID ,
Language . Lua . LuaTable fieldTable , Dictionary < int , DialogueEntry > dialogueEntryCache , StringBuilder sb )
{
if ( conversationTable = = null | | fieldTable = = null ) return 0 ;
var dialogTable = fieldTable . GetValue ( "Dialog" ) as Language . Lua . LuaTable ;
if ( dialogTable = = null ) return 0 ;
var conversation = DialogueManager . MasterDatabase . GetConversation ( conversationID ) ;
if ( conversation = = null ) return 0 ;
sb . Length = 0 ;
// Index dialogue entries by ID: (don't worry about unused old entries; this conversation shouldn't reference them)
DialogueEntry entry ;
for ( int i = 0 ; i < conversation . dialogueEntries . Count ; i + + )
{
entry = conversation . dialogueEntries [ i ] ;
dialogueEntryCache [ entry . id ] = entry ;
}
var first = true ;
// Handle Dialog table's List:
for ( int i = 0 ; i < dialogTable . List . Count ; i + + )
{
var entryID = i + 1 ;
var simStatusTable = dialogTable . List [ i ] as Language . Lua . LuaTable ;
if ( ! first ) sb . Append ( ";" ) ;
first = false ;
if ( ! useEntryID & & dialogueEntryCache . TryGetValue ( entryID , out entry ) )
{
sb . Append ( Field . LookupValue ( entry . fields , saveDialogueEntrySimStatusWithField ) ) ;
}
else
{
sb . Append ( entryID ) ;
}
sb . Append ( ";" ) ;
//--- Optimization since we know table only has one field. Was: var simStatus = simStatusTable.GetValue(DialogueLua.SimStatus).ToString();
var enumerator = simStatusTable . Dict . GetEnumerator ( ) ;
enumerator . MoveNext ( ) ;
var simStatus = enumerator . Current . Value . ToString ( ) ;
sb . Append ( SimStatusToChar ( simStatus ) ) ;
}
// Handle Dialog table's Dict:
foreach ( var kvp2 in dialogTable . KeyValuePairs )
{
var simStatusTable = kvp2 . Value as Language . Lua . LuaTable ;
if ( ! first ) sb . Append ( ";" ) ;
first = false ;
if ( ! useEntryID )
{
var entryID = ( kvp2 . Key is Language . Lua . LuaNumber ) ? ( int ) ( ( kvp2 . Key as Language . Lua . LuaNumber ) . Number ) : Tools . StringToInt ( kvp2 . Key . ToString ( ) ) ;
if ( dialogueEntryCache . TryGetValue ( entryID , out entry ) )
{
sb . Append ( Field . LookupValue ( entry . fields , saveDialogueEntrySimStatusWithField ) ) ;
}
else
{
sb . Append ( entryID ) ;
}
}
else
{
sb . Append ( kvp2 . Key . ToString ( ) ) ;
}
sb . Append ( ";" ) ;
//--- Optimization since we know table only has one field. Was: var simStatus = simStatusTable.GetValue(DialogueLua.SimStatus).ToString();
var enumerator = simStatusTable . Dict . GetEnumerator ( ) ;
enumerator . MoveNext ( ) ;
var simStatus = enumerator . Current . Value . ToString ( ) ;
sb . Append ( SimStatusToChar ( simStatus ) ) ;
}
if ( useConversationID )
{
Lua . Run ( "Conversation[" + conversationID + "].SimX=\"" + sb . ToString ( ) + "\"" ) ;
}
else
{
var fieldName = DialogueLua . StringToTableIndex ( conversation . LookupValue ( saveConversationSimStatusWithField ) ) ;
Lua . Run ( "Variable[\"Conversation_SimX_" + fieldName + "\"]=\"" + sb . ToString ( ) + "\"" ) ;
}
return conversation . dialogueEntries . Count ;
}
// If saveConversationSimStatusWithField or saveDialogueEntrySimStatusWithField are set,
// repopoulate SimStatus from the values in the specified variables/fields.
private static void ApplySimStatusFromRawData ( )
{
if ( includeSimStatus & & DialogueManager . Instance . includeSimStatus & &
( ! string . IsNullOrEmpty ( saveConversationSimStatusWithField ) | | ! string . IsNullOrEmpty ( saveDialogueEntrySimStatusWithField ) ) )
{
ExpandCompressedSimStatusData ( ) ;
}
}
#endif
#endregion
}
}