internal void RunInstruction(Instruction i)
{
switch (i.operation) {
case ByteCode.Label:
// No-op; used as a destination for JumpTo and Jump.
break;
case ByteCode.JumpTo:
// Jumps to a named label
state.programCounter = FindInstructionPointForLabel ((string)i.operandA);
break;
case ByteCode.RunLine:
// Looks up a string from the string table and
// passes it to the client as a line
var lineText = program.GetString ((string)i.operandA);
lineHandler (new Dialogue.LineResult (lineText));
break;
case ByteCode.RunCommand:
// Passes a string to the client as a custom command
commandHandler (
new Dialogue.CommandResult ((string)i.operandA)
);
break;
case ByteCode.PushString:
// Pushes a string value onto the stack; the operand
// is an index into the string table, so that's looked up
// first.
state.PushValue (program.GetString ((string)i.operandA));
break;
case ByteCode.PushNumber:
// Pushes a number onto the stack.
state.PushValue (Convert.ToSingle(i.operandA));
break;
case ByteCode.PushBool:
// Pushes a boolean value onto the stack.
state.PushValue (Convert.ToBoolean(i.operandA));
break;
case ByteCode.PushNull:
// Pushes a null value onto the stack.
state.PushValue (Value.NULL);
break;
case ByteCode.JumpIfFalse:
// Jumps to a named label if the value on the top of the stack
// evaluates to the boolean value 'false'.
if (state.PeekValue ().AsBool == false) {
state.programCounter = FindInstructionPointForLabel ((string)i.operandA);
}
break;
case ByteCode.Jump:
// Jumps to a label whose name is on the stack.
var jumpDestination = state.PeekValue ().AsString;
state.programCounter = FindInstructionPointForLabel (jumpDestination);
break;
case ByteCode.Pop:
// Pops a value from the stack.
state.PopValue ();
break;
case ByteCode.CallFunc:
// Call a function, whose parameters are expected to
// be on the stack. Pushes the function's return value,
// if it returns one.
var functionName = (string)i.operandA;
var function = dialogue.library.GetFunction (functionName);
{
var paramCount = function.paramCount;
// If this function takes "-1" parameters, it is variadic.
// Expect the compiler to have placed the number of parameters
// actually passed at the top of the stack.
if (paramCount == -1) {
paramCount = (int)state.PopValue ().AsNumber;
}
// Get the parameters, which were pushed in reverse
Value[] parameters = new Value[paramCount];
for (int param = paramCount - 1; param >= 0; param--) {
parameters [param] = state.PopValue ();
}
// Invoke the function
var result = function.InvokeWithArray (parameters);
// If the function returns a value, push it
if (function.returnsValue) {
state.PushValue (result);
}
}
break;
case ByteCode.PushVariable:
// Get the contents of a variable, push that onto the stack.
var variableName = (string)i.operandA;
var loadedValue = dialogue.continuity.GetValue (variableName);
state.PushValue (loadedValue);
break;
case ByteCode.StoreVariable:
// Store the top value on the stack in a variable.
var topValue = state.PeekValue ();
var destinationVariableName = (string)i.operandA;
dialogue.continuity.SetValue (destinationVariableName, topValue);
break;
case ByteCode.Stop:
// Immediately stop execution, and report that fact.
nodeCompleteHandler (new Dialogue.NodeCompleteResult (null));
executionState = ExecutionState.Stopped;
break;
case ByteCode.RunNode:
string nodeName;
if (string.IsNullOrEmpty((string) i.operandA)) {
// Get a string from the stack, and jump to a node with that name.
nodeName = state.PeekValue ().AsString;
} else {
// jump straight to the node
nodeName = (string)i.operandA;
}
nodeCompleteHandler (new Dialogue.NodeCompleteResult (nodeName));
SetNode (nodeName);
break;
case ByteCode.AddOption:
// Add an option to the current state.
state.currentOptions.Add (new KeyValuePair<string, string> ((string)i.operandA, (string)i.operandB));
break;
case ByteCode.ShowOptions:
// If we have no options to show, immediately stop.
if (state.currentOptions.Count == 0) {
executionState = ExecutionState.Stopped;
nodeCompleteHandler (new Dialogue.NodeCompleteResult (null));
break;
}
// If we have a single option, and it has no label, select it immediately and continue
// execution
if (state.currentOptions.Count == 1 && state.currentOptions[0].Key == null) {
var destinationNode = state.currentOptions[0].Value;
state.PushValue(destinationNode);
state.currentOptions.Clear();
break;
}
if (dialogue.continuity.GetValue(SpecialVariables.ShuffleOptions).boolValue) {
// Shuffle the dialog options if needed
var n = state.currentOptions.Count;
for (int opt1 = 0; opt1 < n; opt1++) {
int opt2 = opt1 + (int)(random.NextDouble () * (n - opt1)); // r.Next(0, state.currentOptions.Count-1);
var temp = state.currentOptions [opt2];
state.currentOptions [opt2] = state.currentOptions [opt1];
state.currentOptions [opt1] = temp;
}
}
// Otherwise, present the list of options to the user and let them pick
var optionStrings = new List<string> ();
foreach (var option in state.currentOptions) {
optionStrings.Add (program.GetString (option.Key));
}
// We can't continue until our client tell us which option to pick
executionState = ExecutionState.WaitingOnOptionSelection;
// Pass the options set to the client, as well as a delegate for them to call when the
// user has made a selection
optionsHandler (new Dialogue.OptionSetResult (optionStrings, delegate (int selectedOption) {
// we now know what number option was selected; push the corresponding node name
// to the stack
var destinationNode = state.currentOptions[selectedOption].Value;
state.PushValue(destinationNode);
// We no longer need the accumulated list of options; clear it so that it's
// ready for the next one
state.currentOptions.Clear();
// We can now also keep running
executionState = ExecutionState.Running;
}));
break;
default:
// Whoa, no idea what bytecode this is. Stop the program
// and throw an exception.
executionState = ExecutionState.Stopped;
throw new ArgumentOutOfRangeException ();
}
}