-
-
Notifications
You must be signed in to change notification settings - Fork 201
/
DialogueRunner.cs
762 lines (603 loc) · 30.2 KB
/
DialogueRunner.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
/*
The MIT License (MIT)
Copyright (c) 2015-2017 Secret Lab Pty. Ltd. and Yarn Spinner contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using CsvHelper;
using System;
// Field ... is never assigned to and will always have its default value null
#pragma warning disable 0649
namespace Yarn.Unity
{
/// DialogueRunners act as the interface between your game and
/// YarnSpinner.
/** Make our menu item slightly nicer looking */
[AddComponentMenu("Scripts/Yarn Spinner/Dialogue Runner")]
public class DialogueRunner : MonoBehaviour
{
/// The source files to load the conversation from
public YarnProgram[] yarnScripts;
public string textLanguage;
/// Our variable storage
public Yarn.Unity.VariableStorageBehaviour variableStorage;
/// The object that will handle the actual display and user input
public Yarn.Unity.DialogueUIBehaviour dialogueUI;
/// Which node to start from
public string startNode = Yarn.Dialogue.DEFAULT_START;
/// Whether we should start dialogue when the scene starts
public bool startAutomatically = true;
/// Tests to see if the dialogue is running
public bool isDialogueRunning { get; private set; }
public bool automaticCommands = true;
private System.Action _continue;
private System.Action<int> _selectAction;
public delegate void CommandHandler(string[] parameters);
public delegate void BlockingCommandHandler(string[] parameters, System.Action onComplete);
/// Maps the names of commands to action delegates.
private Dictionary<string, CommandHandler> commandHandlers = new Dictionary<string, CommandHandler>();
private Dictionary<string, BlockingCommandHandler> blockingCommandHandlers = new Dictionary<string, BlockingCommandHandler>();
// Maps string IDs received from Yarn Spinner to user-facing text
private Dictionary<string, string> strings = new Dictionary<string, string>();
/// A type of UnityEvent that takes a single string parameter.
///
/// We need to create a concrete subclass in order for Unity to
/// serialise the type correctly.
[System.Serializable]
public class StringUnityEvent : UnityEngine.Events.UnityEvent<string> { }
/// A Unity event that receives the name of the node that just
/// finished running
#pragma warning disable 0649
[SerializeField] StringUnityEvent onNodeComplete;
#pragma warning restore 0649
// A flag used to note when we call into a blocking command
// handler, but it calls its complete handler immediately -
// _before_ the Dialogue is told to pause. This out-of-order
// problem can lead to the Dialogue being stuck in a paused state.
// To solve this, this variable is set to false before any blocking
// command handler is called, and set to true when ContinueDialogue
// is called. If it's true after calling a blocking command
// handler, then the Dialogue is not told to pause.
private bool wasCompleteCalled = false;
/// Our conversation engine
/** Automatically created on first access
*/
private Dialogue _dialogue;
public Dialogue dialogue {
get {
if (_dialogue == null) {
// Create the main Dialogue runner, and pass our
// variableStorage to it
_dialogue = new Yarn.Dialogue (variableStorage);
// Set up the logging system.
_dialogue.LogDebugMessage = delegate (string message) {
Debug.Log (message);
};
_dialogue.LogErrorMessage = delegate (string message) {
Debug.LogError (message);
};
_dialogue.lineHandler = HandleLine;
_dialogue.commandHandler = HandleCommand;
_dialogue.optionsHandler = HandleOptions;
_dialogue.nodeCompleteHandler = (node) => {
onNodeComplete?.Invoke(node);
return Dialogue.HandlerExecutionType.ContinueExecution;
};
_dialogue.dialogueCompleteHandler = HandleDialogueComplete;
// Yarn Spinner defines two built-in commands: "wait",
// and "stop". Stop is defined inside the Virtual
// Machine (the compiler traps it and makes it a
// special case.) Wait is defined here in Unity.
AddCommandHandler("wait", HandleWaitCommand);
foreach (var yarnScript in yarnScripts) {
AddStringTable(yarnScript);
}
_continue = this.ContinueDialogue;
_selectAction = this.SelectedOption;
}
return _dialogue;
}
}
private void HandleWaitCommand(string[] parameters, System.Action onComplete)
{
if (parameters?.Length != 1) {
Debug.LogErrorFormat("<<wait>> command expects one parameter.");
onComplete();
return;
}
string durationString = parameters[0];
if (float.TryParse(
durationString,
System.Globalization.NumberStyles.AllowDecimalPoint,
System.Globalization.CultureInfo.InvariantCulture,
out var duration) == false) {
Debug.LogErrorFormat($"<<wait>> failed to parse duration {durationString}");
onComplete();
}
StartCoroutine(DoHandleWait(duration, onComplete));
}
private IEnumerator DoHandleWait(float duration, Action onComplete)
{
yield return new WaitForSeconds(duration);
onComplete();
}
private void HandleDialogueComplete()
{
isDialogueRunning = false;
this.dialogueUI.DialogueComplete();
}
private void HandleOptions(OptionSet options)
{
this.dialogueUI.RunOptions(options, strings, _selectAction);
}
private Dialogue.HandlerExecutionType HandleCommand(Command command)
{
bool wasValidCommand;
Dialogue.HandlerExecutionType executionType;
// Try looking in the command handlers first, which is a lot
// cheaper than crawling the game object hierarchy.
// Set a flag that we can use to tell if the dispatched command
// immediately called _continue
wasCompleteCalled = false;
(wasValidCommand, executionType) = DispatchCommandToRegisteredHandlers(command, _continue);
if (wasValidCommand) {
// This was a valid command. It returned either continue,
// or pause; if it returned pause, there's a chance that
// the command handler immediately called _continue, in
// which case we should not pause.
if (wasCompleteCalled) {
return Dialogue.HandlerExecutionType.ContinueExecution;
} else {
// Either continue execution, or pause (in which case
// _continue will be called)
return executionType;
}
}
// We didn't find it in the comand handlers. Try looking in the game objects.
(wasValidCommand, executionType) = DispatchCommandToGameObject(command);
if (wasValidCommand) {
// We found an object and method to invoke as a Yarn
// command. It may or may not have been a coroutine; if it
// was a coroutine, executionType will be
// HandlerExecutionType.Pause, and we'll wait for it to
// complete before resuming execution.
return executionType;
}
// We didn't find a method in our C# code to invoke. Pass it to
// the UI to handle; it will determine whether we pause or
// continue.
return this.dialogueUI.RunCommand(command, _continue);
}
public (bool commandWasFound, Dialogue.HandlerExecutionType executionType) DispatchCommandToRegisteredHandlers(Command command, System.Action onComplete) {
var commandTokens = command.Text.Split(new[] {' '}, System.StringSplitOptions.RemoveEmptyEntries);
Debug.Log($"Command: <<{command.Text}>>");
if (commandTokens.Length == 0) {
// Nothing to do
return (false, Dialogue.HandlerExecutionType.ContinueExecution);
}
var firstWord = commandTokens[0];
if (commandHandlers.ContainsKey(firstWord) == false &&
blockingCommandHandlers.ContainsKey(firstWord) == false) {
// We don't have a registered handler for this command, but
// some other part of the game might.
return (false, Dialogue.HandlerExecutionType.ContinueExecution);
}
// Single-word command, eg <<jump>>
if (commandTokens.Length == 1) {
if (commandHandlers.ContainsKey(firstWord)) {
commandHandlers[firstWord](null);
return (true, Dialogue.HandlerExecutionType.ContinueExecution);
} else {
blockingCommandHandlers[firstWord](new string[] {}, onComplete);
return (true, Dialogue.HandlerExecutionType.PauseExecution);
}
}
// Multi-word command, eg <<walk Mae left>>
var remainingWords = new string[commandTokens.Length - 1];
// Copy everything except the first word from the array
System.Array.Copy(commandTokens, 1, remainingWords, 0, remainingWords.Length);
if (commandHandlers.ContainsKey(firstWord)) {
commandHandlers[firstWord](remainingWords);
return (true, Dialogue.HandlerExecutionType.ContinueExecution);
} else {
blockingCommandHandlers[firstWord](remainingWords, onComplete);
return (true, Dialogue.HandlerExecutionType.PauseExecution);
}
}
/// Forward the line to the dialogue UI.
private Dialogue.HandlerExecutionType HandleLine(Line line)
{
return this.dialogueUI.RunLine (line, strings, _continue);
}
/// Adds a command handler. Yarn Spinner will continue execution after this handler is called.
public void AddCommandHandler(string commandName, CommandHandler handler) {
if (commandHandlers.ContainsKey(commandName) || blockingCommandHandlers.ContainsKey(commandName)) {
Debug.LogError($"Cannot add a command handler for {commandName}: one already exists");
return;
}
commandHandlers.Add(commandName, handler);
}
/// Adds a command handler. Yarn Spinner will pause execution after this handler is called.
public void AddCommandHandler(string commandName, BlockingCommandHandler handler) {
if (commandHandlers.ContainsKey(commandName) || blockingCommandHandlers.ContainsKey(commandName)) {
Debug.LogError($"Cannot add a command handler for {commandName}: one already exists");
return;
}
blockingCommandHandlers.Add(commandName, handler);
}
/// Removes a specific command handler.
public void RemoveCommandHandler(string commandName) {
commandHandlers.Remove(commandName);
blockingCommandHandlers.Remove(commandName);
}
/// Start the dialogue
void Start ()
{
// Ensure that we have our Implementation object
if (dialogueUI == null) {
Debug.LogError ("Implementation was not set! Can't run the dialogue!");
return;
}
// And that we have our variable storage object
if (variableStorage == null) {
Debug.LogError ("Variable storage was not set! Can't run the dialogue!");
return;
}
// Ensure that the variable storage has the right stuff in it
variableStorage.ResetToDefaults ();
// Combine all scripts together and load them
if (yarnScripts != null && yarnScripts.Length > 0) {
var compiledPrograms = new List<Program>();
foreach (var program in yarnScripts) {
compiledPrograms.Add(program.GetProgram());
}
var combinedProgram = Program.Combine(compiledPrograms.ToArray());
dialogue.SetProgram(combinedProgram);
}
if (startAutomatically) {
StartDialogue();
}
}
/// Adds a program and its base localisation string table
internal void Add(YarnProgram scriptToLoad)
{
AddProgram(scriptToLoad);
AddStringTable(scriptToLoad);
}
/// Adds a program, and all of its nodes
internal void AddProgram(YarnProgram scriptToLoad)
{
this.dialogue.AddProgram(scriptToLoad.GetProgram());
}
/// Adds a tagged string table from the yarn asset depending on the variable "textLanguage"
public void AddStringTable(YarnProgram yarnScript) {
var textToLoad = new TextAsset();
if (yarnScript.localizations != null || yarnScript.localizations.Length > 0) {
textToLoad = Array.Find(yarnScript.localizations, element => element.languageName == textLanguage)?.text;
}
if (textToLoad == null || string.IsNullOrEmpty(textToLoad.text)) {
textToLoad = yarnScript.baseLocalisationStringTable;
}
// Use the invariant culture when parsing the CSV
var configuration = new CsvHelper.Configuration.Configuration(
System.Globalization.CultureInfo.InvariantCulture
);
using (var reader = new System.IO.StringReader(textToLoad.text))
using (var csv = new CsvReader(reader, configuration)) {
csv.Read();
csv.ReadHeader();
while (csv.Read()) {
strings.Add(csv.GetField("id"), csv.GetField("text"));
}
}
}
public void AddStringTable(IDictionary<string, Yarn.StringInfo> stringTable) {
foreach (var line in stringTable) {
strings.Add(line.Key, line.Value.text);
}
}
/// Indicates to the DialogueRunner that the user has selected an option
private void SelectedOption(int obj)
{
this.dialogue.SetSelectedOption(obj);
ContinueDialogue();
}
/// Destroy the variable store and start the dialogue again
public void ResetDialogue ()
{
variableStorage.ResetToDefaults ();
StartDialogue ();
}
/// Start the dialogue from the start node
public void StartDialogue () {
StartDialogue(startNode);
}
/// Start the dialogue from a given node
public void StartDialogue (string startNode)
{
// Stop any processes that might be running already
dialogueUI.StopAllCoroutines ();
// Get it going
RunDialogue (startNode);
}
private void ContinueDialogue()
{
wasCompleteCalled = true;
this.dialogue.Continue();
}
void RunDialogue (string startNode = "Start")
{
// Mark that we're in conversation.
isDialogueRunning = true;
// Signal that we're starting up.
this.dialogueUI.DialogueStarted();
this.dialogue.SetNode(startNode);
ContinueDialogue();
}
/// Clear the dialogue system
public void Clear() {
if (isDialogueRunning) {
throw new System.InvalidOperationException("You cannot clear the dialogue system while a dialogue is running.");
}
dialogue.UnloadAll();
}
/// Stop the dialogue
public void Stop() {
isDialogueRunning = false;
dialogue.Stop();
}
/// Test to see if a node name exists
public bool NodeExists(string nodeName) {
return dialogue.NodeExists(nodeName);
}
/// Return the current node name
public string currentNodeName {
get {
return dialogue.currentNode;
}
}
/// commands that can be automatically dispatched look like this:
/// COMMANDNAME OBJECTNAME <param> <param> <param> ...
/** We can dispatch this command if:
* 1. it has at least 2 words
* 2. the second word is the name of an object
* 3. that object has components that have methods with the
* YarnCommand attribute that have the correct commandString set
*/
public (bool methodFound, Dialogue.HandlerExecutionType executionType) DispatchCommandToGameObject(Command command) {
var words = command.Text.Split(' ');
// need 2 parameters in order to have both a command name
// and the name of an object to find
if (words.Length < 2)
{
return (false, Dialogue.HandlerExecutionType.ContinueExecution);
}
var commandName = words[0];
var objectName = words[1];
var sceneObject = GameObject.Find(objectName);
// If we can't find an object, we can't dispatch a command
if (sceneObject == null)
{
return (false, Dialogue.HandlerExecutionType.ContinueExecution);
}
int numberOfMethodsFound = 0;
List<string[]> errorValues = new List<string[]>();
List<string> parameters;
if (words.Length > 2) {
parameters = new List<string>(words);
parameters.RemoveRange(0, 2);
} else {
parameters = new List<string>();
}
var startedCoroutine = false;
// Find every MonoBehaviour (or subclass) on the object
foreach (var component in sceneObject.GetComponents<MonoBehaviour>()) {
var type = component.GetType();
// Find every method in this component
foreach (var method in type.GetMethods()) {
// Find the YarnCommand attributes on this method
var attributes = (YarnCommandAttribute[]) method.GetCustomAttributes(typeof(YarnCommandAttribute), true);
// Find the YarnCommand whose commandString is equal to
// the command name
foreach (var attribute in attributes) {
if (attribute.commandString == commandName) {
var methodParameters = method.GetParameters();
bool paramsMatch = false;
// Check if this is a params array
if (methodParameters.Length == 1 &&
methodParameters[0].ParameterType.IsAssignableFrom(typeof(string[])))
{
// Cool, we can send the command!
// If this is a coroutine, start it,
// and set a flag so that we know to
// wait for it to finish
string[][] paramWrapper = new string[1][];
paramWrapper[0] = parameters.ToArray();
if (method.ReturnType == typeof(IEnumerator))
{
StartCoroutine(DoYarnCommand(component, method, paramWrapper));
startedCoroutine = true;
}
else
{
method.Invoke(component, paramWrapper);
}
numberOfMethodsFound++;
paramsMatch = true;
}
// Otherwise, verify that this method has the right number of parameters
else if (methodParameters.Length == parameters.Count)
{
paramsMatch = true;
foreach (var paramInfo in methodParameters)
{
if (!paramInfo.ParameterType.IsAssignableFrom(typeof(string)))
{
Debug.LogErrorFormat(sceneObject, "Method \"{0}\" wants to respond to Yarn command \"{1}\", but not all of its parameters are strings!", method.Name, commandName);
paramsMatch = false;
break;
}
}
if (paramsMatch)
{
// Cool, we can send the command!
// If this is a coroutine, start it,
// and set a flag so that we know to
// wait for it to finish
if (method.ReturnType == typeof(IEnumerator))
{
StartCoroutine(DoYarnCommand(component, method, parameters.ToArray()));
startedCoroutine = true;
}
else
{
method.Invoke(component, parameters.ToArray());
}
numberOfMethodsFound++;
}
}
//parameters are invalid, but name matches.
if (!paramsMatch)
{
//save this error in case a matching
//command is never found.
errorValues.Add(new string[] { method.Name, commandName, methodParameters.Length.ToString(), parameters.Count.ToString() });
}
}
}
}
}
// Warn if we found multiple things that could respond to this
// command.
if (numberOfMethodsFound > 1) {
Debug.LogWarningFormat(sceneObject, "The command \"{0}\" found {1} targets. " +
"You should only have one - check your scripts.", command, numberOfMethodsFound);
} else if (numberOfMethodsFound == 0) {
//list all of the near-miss methods only if a proper match
//is not found, but correctly-named methods are.
foreach (string[] errorVal in errorValues) {
Debug.LogErrorFormat(sceneObject, "Method \"{0}\" wants to respond to Yarn command \"{1}\", but it has a different number of parameters ({2}) to those provided ({3}), or is not a string array!", errorVal[0], errorVal[1], errorVal[2], errorVal[3]);
}
}
var wasValidCommand = numberOfMethodsFound > 0;
if (wasValidCommand == false) {
return (false, Dialogue.HandlerExecutionType.ContinueExecution);
}
if (startedCoroutine) {
// Signal to the Dialogue that execution should wait.
return (true, Dialogue.HandlerExecutionType.PauseExecution);
} else {
// This wasn't a coroutine, so no need to wait for it.
return (true, Dialogue.HandlerExecutionType.ContinueExecution);
}
}
IEnumerator DoYarnCommand(MonoBehaviour component, System.Reflection.MethodInfo method, string[][] parameters) {
// Wait for this command coroutine to complete
yield return StartCoroutine((IEnumerator)method.Invoke(component, parameters));
// And then continue running dialogue
ContinueDialogue();
}
IEnumerator DoYarnCommand(MonoBehaviour component, System.Reflection.MethodInfo method, string[] parameters) {
// Wait for this command coroutine to complete
yield return StartCoroutine((IEnumerator)method.Invoke(component, parameters));
// And then continue running dialogue
ContinueDialogue();
}
// Expose the RegisterFunction methods from dialogue.library to Unity
/// Registers a new function that returns a value, so that it can
/// be called from Yarn scripts.
public void RegisterFunction(string name, int parameterCount, ReturningFunction implementation) {
dialogue.library.RegisterFunction(name, parameterCount, implementation);
}
/// Registers a new function that doesn't return a value, so that
/// it can be called from Yarn scripts.
public void RegisterFunction(string name, int parameterCount, Function implementation) {
dialogue.library.RegisterFunction(name, parameterCount, implementation);
}
}
/// them to Yarn.
/** For example:
* [YarnCommand("dosomething")]
* void Foo() {
* do something!
* }
*/
public class YarnCommandAttribute : System.Attribute
{
public string commandString { get; private set; }
public YarnCommandAttribute(string commandString) {
this.commandString = commandString;
}
}
/// Scripts that can act as the UI for the conversation should subclass
/// this
public abstract class DialogueUIBehaviour : MonoBehaviour
{
/// A conversation has started.
public virtual void DialogueStarted() {
// Default implementation does nothing.
}
/// Display a line.
public abstract Dialogue.HandlerExecutionType RunLine (Yarn.Line line, IDictionary<string, string> strings, System.Action onLineComplete);
/// Display the options, and call the optionChooser when done.
public abstract void RunOptions (Yarn.OptionSet optionSet,
IDictionary<string, string> strings,
System.Action<int> onOptionSelected);
/// Perform some game-specific command.
public abstract Dialogue.HandlerExecutionType RunCommand (Yarn.Command command, System.Action onCommandComplete);
/// The node has ended.
public virtual Dialogue.HandlerExecutionType NodeComplete(string nextNode, System.Action onComplete) {
// Default implementation does nothing.
return Dialogue.HandlerExecutionType.ContinueExecution;
}
/// The conversation has ended.
public virtual void DialogueComplete () {
// Default implementation does nothing.
}
}
/// Scripts that can act as a variable storage should subclass this
public abstract class VariableStorageBehaviour : MonoBehaviour, Yarn.VariableStorage
{
/// Get a value
public abstract Value GetValue(string variableName);
public virtual void SetValue(string variableName, float floatValue)
{
Value value = new Yarn.Value(floatValue);
this.SetValue(variableName, value);
}
public virtual void SetValue(string variableName, bool stringValue)
{
Value value = new Yarn.Value(stringValue);
this.SetValue(variableName, value);
}
public virtual void SetValue(string variableName, string boolValue)
{
Value value = new Yarn.Value(boolValue);
this.SetValue(variableName, value);
}
/// Set a value
public abstract void SetValue(string variableName, Value value);
/// Not implemented here
public virtual void Clear ()
{
throw new System.NotImplementedException ();
}
public abstract void ResetToDefaults ();
}
}