// This code is part of the Fungus library (https://github.com/snozbot/fungus) // It is released for free under the MIT open source license (https://github.com/snozbot/fungus/blob/master/LICENSE) using UnityEngine; namespace Fungus { /// /// Base for all Conditional based Commands, Ifs, Loops, and so on. /// [AddComponentMenu("")] public abstract class Condition : Command { protected End endCommand; public override void OnEnter() { if (ParentBlock == null) { return; } //if looping we need the end command in order to work if(IsLooping && !EnsureRequiredEnd()) { Debug.LogError(GetLocationIdentifier() + " is looping but has no matching End command"); Continue(); return; } if ( !HasNeededProperties() ) { Debug.LogError(GetLocationIdentifier() + " cannot run due to missing required properties"); Continue(); return; } //Ensuring we arrived at this elif honestly, not incorrectly due to fall through from a previous command if (this.IsElseIf && !DoesPassElifSanityCheck()) { //elif is being asked to run but didn't come from a previously failing if or elif, this isn't allowed MoveToEnd(); return; } EvaluateAndContinue(); } public override bool OpenBlock() { return true; } public override Color GetButtonColor() { return new Color32(253, 253, 150, 255); } public virtual bool IsLooping { get { return false; } } /// /// Moves execution to the closing End of the current command, attempts to locate end if not /// already known and if no closing End exists. /// public virtual void MoveToEnd() { if(endCommand == null) { endCommand = FindOurEndCommand(); } if (endCommand != null) { // Continue at next command after End // and make the end non looping incase it gets run via index etc. endCommand.Loop = false; Continue(endCommand.CommandIndex + 1); } else { //nowhere to go, so we assume the block wants to stop but is missing and end, this // is also ensures back compat Debug.LogWarning("Condition wants to move to end but no End command found, stopping block. " + GetLocationIdentifier()); StopParentBlock(); } } protected End FindOurEndCommand() { return FindMatchingEndCommand(this); } /// /// Helper to find the paired End Command for the given command. /// /// /// Mathcing End Command or null if not found public static End FindMatchingEndCommand(Command startCommand) { if (startCommand.ParentBlock == null) return null; int indent = startCommand.IndentLevel; for (int i = startCommand.CommandIndex + 1; i < startCommand.ParentBlock.CommandList.Count; ++i) { var command = startCommand.ParentBlock.CommandList[i]; if (command.IndentLevel == indent) { if (command is End) { return command as End; } } else if (command.IndentLevel < indent) { //managed to be less indent than the inner but not find and end, this shouldn't occur // but may be user error or bad data, makes sense for completeness here return null; } } return null; } /// /// Helper for child classes that require an End command to function. For IsLooping commands /// this also configures the loopback within the End command. /// /// protected virtual bool EnsureRequiredEnd() { if (endCommand == null) { endCommand = FindOurEndCommand(); if (endCommand == null) { Debug.LogError( GetLocationIdentifier() + "', could not find closing End command and thus cannot loop."); //StopParentBlock(); return false; } } if (IsLooping) { // Tell the following end command to loop back endCommand.Loop = true; endCommand.LoopBackIndex = CommandIndex; } return true; } /// /// Called by OnEnter when the condition is needed to evaluate and continue execution. /// Means child classes do not have to deal with erronuous execution conditions, like fall through. /// protected virtual void EvaluateAndContinue() { PreEvaluate(); if (EvaluateCondition()) { OnTrue(); } else { OnFalse(); } } /// /// Called when the condition is run and EvaluateCondition returns true /// protected virtual void OnTrue() { Continue(); } /// /// Called when the condition is run and EvaluateCondition returns false /// protected virtual void OnFalse() { //looping constructs only care about the end if(IsLooping) { MoveToEnd(); return; } // Find the next Else, ElseIf or End command at the same indent level as this If command for (int i = CommandIndex + 1; i < ParentBlock.CommandList.Count; ++i) { Command nextCommand = ParentBlock.CommandList[i]; if (nextCommand == null) { continue; } // Find next command at same indent level as this If command // Skip disabled commands, comments & labels if (!((Command)nextCommand).enabled || nextCommand.GetType() == typeof(Comment) || nextCommand.GetType() == typeof(Label) || nextCommand.IndentLevel != indentLevel) { continue; } System.Type type = nextCommand.GetType(); if (type == typeof(Else) || type == typeof(End)) { if (i >= ParentBlock.CommandList.Count - 1) { // Last command in Block, so stop StopParentBlock(); } else { // Execute command immediately after the Else or End command Continue(nextCommand.CommandIndex + 1); return; } } else if (type.IsSubclassOf(typeof(Condition)) && (nextCommand as Condition).IsElseIf) { // Execute the Else If command Continue(i); return; } } // No matching End command found, so just stop the block StopParentBlock(); } /// /// Sits in the if within EvaluateAndContinue, if returns true, OnTrue will run, if false, OnFalse will run. /// protected abstract bool EvaluateCondition(); /// /// Child classes are required to report if it is possible for them to be evaulated. /// protected virtual bool HasNeededProperties() { return true; } /// /// Declare if the child class is implementing an 'else if' command, which requires some special handling /// protected virtual bool IsElseIf { get { return false; } } /// /// Called before EvaluateCondition, allowing for child classes to gather required data /// protected virtual void PreEvaluate() { } /// /// Ensure that this condition didn't come from a non matching if/elif. /// /// protected virtual bool DoesPassElifSanityCheck() { System.Type previousCommandType = ParentBlock.GetPreviousActiveCommandType(); var prevCmdIndent = ParentBlock.GetPreviousActiveCommandIndent(); var prevCmd = ParentBlock.GetPreviousActiveCommand(); //handle our matching if or else if in the chain failing and moving to us, // need to make sure it is the same indent level if (prevCmd == null || prevCmdIndent != IndentLevel || !previousCommandType.IsSubclassOf(typeof(Condition)) || (prevCmd as Condition).IsLooping) { return false; } return true; } } }