You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
858 lines
20 KiB
858 lines
20 KiB
using UnityEngine; |
|
using UnityEngine.Serialization; |
|
using System; |
|
using System.Linq; |
|
using System.Collections; |
|
using System.Collections.Generic; |
|
using System.Text.RegularExpressions; |
|
|
|
namespace Fungus |
|
{ |
|
/** |
|
* Visual scripting controller for the Flowchart programming language. |
|
* Flowchart objects may be edited visually using the Flowchart editor window. |
|
*/ |
|
[ExecuteInEditMode] |
|
public class Flowchart : MonoBehaviour |
|
{ |
|
/** |
|
* Current version used to compare with the previous version so older versions can be custom-updated from previous versions. |
|
*/ |
|
public const string CURRENT_VERSION = "1.0"; |
|
|
|
/** |
|
* The name of the initial block in a new flowchart. |
|
*/ |
|
public const string DEFAULT_BLOCK_NAME = "New Block"; |
|
|
|
/** |
|
* Variable to track flowchart's version and if initial set up has completed. |
|
*/ |
|
[HideInInspector] |
|
public string version; |
|
|
|
/** |
|
* Scroll position of Flowchart editor window. |
|
*/ |
|
[HideInInspector] |
|
public Vector2 scrollPos; |
|
|
|
/** |
|
* Scroll position of Flowchart variables window. |
|
*/ |
|
[HideInInspector] |
|
public Vector2 variablesScrollPos; |
|
|
|
/** |
|
* Show the variables pane. |
|
*/ |
|
[HideInInspector] |
|
public bool variablesExpanded = true; |
|
|
|
/** |
|
* Height of command block view in inspector. |
|
*/ |
|
[HideInInspector] |
|
public float blockViewHeight = 400; |
|
|
|
/** |
|
* Zoom level of Flowchart editor window |
|
*/ |
|
[HideInInspector] |
|
public float zoom = 1f; |
|
|
|
/** |
|
* Scrollable area for Flowchart editor window. |
|
*/ |
|
[HideInInspector] |
|
public Rect scrollViewRect; |
|
|
|
/** |
|
* Currently selected block in the Flowchart editor. |
|
*/ |
|
[HideInInspector] |
|
[FormerlySerializedAs("selectedSequence")] |
|
public Block selectedBlock; |
|
|
|
/** |
|
* Currently selected command in the Flowchart editor. |
|
*/ |
|
[HideInInspector] |
|
public List<Command> selectedCommands = new List<Command>(); |
|
|
|
/** |
|
* The list of variables that can be accessed by the Flowchart. |
|
*/ |
|
[HideInInspector] |
|
public List<Variable> variables = new List<Variable>(); |
|
|
|
[TextArea(3, 5)] |
|
[Tooltip("Description text displayed in the Flowchart editor window")] |
|
public string description = ""; |
|
|
|
/** |
|
* Slow down execution in the editor to make it easier to visualise program flow. |
|
*/ |
|
[Range(0f, 5f)] |
|
[Tooltip("Adds a pause after each execution step to make it easier to visualise program flow. Editor only, has no effect in platform builds.")] |
|
public float stepPause = 0f; |
|
|
|
/** |
|
* Use command color when displaying the command list in the inspector. |
|
*/ |
|
[Tooltip("Use command color when displaying the command list in the Fungus Editor window")] |
|
public bool colorCommands = true; |
|
|
|
/** |
|
* Hides the Flowchart block and command components in the inspector. |
|
* Deselect to inspect the block and command components that make up the Flowchart. |
|
*/ |
|
[Tooltip("Hides the Flowchart block and command components in the inspector")] |
|
public bool hideComponents = true; |
|
|
|
/** |
|
* Saves the selected block and commands when saving the scene. |
|
* Helps avoid version control conflicts if you've only changed the active selection. |
|
*/ |
|
[Tooltip("Saves the selected block and commands when saving the scene.")] |
|
public bool saveSelection = true; |
|
|
|
/** |
|
* Unique identifier for identifying this flowchart in localized string keys. |
|
*/ |
|
[Tooltip("Unique identifier for this flowchart in localized string keys. If no id is specified then the name of the Flowchart object will be used.")] |
|
public string localizationId = ""; |
|
|
|
/** |
|
* Cached list of flowchart objects in the scene for fast lookup |
|
*/ |
|
public static List<Flowchart> cachedFlowcharts = new List<Flowchart>(); |
|
|
|
/** |
|
* Returns the next id to assign to a new flowchart item. |
|
* Item ids increase monotically so they are guaranteed to |
|
* be unique within a Flowchart. |
|
*/ |
|
public int NextItemId() |
|
{ |
|
int maxId = -1; |
|
Block[] blocks = GetComponentsInChildren<Block>(); |
|
foreach (Block block in blocks) |
|
{ |
|
maxId = Math.Max(maxId, block.itemId); |
|
} |
|
|
|
Command[] commands = GetComponentsInChildren<Command>(); |
|
foreach (Command command in commands) |
|
{ |
|
maxId = Math.Max(maxId, command.itemId); |
|
} |
|
return maxId + 1; |
|
} |
|
|
|
public virtual void OnEnable() |
|
{ |
|
if (!cachedFlowcharts.Contains(this)) |
|
{ |
|
cachedFlowcharts.Add(this); |
|
} |
|
|
|
CheckItemIds(); |
|
CleanupComponents(); |
|
UpdateVersion(); |
|
} |
|
|
|
public virtual void OnDisable() |
|
{ |
|
cachedFlowcharts.Remove(this); |
|
} |
|
|
|
protected virtual void CheckItemIds() |
|
{ |
|
// Make sure item ids are unique and monotonically increasing. |
|
// This should always be the case, but some legacy Flowcharts may have issues. |
|
List<int> usedIds = new List<int>(); |
|
Block[] blocks = GetComponentsInChildren<Block>(); |
|
foreach (Block block in blocks) |
|
{ |
|
if (block.itemId == -1 || |
|
usedIds.Contains(block.itemId)) |
|
{ |
|
block.itemId = NextItemId(); |
|
} |
|
usedIds.Add(block.itemId); |
|
} |
|
|
|
Command[] commands = GetComponentsInChildren<Command>(); |
|
foreach (Command command in commands) |
|
{ |
|
if (command.itemId == -1 || |
|
usedIds.Contains(command.itemId)) |
|
{ |
|
command.itemId = NextItemId(); |
|
} |
|
usedIds.Add(command.itemId); |
|
} |
|
} |
|
|
|
protected virtual void CleanupComponents() |
|
{ |
|
// Delete any unreferenced components which shouldn't exist any more |
|
// Unreferenced components don't have any effect on the flowchart behavior, but |
|
// they waste memory so should be cleared out periodically. |
|
|
|
Block[] blocks = GetComponentsInChildren<Block>(); |
|
|
|
foreach (Variable variable in GetComponents<Variable>()) |
|
{ |
|
if (!variables.Contains(variable)) |
|
{ |
|
DestroyImmediate(variable); |
|
} |
|
} |
|
|
|
foreach (Command command in GetComponents<Command>()) |
|
{ |
|
bool found = false; |
|
foreach (Block block in blocks) |
|
{ |
|
if (block.commandList.Contains(command)) |
|
{ |
|
found = true; |
|
break; |
|
} |
|
} |
|
|
|
if (!found) |
|
{ |
|
DestroyImmediate(command); |
|
} |
|
} |
|
|
|
foreach (EventHandler eventHandler in GetComponents<EventHandler>()) |
|
{ |
|
bool found = false; |
|
foreach (Block block in blocks) |
|
{ |
|
if (block.eventHandler == eventHandler) |
|
{ |
|
found = true; |
|
break; |
|
} |
|
} |
|
|
|
if (!found) |
|
{ |
|
DestroyImmediate(eventHandler); |
|
} |
|
} |
|
} |
|
|
|
private void UpdateVersion() |
|
{ |
|
// If versions match, then we are already using the latest. |
|
if (version == CURRENT_VERSION) return; |
|
|
|
switch (version) |
|
{ |
|
// Version never set, so we are initializing on first creation or this flowchart is pre-versioning. |
|
case null: |
|
case "": |
|
Initialize(); |
|
break; |
|
} |
|
|
|
version = CURRENT_VERSION; |
|
} |
|
|
|
protected virtual void Initialize() |
|
{ |
|
// If there are other flowcharts in the scene and the selected block has the default name, then this is probably a new block. |
|
// Reset the event handler of the new flowchart's default block to avoid crashes. |
|
if (selectedBlock && cachedFlowcharts.Count > 1 && selectedBlock.blockName == DEFAULT_BLOCK_NAME) |
|
{ |
|
selectedBlock.eventHandler = null; |
|
} |
|
} |
|
|
|
protected virtual Block CreateBlockComponent(GameObject parent) |
|
{ |
|
Block block = parent.AddComponent<Block>(); |
|
return block; |
|
} |
|
|
|
/** |
|
* Create a new block node which you can then add commands to. |
|
*/ |
|
public virtual Block CreateBlock(Vector2 position) |
|
{ |
|
Block b = CreateBlockComponent(gameObject); |
|
b.nodeRect.x = position.x; |
|
b.nodeRect.y = position.y; |
|
b.blockName = GetUniqueBlockKey(b.blockName, b); |
|
b.itemId = NextItemId(); |
|
|
|
return b; |
|
} |
|
|
|
/** |
|
* Returns the named Block in the flowchart, or null if not found. |
|
*/ |
|
public virtual Block FindBlock(string blockName) |
|
{ |
|
Block [] blocks = GetComponentsInChildren<Block>(); |
|
foreach (Block block in blocks) |
|
{ |
|
if (block.blockName == blockName) |
|
{ |
|
return block; |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
/** |
|
* Start running another Flowchart by executing a specific child block. |
|
* The block must be in an idle state to be executed. |
|
* You can use this method in a UI event. e.g. to handle a button click. |
|
*/ |
|
public virtual void ExecuteBlock(string blockName) |
|
{ |
|
Block [] blocks = GetComponentsInChildren<Block>(); |
|
foreach (Block block in blocks) |
|
{ |
|
if (block.blockName == blockName) |
|
{ |
|
ExecuteBlock(block); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Sends a message to this Flowchart only. |
|
* Any block with a matching MessageReceived event handler will start executing. |
|
*/ |
|
public virtual void SendFungusMessage(string messageName) |
|
{ |
|
MessageReceived[] eventHandlers = GetComponentsInChildren<MessageReceived>(); |
|
foreach (MessageReceived eventHandler in eventHandlers) |
|
{ |
|
eventHandler.OnSendFungusMessage(messageName); |
|
} |
|
} |
|
|
|
/** |
|
* Sends a message to all Flowchart objects in the current scene. |
|
* Any block with a matching MessageReceived event handler will start executing. |
|
*/ |
|
public static void BroadcastFungusMessage(string messageName) |
|
{ |
|
MessageReceived[] eventHandlers = GameObject.FindObjectsOfType<MessageReceived>(); |
|
foreach (MessageReceived eventHandler in eventHandlers) |
|
{ |
|
eventHandler.OnSendFungusMessage(messageName); |
|
} |
|
} |
|
|
|
/** |
|
* Start executing a specific child block in the flowchart. |
|
* The block must be in an idle state to be executed. |
|
* Returns true if the Block started execution. |
|
*/ |
|
public virtual bool ExecuteBlock(Block block, Action onComplete = null) |
|
{ |
|
// Block must be a component of the Flowchart game object |
|
if (block == null || |
|
block.gameObject != gameObject) |
|
{ |
|
return false; |
|
} |
|
|
|
// Can't restart a running block, have to wait until it's idle again |
|
if (block.IsExecuting()) |
|
{ |
|
return false; |
|
} |
|
|
|
// Execute the first command in the command list |
|
block.Execute(onComplete); |
|
|
|
return true; |
|
} |
|
|
|
/** |
|
* Returns a new variable key that is guaranteed not to clash with any existing variable in the list. |
|
*/ |
|
public virtual string GetUniqueVariableKey(string originalKey, Variable ignoreVariable = null) |
|
{ |
|
int suffix = 0; |
|
string baseKey = originalKey; |
|
|
|
// Only letters and digits allowed |
|
char[] arr = baseKey.Where(c => (char.IsLetterOrDigit(c) || c == '_')).ToArray(); |
|
baseKey = new string(arr); |
|
|
|
// No leading digits allowed |
|
baseKey = baseKey.TrimStart('0','1','2','3','4','5','6','7','8','9'); |
|
|
|
// No empty keys allowed |
|
if (baseKey.Length == 0) |
|
{ |
|
baseKey = "Var"; |
|
} |
|
|
|
string key = baseKey; |
|
while (true) |
|
{ |
|
bool collision = false; |
|
foreach(Variable variable in variables) |
|
{ |
|
if (variable == null || |
|
variable == ignoreVariable || |
|
variable.key == null) |
|
{ |
|
continue; |
|
} |
|
|
|
if (variable.key.Equals(key, StringComparison.CurrentCultureIgnoreCase)) |
|
{ |
|
collision = true; |
|
suffix++; |
|
key = baseKey + suffix; |
|
} |
|
} |
|
|
|
if (!collision) |
|
{ |
|
return key; |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Returns a new Block key that is guaranteed not to clash with any existing Block in the Flowchart. |
|
*/ |
|
public virtual string GetUniqueBlockKey(string originalKey, Block ignoreBlock = null) |
|
{ |
|
int suffix = 0; |
|
string baseKey = originalKey.Trim(); |
|
|
|
// No empty keys allowed |
|
if (baseKey.Length == 0) |
|
{ |
|
baseKey = "New Block"; |
|
} |
|
|
|
Block[] blocks = GetComponentsInChildren<Block>(); |
|
|
|
string key = baseKey; |
|
while (true) |
|
{ |
|
bool collision = false; |
|
foreach(Block block in blocks) |
|
{ |
|
if (block == ignoreBlock || |
|
block.blockName == null) |
|
{ |
|
continue; |
|
} |
|
|
|
if (block.blockName.Equals(key, StringComparison.CurrentCultureIgnoreCase)) |
|
{ |
|
collision = true; |
|
suffix++; |
|
key = baseKey + suffix; |
|
} |
|
} |
|
|
|
if (!collision) |
|
{ |
|
return key; |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Returns a new Label key that is guaranteed not to clash with any existing Label in the Block. |
|
*/ |
|
public virtual string GetUniqueLabelKey(string originalKey, Label ignoreLabel) |
|
{ |
|
int suffix = 0; |
|
string baseKey = originalKey.Trim(); |
|
|
|
// No empty keys allowed |
|
if (baseKey.Length == 0) |
|
{ |
|
baseKey = "New Label"; |
|
} |
|
|
|
Block block = ignoreLabel.parentBlock; |
|
|
|
string key = baseKey; |
|
while (true) |
|
{ |
|
bool collision = false; |
|
foreach(Command command in block.commandList) |
|
{ |
|
Label label = command as Label; |
|
if (label == null || |
|
label == ignoreLabel) |
|
{ |
|
continue; |
|
} |
|
|
|
if (label.key.Equals(key, StringComparison.CurrentCultureIgnoreCase)) |
|
{ |
|
collision = true; |
|
suffix++; |
|
key = baseKey + suffix; |
|
} |
|
} |
|
|
|
if (!collision) |
|
{ |
|
return key; |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Returns the variable with the specified key, or null if the key is not found. |
|
* You can then access the variable's value using the Value property. e.g. |
|
* BooleanVariable boolVar = flowchart.GetVariable<BooleanVariable>("MyBool"); |
|
* boolVar.Value = false; |
|
*/ |
|
public T GetVariable<T>(string key) where T : Variable |
|
{ |
|
foreach (Variable variable in variables) |
|
{ |
|
if (variable.key == key) |
|
{ |
|
return variable as T; |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
/** |
|
* Gets a list of all variables with public scope in this Flowchart. |
|
*/ |
|
public virtual List<Variable> GetPublicVariables() |
|
{ |
|
List<Variable> publicVariables = new List<Variable>(); |
|
foreach (Variable v in variables) |
|
{ |
|
if (v.scope == VariableScope.Public) |
|
{ |
|
publicVariables.Add(v); |
|
} |
|
} |
|
|
|
return publicVariables; |
|
} |
|
|
|
/** |
|
* Gets the value of a boolean variable. |
|
* Returns false if the variable key does not exist. |
|
*/ |
|
public virtual bool GetBooleanVariable(string key) |
|
{ |
|
foreach (Variable v in variables) |
|
{ |
|
if (v.key == key) |
|
{ |
|
BooleanVariable variable = v as BooleanVariable; |
|
if (variable != null) |
|
{ |
|
return variable.value; |
|
} |
|
} |
|
} |
|
Debug.LogWarning("Boolean variable " + key + " not found."); |
|
return false; |
|
} |
|
|
|
/** |
|
* Sets the value of a boolean variable. |
|
* The variable must already be added to the list of variables for this Flowchart. |
|
*/ |
|
public virtual void SetBooleanVariable(string key, bool value) |
|
{ |
|
foreach (Variable v in variables) |
|
{ |
|
if (v.key == key) |
|
{ |
|
BooleanVariable variable = v as BooleanVariable; |
|
if (variable != null) |
|
{ |
|
variable.value = value; |
|
return; |
|
} |
|
} |
|
} |
|
Debug.LogWarning("Boolean variable " + key + " not found."); |
|
} |
|
|
|
/** |
|
* Gets the value of an integer variable. |
|
* Returns 0 if the variable key does not exist. |
|
*/ |
|
public virtual int GetIntegerVariable(string key) |
|
{ |
|
foreach (Variable v in variables) |
|
{ |
|
if (v.key == key) |
|
{ |
|
IntegerVariable variable = v as IntegerVariable; |
|
if (variable != null) |
|
{ |
|
return variable.value; |
|
} |
|
} |
|
} |
|
Debug.LogWarning("Integer variable " + key + " not found."); |
|
return 0; |
|
} |
|
|
|
/** |
|
* Sets the value of an integer variable. |
|
* The variable must already be added to the list of variables for this Flowchart. |
|
*/ |
|
public virtual void SetIntegerVariable(string key, int value) |
|
{ |
|
foreach (Variable v in variables) |
|
{ |
|
if (v.key == key) |
|
{ |
|
IntegerVariable variable = v as IntegerVariable; |
|
if (variable != null) |
|
{ |
|
variable.value = value; |
|
return; |
|
} |
|
} |
|
} |
|
Debug.LogWarning("Integer variable " + key + " not found."); |
|
} |
|
|
|
/** |
|
* Gets the value of a float variable. |
|
* Returns 0 if the variable key does not exist. |
|
*/ |
|
public virtual float GetFloatVariable(string key) |
|
{ |
|
foreach (Variable v in variables) |
|
{ |
|
if (v.key == key) |
|
{ |
|
FloatVariable variable = v as FloatVariable; |
|
if (variable != null) |
|
{ |
|
return variable.value; |
|
} |
|
} |
|
} |
|
Debug.LogWarning("Float variable " + key + " not found."); |
|
return 0f; |
|
} |
|
|
|
/** |
|
* Sets the value of a float variable. |
|
* The variable must already be added to the list of variables for this Flowchart. |
|
*/ |
|
public virtual void SetFloatVariable(string key, float value) |
|
{ |
|
foreach (Variable v in variables) |
|
{ |
|
if (v.key == key) |
|
{ |
|
FloatVariable variable = v as FloatVariable; |
|
if (variable != null) |
|
{ |
|
variable.value = value; |
|
return; |
|
} |
|
} |
|
} |
|
Debug.LogWarning("Float variable " + key + " not found."); |
|
} |
|
|
|
/** |
|
* Gets the value of a string variable. |
|
* Returns the empty string if the variable key does not exist. |
|
*/ |
|
public virtual string GetStringVariable(string key) |
|
{ |
|
foreach (Variable v in variables) |
|
{ |
|
if (v.key == key) |
|
{ |
|
StringVariable variable = v as StringVariable; |
|
if (variable != null) |
|
{ |
|
return variable.value; |
|
} |
|
} |
|
} |
|
Debug.LogWarning("String variable " + key + " not found."); |
|
return ""; |
|
} |
|
|
|
/** |
|
* Sets the value of a string variable. |
|
* The variable must already be added to the list of variables for this Flowchart. |
|
*/ |
|
public virtual void SetStringVariable(string key, string value) |
|
{ |
|
foreach (Variable v in variables) |
|
{ |
|
if (v.key == key) |
|
{ |
|
StringVariable variable = v as StringVariable; |
|
if (variable != null) |
|
{ |
|
variable.value = value; |
|
return; |
|
} |
|
} |
|
} |
|
Debug.LogWarning("String variable " + key + " not found."); |
|
} |
|
|
|
/** |
|
* Set the block objects to be hidden or visible depending on the hideComponents property. |
|
*/ |
|
public virtual void UpdateHideFlags() |
|
{ |
|
if (hideComponents) |
|
{ |
|
Block[] blocks = GetComponentsInChildren<Block>(); |
|
foreach (Block block in blocks) |
|
{ |
|
block.hideFlags = HideFlags.HideInInspector; |
|
if (block.gameObject != gameObject) |
|
{ |
|
block.gameObject.hideFlags = HideFlags.HideInHierarchy; |
|
} |
|
} |
|
|
|
Command[] commands = GetComponentsInChildren<Command>(); |
|
foreach (Command command in commands) |
|
{ |
|
command.hideFlags = HideFlags.HideInInspector; |
|
} |
|
|
|
EventHandler[] eventHandlers = GetComponentsInChildren<EventHandler>(); |
|
foreach (EventHandler eventHandler in eventHandlers) |
|
{ |
|
eventHandler.hideFlags = HideFlags.HideInInspector; |
|
} |
|
} |
|
else |
|
{ |
|
MonoBehaviour[] monoBehaviours = GetComponentsInChildren<MonoBehaviour>(); |
|
foreach (MonoBehaviour monoBehaviour in monoBehaviours) |
|
{ |
|
if (monoBehaviour == null) |
|
{ |
|
continue; |
|
} |
|
|
|
monoBehaviour.hideFlags = HideFlags.None; |
|
monoBehaviour.gameObject.hideFlags = HideFlags.None; |
|
} |
|
} |
|
|
|
} |
|
|
|
public virtual void ClearSelectedCommands() |
|
{ |
|
selectedCommands.Clear(); |
|
} |
|
|
|
public virtual void AddSelectedCommand(Command command) |
|
{ |
|
if (!selectedCommands.Contains(command)) |
|
{ |
|
selectedCommands.Add(command); |
|
} |
|
} |
|
|
|
public virtual void Reset(bool resetCommands, bool resetVariables) |
|
{ |
|
if (resetCommands) |
|
{ |
|
Command[] commands = GetComponentsInChildren<Command>(); |
|
foreach (Command command in commands) |
|
{ |
|
command.OnReset(); |
|
} |
|
} |
|
|
|
if (resetVariables) |
|
{ |
|
foreach (Variable variable in variables) |
|
{ |
|
variable.OnReset(); |
|
} |
|
} |
|
} |
|
|
|
public virtual string SubstituteVariables(string text) |
|
{ |
|
string subbedText = text; |
|
|
|
// Instantiate the regular expression object. |
|
Regex r = new Regex("{\\$.*?}"); |
|
|
|
// Match the regular expression pattern against a text string. |
|
var results = r.Matches(text); |
|
foreach (Match match in results) |
|
{ |
|
string key = match.Value.Substring(2, match.Value.Length - 3); |
|
|
|
// Look for any matching variables in this Flowchart first (public or private) |
|
foreach (Variable variable in variables) |
|
{ |
|
if (variable.key == key) |
|
{ |
|
string value = variable.ToString(); |
|
subbedText = subbedText.Replace(match.Value, value); |
|
} |
|
} |
|
|
|
// Now search all public variables in all scene Flowcharts in the scene |
|
foreach (Flowchart flowchart in cachedFlowcharts) |
|
{ |
|
if (flowchart == this) |
|
{ |
|
// We've already searched this flowchart |
|
continue; |
|
} |
|
|
|
foreach (Variable variable in flowchart.variables) |
|
{ |
|
if (variable.scope == VariableScope.Public && |
|
variable.key == key) |
|
{ |
|
string value = variable.ToString(); |
|
subbedText = subbedText.Replace(match.Value, value); |
|
} |
|
} |
|
} |
|
|
|
// Next look for matching localized string |
|
string localizedString = Localization.GetLocalizedString(key); |
|
if (localizedString != null) |
|
{ |
|
subbedText = subbedText.Replace(match.Value, localizedString); |
|
} |
|
} |
|
|
|
return subbedText; |
|
} |
|
} |
|
|
|
} |