An easy to use Unity 3D library for creating illustrated Interactive Fiction games and more.
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.

288 lines
9.7 KiB

// 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
{
/// <summary>
/// Base for all Conditional based Commands, Ifs, Loops, and so on.
/// </summary>
[AddComponentMenu("")]
public abstract class Condition : Command
{
protected End endCommand;
#region Public members
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; } }
/// <summary>
/// Moves execution to the closing End of the current command, attempts to locate end if not
/// already known and if no closing End exists.
/// </summary>
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();
}
}
#endregion
protected End FindOurEndCommand()
{
return FindMatchingEndCommand(this);
}
/// <summary>
/// Helper to find the paired End Command for the given command.
/// </summary>
/// <param name="startCommand"></param>
/// <returns>Mathcing End Command or null if not found</returns>
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;
}
/// <summary>
/// Helper for child classes that require an End command to function. For IsLooping commands
/// this also configures the loopback within the End command.
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// 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.
/// </summary>
protected virtual void EvaluateAndContinue()
{
PreEvaluate();
if (EvaluateCondition())
{
OnTrue();
}
else
{
OnFalse();
}
}
/// <summary>
/// Called when the condition is run and EvaluateCondition returns true
/// </summary>
protected virtual void OnTrue()
{
Continue();
}
/// <summary>
/// Called when the condition is run and EvaluateCondition returns false
/// </summary>
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();
}
/// <summary>
/// Sits in the if within EvaluateAndContinue, if returns true, OnTrue will run, if false, OnFalse will run.
/// </summary>
protected abstract bool EvaluateCondition();
/// <summary>
/// Child classes are required to report if it is possible for them to be evaulated.
/// </summary>
protected virtual bool HasNeededProperties() { return true; }
/// <summary>
/// Declare if the child class is implementing an 'else if' command, which requires some special handling
/// </summary>
protected virtual bool IsElseIf { get { return false; } }
/// <summary>
/// Called before EvaluateCondition, allowing for child classes to gather required data
/// </summary>
protected virtual void PreEvaluate() { }
/// <summary>
/// Ensure that this condition didn't come from a non matching if/elif.
/// </summary>
/// <returns></returns>
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;
}
}
}