// Copyright (c) Pixel Crushers. All rights reserved. using UnityEngine; using System.Collections.Generic; using System.Text.RegularExpressions; namespace PixelCrushers.DialogueSystem { /// /// A dialogue database asset. A dialogue database is a collection of data records necessary /// to run dialogue, such as actors and conversations. This is a ScriptableObject so it can be /// edited, serialized, and assigned in Unity. /// public class DialogueDatabase : ScriptableObject { /// /// The version of the database, typically only used internally by the developer. /// public string version; /// /// The author, typically only used internally by the developer. /// public string author; /// /// The description of the database, typically only used internally by the developer. /// public string description; /// /// The global Lua user script. /// public string globalUserScript; /// /// The number of emphasis settings supported by Chat Mapper and the Dialogue System. /// public const int NumEmphasisSettings = 4; /// /// A Chat Mapper project defines four emphasis settings (text styles) for formatting lines /// of dialogue. This array contains those emphasis settings. /// public EmphasisSetting[] emphasisSettings = new EmphasisSetting[NumEmphasisSettings]; /// /// Assign IDs of assets (actors, items, conversations, etc.) from this base value. /// Useful for multiple databases. /// public int baseID = 1; /// /// The actors in the database. /// public List actors = new List(); /// /// The items in the database. You can use these to track in-game items, or to track quests using the QuestLog class. /// public List items = new List(); /// /// The locations in the database. /// public List locations = new List(); /// /// The variables in the database. /// public List variables = new List(); /// /// The conversations in the database. /// public List conversations = new List(); [System.Serializable] public class SyncInfo { public bool syncActors = false; public bool syncItems = false; public bool syncLocations = false; public bool syncVariables = false; public DialogueDatabase syncActorsDatabase = null; public DialogueDatabase syncItemsDatabase = null; public DialogueDatabase syncLocationsDatabase = null; public DialogueDatabase syncVariablesDatabase = null; } public void ResetEmphasisSettings() { emphasisSettings[0] = new EmphasisSetting(Color.white, false, false, false); emphasisSettings[1] = new EmphasisSetting(Color.red, false, false, false); emphasisSettings[2] = new EmphasisSetting(Color.green, false, false, false); emphasisSettings[3] = new EmphasisSetting(Color.blue, false, false, false); } public SyncInfo syncInfo = new SyncInfo(); /// /// Each database now stores a copy of its template. /// public string templateJson = string.Empty; // Cache dictionary by asset name to speed up searches: private Dictionary actorNameCache = null; private Dictionary itemNameCache = null; private Dictionary locationNameCache = null; private Dictionary variableNameCache = null; private Dictionary conversationTitleCache = null; /// /// Gets the ID of the first player character in the actor list. /// /// /// The player ID. /// public int playerID { get { Actor player = actors.Find(a => a.IsPlayer); return (player != null) ? player.id : 0; } } /// /// Determines whether an actor is a player character. /// /// /// true if actorID is the ID of a player; otherwise, false. /// /// /// An actor ID. /// public bool IsPlayerID(int actorID) { Actor actor = actors.Find(a => a.id == actorID); return (actor != null) ? actor.IsPlayer : false; } /// /// Determines whether an actor is a player character. /// /// true if the named actor is a player; otherwise, false. /// Actor name in the database. public bool IsPlayer(string actorName) { Actor actor = GetActor(actorName); return (actor != null) && actor.IsPlayer; } /// /// Gets the type of the character (PC or NPC) of an actor based on the actor's IsPlayer value. /// /// /// The character type (PC or NPC) /// /// /// The Actor ID to check. /// public CharacterType GetCharacterType(int actorID) { return IsPlayerID(actorID) ? CharacterType.PC : CharacterType.NPC; } #region Cache private void SetupCaches() { if (actorNameCache == null) actorNameCache = CreateCache(actors); if (itemNameCache == null) itemNameCache = CreateCache(items); if (locationNameCache == null) locationNameCache = CreateCache(locations); if (variableNameCache == null) variableNameCache = CreateCache(variables); if (conversationTitleCache == null) conversationTitleCache = CreateCache(conversations); } private Dictionary CreateCache(List assets) where T : Asset { var useTitle = typeof(T) == typeof(Conversation); var cache = new Dictionary(); if (Application.isPlaying) // Only build cache at runtime so Dialogue Editor doesn't have to worry about updating it. { for (int i = 0; i < assets.Count; i++) { var asset = assets[i]; var key = useTitle ? (asset as Conversation).Title : asset.Name; if (!cache.ContainsKey(key)) cache.Add(key, asset); } } return cache; } /// /// Dialogue databases maintain a quick lookup cache of assets by Name (by Title /// for conversations). If you change asset Names/Titles manually, call this /// method to sync the cache with the new values. /// public void ResetCache() { actorNameCache = null; itemNameCache = null; locationNameCache = null; variableNameCache = null; conversationTitleCache = null; } #endregion public delegate string GetCustomEntrytagDelegate(Conversation conversation, DialogueEntry entry); public static GetCustomEntrytagDelegate getCustomEntrytag = null; #if UNITY_2019_3_OR_NEWER && UNITY_EDITOR [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void InitStaticVariables() { getCustomEntrytag = null; } #endif /// /// Gets the actor by name. /// /// /// The actor. /// /// /// The actor's name (the value of the Name field). /// public Actor GetActor(string actorName) { if (string.IsNullOrEmpty(actorName)) return null; SetupCaches(); return actorNameCache.ContainsKey(actorName) ? actorNameCache[actorName] : actors.Find(a => string.Equals(a.Name, actorName)); } /// /// Gets the actor associated with an actor ID. /// /// /// The actor. /// /// /// The actor ID. /// public Actor GetActor(int id) { return actors.Find(a => a.id == id); } /// /// Gets the item by name. /// /// The item. /// The item's name (the value of the Name field). public Item GetItem(string itemName) { if (string.IsNullOrEmpty(itemName)) return null; SetupCaches(); return itemNameCache.ContainsKey(itemName) ? itemNameCache[itemName] : items.Find(i => string.Equals(i.Name, itemName)); } /// /// Gets the item associated with an item ID. /// /// /// The item. /// /// /// The item ID. /// public Item GetItem(int id) { return items.Find(i => i.id == id); } /// /// Gets the location by name. /// /// The location. /// The location's name (value of the Name field). public Location GetLocation(string locationName) { if (string.IsNullOrEmpty(locationName)) return null; SetupCaches(); return locationNameCache.ContainsKey(locationName) ? locationNameCache[locationName] : locations.Find(l => string.Equals(l.Name, locationName)); } /// /// Gets the location associated with a location ID. /// /// /// The location. /// /// /// The location ID. /// public Location GetLocation(int id) { return locations.Find(l => l.id == id); } /// /// Gets the variable by name. /// /// The variable. /// Variable name. public Variable GetVariable(string variableName) { if (string.IsNullOrEmpty(variableName)) return null; SetupCaches(); return variableNameCache.ContainsKey(variableName) ? variableNameCache[variableName] : variables.Find(v => string.Equals(v.Name, variableName)); } /// /// Gets the variable by ID. /// /// The variable. /// ID. public Variable GetVariable(int id) { return variables.Find(v => v.id == id); } /// /// Adds a Conversation to the database. /// /// /// The conversation to add. /// public void AddConversation(Conversation conversation) { SetupCaches(); var title = conversation.Title; //--- Removed for speed: if (DialogueDebug.logInfo) Debug.Log("Dialogue System: Add Conversation: " + title); if (!conversationTitleCache.ContainsKey(title)) conversationTitleCache.Add(title, conversation); conversations.Add(conversation); LinkUtility.SortOutgoingLinks(this, conversation); } /// /// Retrieves a Conversation by its Title field. /// /// /// The conversation matching the specified title. /// /// /// The title of the conversation. /// public Conversation GetConversation(string conversationTitle) { if (string.IsNullOrEmpty(conversationTitle)) return null; SetupCaches(); return conversationTitleCache.ContainsKey(conversationTitle) ? conversationTitleCache[conversationTitle] : conversations.Find(c => string.Equals(c.Title, conversationTitle)); } /// /// Retrieves a Conversation by its ID. /// /// /// The conversation with the specified ID. /// /// /// The Conversation ID. /// public Conversation GetConversation(int conversationID) { return conversations.Find(c => c.id == conversationID); } /// /// Gets a dialogue entry by its conversation and dialogue entry IDs. /// /// The dialogue entry. /// Conversation ID. /// Dialogue entry ID. public DialogueEntry GetDialogueEntry(int conversationID, int dialogueEntryID) { Conversation conversation = GetConversation(conversationID); return (conversation != null) ? conversation.GetDialogueEntry(dialogueEntryID) : null; } /// /// Follows a Link and returns the destination DialogueEntry. /// /// /// The dialogue entry that the link points to. /// /// /// The link to follow. /// public DialogueEntry GetDialogueEntry(Link link) { if (link != null) { Conversation conversation = GetConversation(link.destinationConversationID); if ((conversation != null) && (conversation.dialogueEntries != null)) { return conversation.dialogueEntries.Find(e => e.id == link.destinationDialogueID); } } return null; } /// /// Gets the text of the dialogue entry that a link points to. /// /// /// The text of the link's destination dialogue entry, or null if the link is invalid. /// /// /// The link to follow. /// public string GetLinkText(Link link) { DialogueEntry entry = GetDialogueEntry(link); return (entry == null) ? string.Empty : entry.responseButtonText; } /// /// Add the contents of another database to this database. /// /// /// The database to copy. /// public void Add(DialogueDatabase database) { if (database != null) { AddEmphasisSettings(database.emphasisSettings); AddGlobalUserScript(database); // Only add assets that aren't already in the database (as determined by name/ID as appropriate for the asset type): //--- Optimized to use cache: //foreach (var actor in database.actors) //{ // if (!Contains(this, actor)) actors.Add(actor); //} //foreach (var item in database.items) //{ // if (!Contains(this, item)) items.Add(item); //} //foreach (var location in database.locations) //{ // if (!Contains(this, location)) locations.Add(location); //} //foreach (var variable in database.variables) //{ // if (!Contains(this, variable)) variables.Add(variable); //} //foreach (var conversation in database.conversations) //{ // if (!Contains(this, conversation)) conversations.Add(conversation); //} SetupCaches(); AddAssets(actors, database.actors, actorNameCache); AddAssets(items, database.items, itemNameCache); AddAssets(locations, database.locations, locationNameCache); AddAssets(variables, database.variables, variableNameCache); AddAssets(conversations, database.conversations, conversationTitleCache); } } private void AddAssets(List myAssets, List assetsToAdd, Dictionary cache) where T : Asset { var useTitle = typeof(T) == typeof(Conversation); for (int i = 0; i < assetsToAdd.Count; i++) { var asset = assetsToAdd[i]; if (asset == null) continue; var key = useTitle ? (asset as Conversation).Title : asset.Name; if (key == null) { if (DialogueDebug.logWarnings) Debug.LogWarning($"Dialogue System: A {typeof(T).Name} has an invalid name."); continue; } if (!cache.ContainsKey(key)) { cache.Add(key, asset); myAssets.Add(asset); //--- Removed for speed: if (DialogueDebug.logInfo) Debug.Log("Dialogue System: Add " + typeof(T).Name + ": " + key); } } } private void AddEmphasisSettings(EmphasisSetting[] newEmphasisSettings) { if ((emphasisSettings == null) || (emphasisSettings.Length < NumEmphasisSettings)) { emphasisSettings = newEmphasisSettings; } else if (newEmphasisSettings != null) { emphasisSettings = new EmphasisSetting[newEmphasisSettings.Length]; for (int i = 0; i < newEmphasisSettings.Length; i++) { if ((emphasisSettings[i] == null) || (emphasisSettings[i].IsEmpty)) { emphasisSettings[i] = newEmphasisSettings[i]; } } } } private void AddGlobalUserScript(DialogueDatabase database) { if (!string.IsNullOrEmpty(globalUserScript)) { if (!string.IsNullOrEmpty(database.globalUserScript)) { globalUserScript = string.Format("{0}; {1}", new System.Object[] { globalUserScript, database.globalUserScript }); } } else if (!string.IsNullOrEmpty(database.globalUserScript)) { globalUserScript = database.globalUserScript; } } /// /// Removes from this database all content that's in the specified database. /// /// /// The database containing assets to remove from this database. /// public void Remove(DialogueDatabase database) { if (database != null) { //--- Optimized to use cache: //actors.RemoveAll(x => database.actors.Contains(x)); //items.RemoveAll(x => database.items.Contains(x)); //locations.RemoveAll(x => database.locations.Contains(x)); //variables.RemoveAll(x => database.variables.Contains(x)); //conversations.RemoveAll(x => database.conversations.Contains(x)); SetupCaches(); RemoveAssets(actors, database.actors, actorNameCache); RemoveAssets(items, database.items, itemNameCache); RemoveAssets(locations, database.locations, locationNameCache); RemoveAssets(variables, database.variables, variableNameCache); RemoveAssets(conversations, database.conversations, conversationTitleCache); } } /// /// Removes from this database all content that's in the specified database. /// /// /// The database containing assets to remove from this database. /// /// /// List of databases containing items that should be kept. /// public void Remove(DialogueDatabase database, List keep) { if (database != null) { //--- Optimized to use cache: //actors.RemoveAll(x => (database.actors.Contains(x) && !Contains(keep, x))); //items.RemoveAll(x => (database.items.Contains(x) && !Contains(keep, x))); //locations.RemoveAll(x => (database.locations.Contains(x) && !Contains(keep, x))); //variables.RemoveAll(x => (database.variables.Contains(x) && !Contains(keep, x))); //conversations.RemoveAll(x => (database.conversations.Contains(x) && !Contains(keep, x))); SetupCaches(); RemoveAssets(actors, database.actors, actorNameCache, keep); RemoveAssets(items, database.items, itemNameCache, keep); RemoveAssets(locations, database.locations, locationNameCache, keep); RemoveAssets(variables, database.variables, variableNameCache, keep); RemoveAssets(conversations, database.conversations, conversationTitleCache, keep); } } private void RemoveAssets(List myAssets, List assetsToRemove, Dictionary cache) where T : Asset { var useTitle = typeof(T) == typeof(Conversation); for (int i = 0; i < assetsToRemove.Count; i++) { var asset = assetsToRemove[i]; var key = useTitle ? (asset as Conversation).Title : asset.Name; if (cache.ContainsKey(key)) { myAssets.Remove(cache[key]); cache.Remove(key); } } } private void RemoveAssets(List myAssets, List assetsToRemove, Dictionary cache, List keep) where T : Asset { var useTitle = typeof(T) == typeof(Conversation); for (int i = 0; i < assetsToRemove.Count; i++) { var asset = assetsToRemove[i]; if (Contains(keep, asset)) continue; var key = useTitle ? (asset as Conversation).Title : asset.Name; if (cache.ContainsKey(key)) { myAssets.Remove(cache[key]); cache.Remove(key); } } } /// /// Removes all assets from this database. /// public void Clear() { actors.Clear(); items.Clear(); locations.Clear(); variables.Clear(); conversations.Clear(); ResetCache(); } #region Sync /// /// Syncs all assets from their source databases if assigned in syncInfo. /// public void SyncAll() { SyncActors(); SyncItems(); SyncLocations(); SyncVariables(); } /// /// Syncs the actors from syncInfo.syncActorsDatabase. /// public void SyncActors() { ResetCache(); if (!syncInfo.syncActors || syncInfo.syncActorsDatabase == null || syncInfo.syncActorsDatabase == this) return; // Identify actors in sync DB that have been renamed: var renamedActors = new Dictionary(); // Lua indices foreach (Actor sourceActor in syncInfo.syncActorsDatabase.actors) { var myActor = GetActor(sourceActor.id); if (myActor != null && myActor.Name != sourceActor.Name) { renamedActors.Add(DialogueLua.StringToTableIndex(myActor.Name), DialogueLua.StringToTableIndex(sourceActor.Name)); } } // Copy actors from sync DB to this DB, replacing those in this DB that have same ID: actors.RemoveAll(x => (syncInfo.syncActorsDatabase.GetActor(x.id) != null)); for (int i = 0; i < syncInfo.syncActorsDatabase.actors.Count; i++) { actors.Insert(i, new Actor(syncInfo.syncActorsDatabase.actors[i])); } // Update any renamed actors in dialogue entry Conditions and Scripts: foreach (Conversation conversation in conversations) { foreach (DialogueEntry entry in conversation.dialogueEntries) { foreach (var kvp in renamedActors) { var oldLuaIndex = kvp.Key; var newLuaIndex = kvp.Value; if (!string.IsNullOrEmpty(entry.conditionsString) && entry.conditionsString.Contains(oldLuaIndex)) { entry.conditionsString = ReplaceLuaIndex(entry.conditionsString, "Actor", oldLuaIndex, newLuaIndex); } if (!string.IsNullOrEmpty(entry.userScript) && entry.userScript.Contains(oldLuaIndex)) { entry.userScript = ReplaceLuaIndex(entry.userScript, "Actor", oldLuaIndex, newLuaIndex); } } } } } /// /// Syncs the items from syncInfo.syncItemsDatabase. /// public void SyncItems() { ResetCache(); if (!syncInfo.syncItems || syncInfo.syncItemsDatabase == null || syncInfo.syncItemsDatabase == this) return; // Identify items in sync DB that have been renamed: var renamedItems = new Dictionary(); // Lua indices foreach (Item sourceItem in syncInfo.syncItemsDatabase.items) { var myItem = GetItem(sourceItem.id); if (myItem != null && myItem.Name != sourceItem.Name) { renamedItems.Add(DialogueLua.StringToTableIndex(myItem.Name), DialogueLua.StringToTableIndex(sourceItem.Name)); } } // Copy items from sync DB to this DB, replacing those in this DB that have same ID: items.RemoveAll(x => (syncInfo.syncItemsDatabase.GetItem(x.id) != null)); for (int i = 0; i < syncInfo.syncItemsDatabase.items.Count; i++) { items.Insert(i, new Item(syncInfo.syncItemsDatabase.items[i])); } // Update any renamed items in dialogue entry Conditions and Scripts: foreach (Conversation conversation in conversations) { foreach (DialogueEntry entry in conversation.dialogueEntries) { foreach (var kvp in renamedItems) { var oldLuaIndex = kvp.Key; var newLuaIndex = kvp.Value; if (!string.IsNullOrEmpty(entry.conditionsString) && entry.conditionsString.Contains(oldLuaIndex)) { entry.conditionsString = ReplaceLuaIndex(entry.conditionsString, "Item", oldLuaIndex, newLuaIndex); } if (!string.IsNullOrEmpty(entry.userScript) && entry.userScript.Contains(oldLuaIndex)) { entry.userScript = ReplaceLuaIndex(entry.userScript, "Item", oldLuaIndex, newLuaIndex); } } } } } /// /// Syncs the locations from syncInfo.syncLocationsDatabase. /// public void SyncLocations() { ResetCache(); if (!syncInfo.syncLocations || syncInfo.syncLocationsDatabase == null || syncInfo.syncLocationsDatabase == this) return; // Identify locations in sync DB that have been renamed: var renamedLocations = new Dictionary(); // Lua indices foreach (Location sourceLocation in syncInfo.syncLocationsDatabase.locations) { var myLocation = GetLocation(sourceLocation.id); if (myLocation != null && myLocation.Name != sourceLocation.Name) { renamedLocations.Add(DialogueLua.StringToTableIndex(myLocation.Name), DialogueLua.StringToTableIndex(sourceLocation.Name)); } } // Copy locations from sync DB to this DB, replacing those in this DB that have same ID: locations.RemoveAll(x => (syncInfo.syncLocationsDatabase.GetLocation(x.id) != null)); for (int i = 0; i < syncInfo.syncLocationsDatabase.locations.Count; i++) { locations.Insert(i, new Location(syncInfo.syncLocationsDatabase.locations[i])); } // Update any renamed locations in dialogue entry Conditions and Scripts: foreach (Conversation conversation in conversations) { foreach (DialogueEntry entry in conversation.dialogueEntries) { foreach (var kvp in renamedLocations) { var oldLuaIndex = kvp.Key; var newLuaIndex = kvp.Value; if (!string.IsNullOrEmpty(entry.conditionsString) && entry.conditionsString.Contains(oldLuaIndex)) { entry.conditionsString = ReplaceLuaIndex(entry.conditionsString, "Location", oldLuaIndex, newLuaIndex); } if (!string.IsNullOrEmpty(entry.userScript) && entry.userScript.Contains(oldLuaIndex)) { entry.userScript = ReplaceLuaIndex(entry.userScript, "Location", oldLuaIndex, newLuaIndex); } } } } } /// /// Syncs the variables from syncInfo.syncVariablesDatabase. /// public void SyncVariables() { ResetCache(); if (!syncInfo.syncVariables || syncInfo.syncVariablesDatabase == null || syncInfo.syncVariablesDatabase == this) return; // Identify variables in sync DB that have been renamed: var renamedVariables = new Dictionary(); // Lua indices foreach (Variable sourceVariable in syncInfo.syncVariablesDatabase.variables) { var myVariable = GetVariable(sourceVariable.id); if (myVariable != null && myVariable.Name != sourceVariable.Name) { renamedVariables.Add(DialogueLua.StringToTableIndex(myVariable.Name), DialogueLua.StringToTableIndex(sourceVariable.Name)); } } // Copy variables from sync DB to this DB, replacing those in this DB that have same ID: variables.RemoveAll(x => (syncInfo.syncVariablesDatabase.GetVariable(x.id) != null)); for (int i = 0; i < syncInfo.syncVariablesDatabase.variables.Count; i++) { variables.Insert(i, new Variable(syncInfo.syncVariablesDatabase.variables[i])); } // Update any renamed variables in dialogue entry Conditions and Scripts: foreach (Conversation conversation in conversations) { foreach (DialogueEntry entry in conversation.dialogueEntries) { foreach (var kvp in renamedVariables) { var oldLuaIndex = kvp.Key; var newLuaIndex = kvp.Value; if (!string.IsNullOrEmpty(entry.conditionsString) && entry.conditionsString.Contains(oldLuaIndex)) { entry.conditionsString = ReplaceLuaIndex(entry.conditionsString, "Variable", oldLuaIndex, newLuaIndex); } if (!string.IsNullOrEmpty(entry.userScript) && entry.userScript.Contains(oldLuaIndex)) { entry.userScript = ReplaceLuaIndex(entry.userScript, "Variable", oldLuaIndex, newLuaIndex); } } } } } private string ReplaceLuaIndex(string luaCode, string tableName, string oldLuaIndex, string newLuaIndex) { return luaCode.Replace(tableName + "[\"" + oldLuaIndex + "\"]", tableName + "[\"" + newLuaIndex + "\"]"). Replace(tableName + "['" + oldLuaIndex + "']", tableName + "['" + newLuaIndex + "']"); } #endregion /// /// Checks if a list of assets contains an asset with a specified name. /// /// /// true if an asset matches the specified name. /// /// /// The list of assets to search. /// /// /// The name to search for. /// /// /// The type of asset. /// public static bool ContainsName(List assetList, string assetName) where T : Asset { return (assetList != null) && (assetList.Find(x => string.Equals(x.Name, assetName)) != null); } /// /// Checks if a list of assets contains an asset with a specified ID. /// /// /// true if an asset matches the specified ID. /// /// /// The list of assets to search. /// /// /// The ID to search for. /// /// /// The type of asset. /// public static bool ContainsID(List assetList, int assetID) where T : Asset { return (assetList != null) && (assetList.Find(x => (x.id == assetID)) != null); } /// /// Checks is a list of conversations contains a conversation with a specified title. /// /// /// true if the title is found. /// /// /// The conversations to search. /// /// /// The title to search for. /// public static bool ContainsTitle(List conversations, string title) { return (conversations != null) && (conversations.Find(x => string.Equals(x.Title, title)) != null); } /// /// Checks if a database contains an asset. Depending on the asset's type, it will check by /// name or title. This might not be an exact match -- for example, two conversations could /// have the same title, in which case either one would return true. /// /// /// The database to search. /// /// /// The asset to search for. /// public static bool Contains(DialogueDatabase database, Asset asset) { if (asset == null) { return false; } //--- Optimized to use cache: //else if (asset is Actor) //{ // return ContainsName(database.actors, asset.Name); //} //else if (asset is Item) //{ // return ContainsName(database.items, asset.Name); //} //else if (asset is Location) //{ // return ContainsName(database.locations, asset.Name); //} //else if (asset is Variable) //{ // return ContainsName(database.variables, asset.Name); //} //else if (asset is Conversation) //{ // return ContainsTitle(database.conversations, (asset as Conversation).Title); //} database.SetupCaches(); if (asset is Actor) { return database.actorNameCache.ContainsKey(asset.Name); } else if (asset is Item) { return database.itemNameCache.ContainsKey(asset.Name); } else if (asset is Location) { return database.locationNameCache.ContainsKey(asset.Name); } else if (asset is Variable) { return database.variableNameCache.ContainsKey(asset.Name); } else if (asset is Conversation) { return database.conversationTitleCache.ContainsKey((asset as Conversation).Title); } else { Debug.LogError(string.Format("{0}: Unexpected asset type {1}", new System.Object[] { DialogueDebug.Prefix, asset.GetType().Name })); return false; } } /// /// Checks if a list of databases contains an asset. /// /// /// The list of databases to check. /// /// /// The asset to search for. /// public static bool Contains(List databaseList, Asset asset) { foreach (DialogueDatabase database in databaseList) { if (Contains(database, asset)) return true; } return false; } public const string InvalidEntrytag = "invalid_entrytag"; public const string VoiceOverFileFieldName = DialogueSystemFields.VoiceOverFile; //--- Was manually specifying invalid filename chars: private static Regex entrytagRegex = new Regex("[;:,!\'\"\t\r\n\\/\\?\\[\\] ]"); private static Regex entrytagRegex = new Regex(string.Format("[{0}]", Regex.Escape(" " + new string(System.IO.Path.GetInvalidFileNameChars()) + new string(System.IO.Path.GetInvalidPathChars())))); private static Regex actorNameLineNumberEntrytagRegex = new Regex("[,\"\t\r\n\\/<>?]"); /// /// Gets the entrytag string of a dialogue entry. /// /// Dialogue entry's conversation. /// Dialogue entry. /// Entrytag format. /// Entrytag. public string GetEntrytag(Conversation conversation, DialogueEntry entry, EntrytagFormat entrytagFormat) { if (conversation == null || entry == null) return InvalidEntrytag; Actor actor = null; switch (entrytagFormat) { case EntrytagFormat.ActorName_ConversationID_EntryID: actor = GetActor(entry.ActorID); if (actor == null) return InvalidEntrytag; return string.Format("{0}_{1}_{2}", entrytagRegex.Replace(actor.Name, "_"), conversation.id, entry.id); case EntrytagFormat.ConversationTitle_EntryID: return string.Format("{0}_{1}", entrytagRegex.Replace(conversation.Title, "_"), entry.id); case EntrytagFormat.ActorNameLineNumber: actor = GetActor(entry.ActorID); if (actor == null) return InvalidEntrytag; int lineNumber = (conversation.id * 500) + entry.id; return string.Format("{0}{1}", actorNameLineNumberEntrytagRegex.Replace(actor.Name, "_"), lineNumber); case EntrytagFormat.ConversationID_ActorName_EntryID: actor = GetActor(entry.ActorID); if (actor == null) return InvalidEntrytag; return string.Format("{0}_{1}_{2}", conversation.id, entrytagRegex.Replace(actor.Name, "_"), entry.id); case EntrytagFormat.ActorName_ConversationTitle_EntryDescriptor: actor = GetActor(entry.ActorID); if (actor == null) { return InvalidEntrytag; } var entryDesc = !string.IsNullOrEmpty(entry.Title) ? entry.Title : (!string.IsNullOrEmpty(entry.currentMenuText) ? entry.currentMenuText : entry.id.ToString()); return string.Format("{0}_{1}_{2}", entrytagRegex.Replace(actor.Name, "_"), entrytagRegex.Replace(conversation.Title, "_"), entrytagRegex.Replace(entryDesc, "_")); case EntrytagFormat.VoiceOverFile: if (entry == null) return InvalidEntrytag; var voiceOverFile = Field.LookupValue(entry.fields, VoiceOverFileFieldName); return (voiceOverFile == null) ? InvalidEntrytag : entrytagRegex.Replace(voiceOverFile, "_"); case EntrytagFormat.Title: if (entry == null) return InvalidEntrytag; var title = Field.LookupValue(entry.fields, DialogueSystemFields.Title); return (title == null) ? InvalidEntrytag : entrytagRegex.Replace(title, "_"); case EntrytagFormat.Custom: return (getCustomEntrytag != null) ? getCustomEntrytag(conversation, entry) : InvalidEntrytag; default: return InvalidEntrytag; } } /// /// Gets the entrytag string of a dialogue entry. /// /// Dialogue entry's conversation. /// Dialogue entry. /// Entrytag format. /// Entrytag. public string GetEntrytag(int conversationID, int dialogueEntryID, EntrytagFormat entrytagFormat) { var conversation = GetConversation(conversationID); var entry = (conversation != null) ? conversation.GetDialogueEntry(dialogueEntryID) : null; return GetEntrytag(conversation, entry, entrytagFormat); } /// /// Gets the entrytaglocal string (localized version) of a dialogue entry. /// /// Dialogue entry's conversation. /// Dialogue entry. /// Entrytag format. /// Localized entrytag. public string GetEntrytaglocal(Conversation conversation, DialogueEntry entry, EntrytagFormat entrytagFormat) { return GetEntrytag(conversation, entry, entrytagFormat) + "_" + Localization.language; } /// /// Gets the entrytaglocal string (localized version) of a dialogue entry. /// /// Dialogue entry's conversation. /// Dialogue entry. /// Entrytag format. /// Localized entrytag. public string GetEntrytaglocal(int conversationID, int dialogueEntryID, EntrytagFormat entrytagFormat) { var conversation = GetConversation(conversationID); var entry = (conversation != null) ? conversation.GetDialogueEntry(dialogueEntryID) : null; return GetEntrytaglocal(conversation, entry, entrytagFormat); } } }