├── Grimm_Tests ├── bin │ └── conversations │ │ ├── conversation9.dia │ │ ├── conversation14.dia │ │ ├── conversation15.dia │ │ ├── conversation23.dia │ │ ├── conversation11.dia │ │ ├── conversation24.dia │ │ ├── conversation13.dia │ │ ├── conversation36.dia │ │ ├── conversation25.dia │ │ ├── conversation31.dia │ │ ├── conversation12.dia │ │ ├── conversation26.dia │ │ ├── conversation38.dia │ │ ├── conversation17.dia │ │ ├── conversation34.dia │ │ ├── conversation18.dia │ │ ├── conversation30.dia │ │ ├── conversation1.dia │ │ ├── conversation21.dia │ │ ├── conversation33.dia │ │ ├── conversation39.dia │ │ ├── conversation16.dia │ │ ├── conversation28.dia │ │ ├── conversation5.dia │ │ ├── conversation2.dia │ │ ├── conversation35.dia │ │ ├── conversation27.dia │ │ ├── conversation8.dia │ │ ├── conversation37.dia │ │ ├── conversation3.dia │ │ ├── conversation20.dia │ │ ├── conversation10.dia │ │ ├── conversation29.dia │ │ ├── conversation6.dia │ │ ├── conversation7.dia │ │ ├── conversation19.dia │ │ ├── conversation22.dia │ │ ├── conversation22b.dia │ │ ├── conversation32.dia │ │ ├── conversation4.dia │ │ └── conversations-textmate-project.tmproj ├── dlls │ └── nunit.framework.dll ├── Properties │ └── AssemblyInfo.cs ├── tests │ ├── ConversationPrinterTest.cs │ └── DialogueRunnerTest.cs └── Grimm_Tests.csproj ├── .gitignore ├── InteractiveDialogueTester ├── bin │ └── Debug │ │ ├── nunit.framework.dll │ │ ├── meeting.dia │ │ └── PixieMeeting1.dia ├── Properties │ └── AssemblyInfo.cs ├── InteractiveDialogueTester.csproj └── Main.cs ├── Grimm ├── src │ ├── Dialogue │ │ ├── Nodes │ │ │ ├── ImmediateNode.cs │ │ │ ├── UnifiedEndNodeForScope.cs │ │ │ ├── ConversationStartDialogueNode.cs │ │ │ ├── ConversationEndDialogueNode.cs │ │ │ ├── SilentDialogueNode.cs │ │ │ ├── FocusDialogueNode.cs │ │ │ ├── AssertDialogueNode.cs │ │ │ ├── BroadcastDialogueNode.cs │ │ │ ├── CancelDialogueNode.cs │ │ │ ├── StartCommandoDialogueNode.cs │ │ │ ├── GotoDialogueNode.cs │ │ │ ├── LoopDialogueNode.cs │ │ │ ├── BreakDialogueNode.cs │ │ │ ├── StopDialogueNode.cs │ │ │ ├── TimedWaitDialogueNode.cs │ │ │ ├── InterruptDialogueNode.cs │ │ │ ├── ExpressionNode.cs │ │ │ ├── CallFunctionDialogueNode.cs │ │ │ ├── BranchingDialogueNode.cs │ │ │ ├── TimedDialogueNode.cs │ │ │ ├── IfDialogueNode.cs │ │ │ ├── ListeningDialogueNode.cs │ │ │ ├── WaitDialogueNode.cs │ │ │ └── DialogueNode.cs │ │ ├── IRegisteredDialogueNode.cs │ │ ├── Speech.cs │ │ ├── ScriptLoader │ │ │ ├── Token.cs │ │ │ ├── Tokenizer.cs │ │ │ └── DialogueScriptLoader.cs │ │ ├── DialogueScriptPrinter.cs │ │ └── DialogueRunner.cs │ └── GrimmException.cs ├── LanguageDefinitionPlugins │ ├── Textmate │ │ └── grimm-grammar.txt │ └── Notpepad++ │ │ └── Grimm ├── Grimm.sln ├── Properties │ └── AssemblyInfo.cs └── Grimm.csproj ├── LICENSE ├── README └── Grimm.sln /Grimm_Tests/bin/conversations/conversation9.dia: -------------------------------------------------------------------------------- 1 | [Örjan] -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation14.dia: -------------------------------------------------------------------------------- 1 | poo(cat, dog) -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation15.dia: -------------------------------------------------------------------------------- 1 | cat.poo(dog) -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation23.dia: -------------------------------------------------------------------------------- 1 | 2 | FOCUS 3 | -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation11.dia: -------------------------------------------------------------------------------- 1 | START conversation1 -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation24.dia: -------------------------------------------------------------------------------- 1 | LOOP { 2 | 3 | } -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation13.dia: -------------------------------------------------------------------------------- 1 | foo(blah, bleh, bluh) -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation36.dia: -------------------------------------------------------------------------------- 1 | WAIT 3 2 | malin "Woo!" -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation25.dia: -------------------------------------------------------------------------------- 1 | LOOP { 2 | erik "hej" 3 | } -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation31.dia: -------------------------------------------------------------------------------- 1 | 2 | STOP conversation32 3 | -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation12.dia: -------------------------------------------------------------------------------- 1 | WAIT SunIsShining() 2 | Erik "Yay!" -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation26.dia: -------------------------------------------------------------------------------- 1 | LOOP { 2 | BREAK 3 | } 4 | erik "yo" -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation38.dia: -------------------------------------------------------------------------------- 1 | 2 | LISTEN Foo WAIT IsHour(20) 3 | -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation17.dia: -------------------------------------------------------------------------------- 1 | a "hej1" 2 | STOP 3 | a "hej2" 4 | a "hej3" -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation34.dia: -------------------------------------------------------------------------------- 1 | 2 | CHOICE { 3 | "Hej?": 4 | "Hej!": 5 | } -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation18.dia: -------------------------------------------------------------------------------- 1 | WAIT Monster.StateIs("Angry") 2 | You "Ahhhhh!" -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation30.dia: -------------------------------------------------------------------------------- 1 | IF Foo() 2 | { 3 | Erik "1" 4 | Erik "2" 5 | } -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation1.dia: -------------------------------------------------------------------------------- 1 | Tomten "Hoho" 2 | Barn "Hjälp!" 3 | Tomten "Oh no" 4 | -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation21.dia: -------------------------------------------------------------------------------- 1 | LISTEN RainStarted 2 | Erik "Where is my umbrella?" -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation33.dia: -------------------------------------------------------------------------------- 1 | 2 | Arne "before" 3 | INTERRUPT conversation1 4 | Arne "after" -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation39.dia: -------------------------------------------------------------------------------- 1 | 2 | [AGAIN] 3 | Log("boom") 4 | WAIT 1 5 | GOTO AGAIN 6 | 7 | -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation16.dia: -------------------------------------------------------------------------------- 1 | a "hej1" 2 | ASSERT Pass() 3 | a "hej2" 4 | ASSERT Fail() 5 | a "hej3" -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation28.dia: -------------------------------------------------------------------------------- 1 | WAIT A() AND B() AND C() 2 | 3 | WAIT A() AND B() 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation5.dia: -------------------------------------------------------------------------------- 1 | { 2 | "hej?": 3 | b "hej på dig" 4 | } 5 | 6 | c "slut" 7 | 8 | -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation2.dia: -------------------------------------------------------------------------------- 1 | Dude "Yo!" 2 | Man "Wazzup" 3 | Dude "Not much..." [importantNode] 4 | Man "OK" -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation35.dia: -------------------------------------------------------------------------------- 1 | WAIT Whatever() 2 | erik "Yeah1" 3 | WAIT Whatever() LISTEN Bam 4 | erik "Yeah2" -------------------------------------------------------------------------------- /Grimm_Tests/dlls/nunit.framework.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eriksvedang/Grimm/HEAD/Grimm_Tests/dlls/nunit.framework.dll -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation27.dia: -------------------------------------------------------------------------------- 1 | WAIT Expr() 2 | { 3 | 4 | } 5 | 6 | WAIT Expr() [namedWaitStatement] 7 | { 8 | 9 | } -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation8.dia: -------------------------------------------------------------------------------- 1 | IF TimeForSleep() { 2 | Erik "zzz" 3 | } ELSE { 4 | Erik "come on!" 5 | Erik "let's party" 6 | } -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation37.dia: -------------------------------------------------------------------------------- 1 | 2 | [AGAIN] 3 | 4 | CHOICE { 5 | "a": 6 | "b": 7 | "c": 8 | } 9 | 10 | GOTO AGAIN 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.DS_Store 3 | *.mdb 4 | *.pidb 5 | *.userprefs 6 | *.json 7 | *.xml 8 | *.userprefs 9 | 10 | test-results 11 | Debug/ 12 | Release/ -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation3.dia: -------------------------------------------------------------------------------- 1 | { 2 | "Option 1": 3 | WiseMan "first way" [first] 4 | 5 | "Option 2": 6 | WiseMan "second way" [second] 7 | } -------------------------------------------------------------------------------- /InteractiveDialogueTester/bin/Debug/nunit.framework.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eriksvedang/Grimm/HEAD/InteractiveDialogueTester/bin/Debug/nunit.framework.dll -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation20.dia: -------------------------------------------------------------------------------- 1 | LISTEN GoodNews { 2 | Erik "Yay!" 3 | } 4 | 5 | LISTEN BadNews { 6 | Erik "Oh no!" 7 | } 8 | 9 | BROADCAST GoodNews -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation10.dia: -------------------------------------------------------------------------------- 1 | LANGUAGE Swedish 2 | 3 | Erik "Hej" 4 | Heather "Hej!" 5 | 6 | 7 | LANGUAGE English 8 | 9 | Erik "Hi" 10 | Heather "Hi!" 11 | 12 | -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation29.dia: -------------------------------------------------------------------------------- 1 | IF A() { 2 | Erik "A" 3 | } 4 | ELIF B() { 5 | Erik "B" 6 | } ELIF C() { 7 | Erik "C" 8 | } ELIF D() 9 | { 10 | Erik "D" 11 | } -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation6.dia: -------------------------------------------------------------------------------- 1 | a "a" 2 | GOTO FIRST_JUMP_HERE 3 | e "fel!" 4 | a "c" [THEN_JUMP_HERE] 5 | GOTO __End__ 6 | a "b" [FIRST_JUMP_HERE] 7 | GOTO THEN_JUMP_HERE 8 | e "fel!" -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation7.dia: -------------------------------------------------------------------------------- 1 | IF TimeForSleep() { 2 | Erik "Let's sleep" 3 | Erik "I'm tired" 4 | } 5 | 6 | IF TimeForDinner() 7 | { 8 | Erik "Let's eat" 9 | Erik "I'm hungry" 10 | } -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation19.dia: -------------------------------------------------------------------------------- 1 | 2 | LISTEN SomethingElse { 3 | Audience "Wow" 4 | } 5 | 6 | LISTEN TensionWentUp { 7 | Audience "Oh!" 8 | } 9 | 10 | Audience "Blah blah blah" 11 | 12 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/ImmediateNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GrimmLib 4 | { 5 | public class ImmediateNode : DialogueNode 6 | { 7 | public override void OnEnter() 8 | { 9 | Stop(); 10 | StartNextNode(); 11 | } 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/UnifiedEndNodeForScope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GrimmLib 4 | { 5 | public class UnifiedEndNodeForScope : DialogueNode 6 | { 7 | public override void OnEnter() 8 | { 9 | Stop(); 10 | StartNextNode(); 11 | } 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation22.dia: -------------------------------------------------------------------------------- 1 | 2 | LISTEN BetterTimes [betterTimesListener1] { 3 | PositivePerson "Yay!" 4 | } 5 | 6 | LISTEN BetterTimes [betterTimesListener2] { 7 | NegativePerson "Oh no!" 8 | } 9 | 10 | CANCEL betterTimesListener1 11 | BROADCAST BetterTimes 12 | 13 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/ConversationStartDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GrimmLib 4 | { 5 | public class ConversationStartDialogueNode : DialogueNode 6 | { 7 | public override void OnEnter() 8 | { 9 | Stop(); 10 | StartNextNode(); 11 | } 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/ConversationEndDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GameTypes; 3 | 4 | namespace GrimmLib 5 | { 6 | public class ConversationEndDialogueNode : DialogueNode 7 | { 8 | public override void Update(float dt) 9 | { 10 | Stop(); 11 | _dialogueRunner.StopConversation(conversation); 12 | } 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/SilentDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GrimmLib 4 | { 5 | // When a silent node is entered nothing happens and the dialogue won't continue from there. 6 | 7 | public class SilentDialogueNode : DialogueNode 8 | { 9 | public override void OnEnter() 10 | { 11 | Stop(); 12 | } 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation22b.dia: -------------------------------------------------------------------------------- 1 | 2 | 3 | LISTEN BetterTimes [A] { 4 | Someone "Yup" 5 | LISTEN Dope { 6 | Someone "Do not say this" 7 | } 8 | } 9 | 10 | 11 | BROADCAST BetterTimes 12 | 13 | WAIT 10 # let dope register 14 | 15 | CANCEL A # should cancel both 16 | 17 | WAIT 1 18 | 19 | BROADCAST Dope 20 | 21 | -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation32.dia: -------------------------------------------------------------------------------- 1 | Lars "hej" 2 | Lars "hej" 3 | Lars "hej" 4 | Lars "hej" 5 | Lars "hej" 6 | Lars "hej" 7 | Lars "hej" 8 | Lars "hej" 9 | Lars "hej" 10 | Lars "hej" 11 | Lars "hej" 12 | Lars "hej" 13 | Lars "hej" 14 | Lars "hej" 15 | Lars "hej" 16 | Lars "hej" 17 | Lars "hej" 18 | Lars "hej" 19 | Lars "hej" 20 | Lars "hej" 21 | Lars "hej" -------------------------------------------------------------------------------- /Grimm/src/Dialogue/IRegisteredDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GrimmLib 4 | { 5 | public interface IRegisteredDialogueNode 6 | { 7 | string handle { get; set; } 8 | string conversation { get; set; } 9 | string name { get; set; } 10 | bool isListening { get; set; } 11 | string eventName { get; set; } 12 | void EventHappened(); 13 | string ScopeNode(); 14 | } 15 | } -------------------------------------------------------------------------------- /Grimm/src/GrimmException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GrimmLib 4 | { 5 | public class GrimmException : Exception 6 | { 7 | public GrimmException (string pMessage) : base(pMessage) 8 | { 9 | } 10 | } 11 | 12 | public class GrimmAssertException : Exception 13 | { 14 | public GrimmAssertException (string pMessage) : base(pMessage) 15 | { 16 | } 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversation4.dia: -------------------------------------------------------------------------------- 1 | 2 | Monster "Yo!" 3 | 4 | { 5 | "Be gone foul beast!": 6 | Monster "No" 7 | 8 | "Who are you?": 9 | Monster "I'm the monster" 10 | { 11 | "But what is your name?": 12 | Monster "I dunno..." 13 | "OK, nice": 14 | Monster "Whatever" 15 | "Hmmmm...": 16 | "Say what?": 17 | } 18 | Monster "I'm tired of talking!" 19 | } 20 | 21 | Wizard "Byee" 22 | 23 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/FocusDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GrimmLib 4 | { 5 | public class FocusDialogueNode : DialogueNode 6 | { 7 | public override void OnEnter() 8 | { 9 | _dialogueRunner.FocusConversation(conversation); 10 | Stop(); 11 | StartNextNode(); 12 | } 13 | } 14 | 15 | public class DefocusDialogueNode : DialogueNode 16 | { 17 | public override void OnEnter() 18 | { 19 | _dialogueRunner.DefocusConversation(conversation); 20 | Stop(); 21 | StartNextNode(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/AssertDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GrimmLib 4 | { 5 | public class AssertDialogueNode : ExpressionDialogueNode 6 | { 7 | public override void Update(float dt) 8 | { 9 | Stop(); 10 | if(_dialogueRunner.EvaluateExpression(expression, args) == false) { 11 | var argsConcatenated = string.Join(", ", args); 12 | throw new GrimmAssertException("Assertion " + expression + "(" + argsConcatenated + ") failed in conversation '" + conversation + "'"); 13 | } 14 | StartNextNode(); 15 | } 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/BroadcastDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelayLib; 3 | namespace GrimmLib 4 | { 5 | public class BroadcastDialogueNode : DialogueNode 6 | { 7 | ValueEntry CELL_eventName; 8 | 9 | protected override void SetupCells() 10 | { 11 | base.SetupCells (); 12 | CELL_eventName = EnsureCell("eventName", "undefined"); 13 | } 14 | 15 | public string eventName 16 | { 17 | get { 18 | return CELL_eventName.data; 19 | } 20 | set { 21 | CELL_eventName.data = value; 22 | } 23 | } 24 | 25 | public override void OnEnter() 26 | { 27 | Stop(); 28 | _dialogueRunner.EventHappened(eventName); 29 | StartNextNode(); 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /Grimm/LanguageDefinitionPlugins/Textmate/grimm-grammar.txt: -------------------------------------------------------------------------------- 1 | { scopeName = 'source.untitled'; 2 | fileTypes = ( 'dia' ); 3 | foldingStartMarker = '/\*\*|\{\s*$'; 4 | foldingStopMarker = '\*\*/|^\s*\}'; 5 | patterns = ( 6 | { name = 'keyword.control.untitled'; 7 | match = '\b(IF|ELSE|GOTO|START|WAIT_UNTIL|ASSERT|STOP|LISTEN_FOR|BROADCAST)\b'; 8 | }, 9 | { name = 'comment'; 10 | begin = '#'; 11 | end = '\n'; 12 | }, 13 | { name = 'constant'; 14 | begin = '\['; 15 | end = '\]'; 16 | }, 17 | { name = 'string.quoted.double.untitled'; 18 | begin = '"'; 19 | end = '"'; 20 | patterns = ( 21 | { name = 'constant.character.escape.untitled'; 22 | match = '\\.'; 23 | }, 24 | ); 25 | }, 26 | ); 27 | } -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/CancelDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelayLib; 3 | namespace GrimmLib 4 | { 5 | public class CancelDialogueNode : DialogueNode 6 | { 7 | ValueEntry CELL_handle; 8 | 9 | protected override void SetupCells() 10 | { 11 | base.SetupCells(); 12 | CELL_handle = EnsureCell("handle", ""); 13 | } 14 | 15 | public override void Update(float dt) 16 | { 17 | Stop(); 18 | _dialogueRunner.CancelRegisteredNode(conversation, handle); 19 | StartNextNode(); 20 | } 21 | 22 | #region ACCESSORS 23 | 24 | public string handle { 25 | get { 26 | return CELL_handle.data; 27 | } 28 | set { 29 | CELL_handle.data = value; 30 | } 31 | } 32 | 33 | #endregion 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/StartCommandoDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GameTypes; 3 | using RelayLib; 4 | 5 | namespace GrimmLib 6 | { 7 | public class StartCommandoDialogueNode : DialogueNode 8 | { 9 | ValueEntry CELL_commando; 10 | 11 | protected override void SetupCells() 12 | { 13 | base.SetupCells (); 14 | CELL_commando = EnsureCell("commando", "undefined"); 15 | } 16 | 17 | public override void OnEnter() 18 | { 19 | Stop(); 20 | _dialogueRunner.StartConversation(commando); 21 | StartNextNode(); 22 | } 23 | 24 | #region ACCESSORS 25 | 26 | public string commando 27 | { 28 | get { 29 | return CELL_commando.data; 30 | } 31 | set { 32 | CELL_commando.data = value; 33 | } 34 | } 35 | 36 | #endregion 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Speech.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GrimmLib 4 | { 5 | /// 6 | /// Contains data from the Dialogue Runner's OnSomeoneSaidSomething-event 7 | /// 8 | public struct Speech 9 | { 10 | public string conversation; 11 | public string dialogueNodeName; 12 | public string speaker; 13 | public string line; 14 | 15 | public Speech (string pConversation, string pDialogueNodeName, string pSpeaker, string pLine) 16 | { 17 | conversation = pConversation; 18 | dialogueNodeName = pDialogueNodeName; 19 | speaker = pSpeaker; 20 | line = pLine; 21 | } 22 | 23 | public override string ToString() 24 | { 25 | return string.Format("TalkEventInfo conversation = '{0}', dialogueNodeName = '{1}', talker = '{2}', line = '{3}'", conversation, dialogueNodeName, speaker, line); 26 | } 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Erik Svedäng, Johannes Gotlén 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | Permission is granted to anyone to use this software for any purpose, 7 | including commercial applications, and to alter it and redistribute it 8 | freely, subject to the following restrictions: 9 | 1. The origin of this software must not be misrepresented; you must not 10 | claim that you wrote the original software. If you use this software 11 | in a product, an acknowledgment in the product documentation would be 12 | appreciated but is not required. 13 | 2. Altered source versions must be plainly marked as such, and must not be 14 | misrepresented as being the original software. 15 | 3. This notice may not be removed or altered from any source distribution. 16 | -------------------------------------------------------------------------------- /Grimm/Grimm.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Grimm", "Grimm.csproj", "{04E66DF6-70ED-4DC6-8636-9C32C2EE6D96}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {04E66DF6-70ED-4DC6-8636-9C32C2EE6D96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {04E66DF6-70ED-4DC6-8636-9C32C2EE6D96}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {04E66DF6-70ED-4DC6-8636-9C32C2EE6D96}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {04E66DF6-70ED-4DC6-8636-9C32C2EE6D96}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(MonoDevelopProperties) = preSolution 18 | StartupItem = Grimm.csproj 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/GotoDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelayLib; 3 | namespace GrimmLib 4 | { 5 | public class GotoDialogueNode : DialogueNode 6 | { 7 | ValueEntry CELL_linkedNode; 8 | 9 | protected override void SetupCells() 10 | { 11 | base.SetupCells(); 12 | CELL_linkedNode = EnsureCell("linkedNode", ""); 13 | } 14 | 15 | public override void Update (float dt) 16 | { 17 | string originalNextNode = nextNode; 18 | nextNode = linkedNode; 19 | Stop(); 20 | //_dialogueRunner.logger.Log("GOTO node '" + name + "' in conversation '" + conversation + "' was triggered and is jumping to '" + nextNode + "'"); 21 | StartNextNode(); 22 | nextNode = originalNextNode; 23 | } 24 | 25 | #region ACCESSORS 26 | 27 | public string linkedNode 28 | { 29 | get { 30 | return CELL_linkedNode.data; 31 | } 32 | set { 33 | CELL_linkedNode.data = value; 34 | } 35 | } 36 | 37 | #endregion 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/LoopDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelayLib; 3 | 4 | namespace GrimmLib 5 | { 6 | public class LoopDialogueNode : DialogueNode 7 | { 8 | ValueEntry CELL_branchNode; 9 | DialogueNode _branchNodeCache; 10 | 11 | protected override void SetupCells() 12 | { 13 | base.SetupCells (); 14 | CELL_branchNode = EnsureCell("branchNode", "undefined"); 15 | } 16 | 17 | public override void Update(float dt) 18 | { 19 | if(_branchNodeCache == null) { 20 | _branchNodeCache = _dialogueRunner.GetDialogueNode(conversation, branchNode); 21 | } 22 | 23 | _branchNodeCache.Start(); 24 | } 25 | 26 | public void Break() 27 | { 28 | Stop(); 29 | _dialogueRunner.ScopeEnded(conversation, this.name); 30 | StartNextNode(); 31 | } 32 | 33 | public string branchNode 34 | { 35 | get { 36 | return CELL_branchNode.data; 37 | } 38 | set { 39 | CELL_branchNode.data = value; 40 | } 41 | } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/BreakDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelayLib; 3 | 4 | namespace GrimmLib 5 | { 6 | public class BreakDialogueNode : DialogueNode 7 | { 8 | ValueEntry CELL_breakTargetLoop; 9 | 10 | protected override void SetupCells() 11 | { 12 | base.SetupCells (); 13 | CELL_breakTargetLoop = EnsureCell("breakTarget", "undefined"); 14 | } 15 | 16 | public override void Update(float dt) 17 | { 18 | Stop(); 19 | LoopDialogueNode targetLoopDialogueNode = _dialogueRunner.GetDialogueNode(conversation, breakTargetLoop) as LoopDialogueNode; 20 | if(targetLoopDialogueNode == null) { 21 | throw new GrimmException("targetLoopDialogueNode was not of type LoopDialogueNode"); 22 | } 23 | targetLoopDialogueNode.Break(); 24 | //StartNextNode(); 25 | } 26 | 27 | public string breakTargetLoop 28 | { 29 | get { 30 | return CELL_breakTargetLoop.data; 31 | } 32 | set { 33 | CELL_breakTargetLoop.data = value; 34 | } 35 | } 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /InteractiveDialogueTester/bin/Debug/meeting.dia: -------------------------------------------------------------------------------- 1 | IF CoinFlip() { 2 | Monster "Yo!" 3 | } ELSE { 4 | Monster "Ha ha!" 5 | } 6 | 7 | [again] 8 | 9 | LISTEN Annoyed { 10 | Narrator "The monster is getting more annoyed..." 11 | } 12 | 13 | { 14 | "Be gone foul beast!": 15 | Monster "No" 16 | { 17 | "How dare you cross my path!": 18 | Monster "I don't have any feelings so it's not that hard." 19 | "I'll slay you with my golden sword": 20 | Monster "Not if I eat you first!" 21 | "You're fat": 22 | BROADCAST Annoyed 23 | } 24 | Monster "Don't hurt my feelings" 25 | 26 | "Who are you?": 27 | Monster "I'm the monster" 28 | { 29 | "But what is your name?": 30 | Monster "I dunno..." 31 | "OK, nice": 32 | Monster "Whatever" 33 | "Say what?": 34 | BROADCAST Annoyed 35 | } 36 | Monster "I'm tired of talking!" 37 | } 38 | 39 | GOTO again 40 | 41 | Player "OK, byee" 42 | Monster "Take care, cya" 43 | 44 | { 45 | "Yeah, in hell!": 46 | Monster "..." 47 | BROADCAST Annoyed 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/StopDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelayLib; 3 | 4 | namespace GrimmLib 5 | { 6 | public class StopDialogueNode : DialogueNode 7 | { 8 | ValueEntry CELL_conversationToStop; 9 | 10 | protected override void SetupCells() 11 | { 12 | base.SetupCells(); 13 | CELL_conversationToStop = EnsureCell("conversationToStop", ""); 14 | } 15 | 16 | public override void OnEnter() 17 | { 18 | Stop(); 19 | _dialogueRunner.DefocusConversation (conversationToStop); 20 | _dialogueRunner.StopConversation(conversationToStop); 21 | if(conversationToStop != conversation) { 22 | // if the stopped conversation is another conversation we just go on to the next node 23 | StartNextNode(); 24 | } 25 | } 26 | 27 | #region ACCESSORS 28 | 29 | public string conversationToStop 30 | { 31 | get { 32 | return CELL_conversationToStop.data; 33 | } 34 | set { 35 | CELL_conversationToStop.data = value; 36 | } 37 | } 38 | 39 | #endregion 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /Grimm/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("Grimm")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("Erik")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | 28 | -------------------------------------------------------------------------------- /Grimm_Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("Grimm_Tests")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("Erik")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | 28 | -------------------------------------------------------------------------------- /InteractiveDialogueTester/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("InteractiveDialogueTester")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("Erik")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | 28 | -------------------------------------------------------------------------------- /Grimm_Tests/tests/ConversationPrinterTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelayLib; 3 | using NUnit.Framework; 4 | using GameTypes; 5 | 6 | namespace GrimmLib.tests 7 | { 8 | [TestFixture()] 9 | public class ConversationPrinterTest 10 | { 11 | RelayTwo _relay; 12 | DialogueRunner _dialogueRunner; 13 | 14 | [SetUp()] 15 | public void SetUp() 16 | { 17 | _relay = new RelayTwo(); 18 | _relay.CreateTable(DialogueNode.TABLE_NAME); 19 | _dialogueRunner = new DialogueRunner(_relay, Language.SWEDISH); 20 | } 21 | 22 | [Test()] 23 | public void SimpleConversation() 24 | { 25 | DialogueScriptLoader loader = new DialogueScriptLoader(_dialogueRunner); 26 | loader.LoadDialogueNodesFromFile("../conversations/conversation5.dia"); 27 | DialogueScriptPrinter printer = new DialogueScriptPrinter(_dialogueRunner); 28 | printer.PrintConversation("conversation5"); 29 | } 30 | 31 | [Test()] 32 | public void ComplicatedConversation() 33 | { 34 | DialogueScriptLoader loader = new DialogueScriptLoader(_dialogueRunner); 35 | loader.LoadDialogueNodesFromFile("../conversations/conversation4.dia"); 36 | DialogueScriptPrinter printer = new DialogueScriptPrinter(_dialogueRunner); 37 | printer.PrintConversation("conversation4"); 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/TimedWaitDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelayLib; 3 | using GameTypes; 4 | 5 | namespace GrimmLib 6 | { 7 | public class TimedWaitDialogueNode : DialogueNode 8 | { 9 | ValueEntry CELL_timer; 10 | ValueEntry CELL_timerStartValue; 11 | 12 | protected override void SetupCells() 13 | { 14 | base.SetupCells (); 15 | CELL_timer = EnsureCell( "timer", 1.0f); 16 | CELL_timerStartValue = EnsureCell("timerStartValue", CELL_timer.data); 17 | } 18 | 19 | public override void OnExit() 20 | { 21 | timer = timerStartValue; 22 | } 23 | 24 | public override void Update(float dt) 25 | { 26 | //Console.WriteLine("Updating timed wait node, timer = " + timer); 27 | 28 | if(timer > 0) { 29 | timer -= dt; 30 | if(timer <= 0.0f) { 31 | Stop(); 32 | StartNextNode(); 33 | } 34 | } 35 | } 36 | 37 | #region ACCESSORS 38 | 39 | public float timer 40 | { 41 | get { 42 | return CELL_timer.data; 43 | } 44 | set { 45 | CELL_timer.data = value; 46 | } 47 | } 48 | 49 | public float timerStartValue 50 | { 51 | get { 52 | return CELL_timerStartValue.data; 53 | } 54 | set { 55 | CELL_timerStartValue.data = value; 56 | } 57 | } 58 | 59 | #endregion 60 | } 61 | } -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/InterruptDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelayLib; 3 | using GameTypes; 4 | 5 | namespace GrimmLib 6 | { 7 | public class InterruptDialogueNode : DialogueNode 8 | { 9 | ValueEntry CELL_interruptingConversation; 10 | 11 | protected override void SetupCells() 12 | { 13 | base.SetupCells(); 14 | CELL_interruptingConversation = EnsureCell("interconvo", "undefined"); 15 | } 16 | 17 | public override void OnEnter() 18 | { 19 | _dialogueRunner.StartConversation(interruptingConversation); 20 | } 21 | 22 | public override void Update(float dt) 23 | { 24 | if(!_dialogueRunner.ConversationIsRunning(interruptingConversation)) { 25 | D.Log("Detected that interrupting conversation " + interruptingConversation + " has stopped, will continue in " + base.conversation); 26 | Stop(); 27 | StartNextNode(); 28 | } 29 | // else { 30 | // D.Log("Interrupting conversation " + interruptingConversation + " is still going on..."); 31 | // } 32 | } 33 | 34 | #region ACCESSORS 35 | 36 | public string interruptingConversation 37 | { 38 | get { 39 | return CELL_interruptingConversation.data; 40 | } 41 | set { 42 | CELL_interruptingConversation.data = value; 43 | } 44 | } 45 | 46 | #endregion 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/ExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelayLib; 3 | using GameTypes; 4 | 5 | namespace GrimmLib 6 | { 7 | public class ExpressionDialogueNode : DialogueNode 8 | { 9 | ValueEntry CELL_expression; 10 | ValueEntry CELL_args; 11 | 12 | protected override void SetupCells() 13 | { 14 | base.SetupCells (); 15 | CELL_expression = EnsureCell("expression", "undefined"); 16 | CELL_args = EnsureCell("args", new string[] {}); 17 | } 18 | 19 | #region ACCESSORS 20 | 21 | public string expression 22 | { 23 | get { 24 | return CELL_expression.data; 25 | } 26 | set { 27 | CELL_expression.data = value; 28 | } 29 | } 30 | 31 | public string[] args 32 | { 33 | get { 34 | return CELL_args.data; 35 | } 36 | set { 37 | CELL_args.data = value; 38 | } 39 | } 40 | 41 | #endregion 42 | 43 | public bool Evaluate() 44 | { 45 | try { 46 | return _dialogueRunner.EvaluateExpression(expression, args); 47 | } 48 | catch(Exception e) { 49 | var msg = "Error when evaluating expression " + expression + " in " + conversation + " with args: " + string.Join(", ", args) + " e: " + e.Message + " stack: " + e.StackTrace; 50 | D.Log(msg); 51 | return false; 52 | //throw new GrimmException(msg); 53 | } 54 | } 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/CallFunctionDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelayLib; 3 | using GameTypes; 4 | 5 | namespace GrimmLib 6 | { 7 | public class CallFunctionDialogueNode : DialogueNode 8 | { 9 | ValueEntry CELL_function; 10 | ValueEntry CELL_args; 11 | 12 | protected override void SetupCells() 13 | { 14 | base.SetupCells (); 15 | CELL_function = EnsureCell("function", "undefined"); 16 | CELL_args = EnsureCell("args", new string[] {}); 17 | } 18 | 19 | public override void OnEnter() 20 | { 21 | Stop(); 22 | 23 | try { 24 | _dialogueRunner.CallFunction(function, args); 25 | } 26 | catch(Exception e) { 27 | Console.ForegroundColor = ConsoleColor.Red; 28 | string msg = "Error when calling function from node " + this.name + " in conversation '" + this.conversation + "': " + e.Message + " \nStack trace: " + e.StackTrace; 29 | D.Log(msg); 30 | Console.ForegroundColor = ConsoleColor.White; 31 | 32 | if (_dialogueRunner.onGrimmError != null) { 33 | _dialogueRunner.onGrimmError (msg); 34 | } 35 | //throw new GrimmException (msg); 36 | } 37 | 38 | StartNextNode(); 39 | } 40 | 41 | public override string ToString () 42 | { 43 | return string.Format ("[CallFunctionDialogueNode: function={0}, args={1}, conversation={2}]", function, args, conversation); 44 | } 45 | 46 | #region ACCESSORS 47 | 48 | public string function 49 | { 50 | get { 51 | return CELL_function.data; 52 | } 53 | set { 54 | CELL_function.data = value; 55 | } 56 | } 57 | 58 | public string[] args 59 | { 60 | get { 61 | return CELL_args.data; 62 | } 63 | set { 64 | CELL_args.data = value; 65 | } 66 | } 67 | 68 | #endregion 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/ScriptLoader/Token.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GameTypes; 3 | 4 | namespace GrimmLib 5 | { 6 | public class Token 7 | { 8 | public enum TokenType { 9 | 10 | NO_TOKEN_TYPE, 11 | EOF, 12 | NEW_LINE, 13 | COMMA, 14 | 15 | NAME, 16 | NUMBER, 17 | QUOTED_STRING, 18 | SWITCH, 19 | COLON, 20 | END, 21 | GOTO, 22 | 23 | PARANTHESIS_LEFT, 24 | PARANTHESIS_RIGHT, 25 | BLOCK_BEGIN, 26 | BLOCK_END, 27 | BRACKET_LEFT, 28 | BRACKET_RIGHT, 29 | DOT, 30 | 31 | IF, 32 | ELSE, 33 | ELIF, 34 | 35 | CHOICE, 36 | LANGUAGE, 37 | START, 38 | INTERRUPT, 39 | WAIT, 40 | ASSERT, 41 | LOOP, 42 | BREAK, 43 | STOP, 44 | LISTEN, 45 | BROADCAST, 46 | CANCEL, 47 | FOCUS, 48 | DEFOCUS, 49 | AND, 50 | 51 | ETERNAL 52 | 53 | }; 54 | 55 | TokenType _tokenType; 56 | string _tokenString; 57 | int _lineNr = -1; 58 | int _linePosition = -1; 59 | 60 | public Token (TokenType pTokenType, string pTokenString) 61 | { 62 | _tokenType = pTokenType; 63 | _tokenString = pTokenString; 64 | } 65 | 66 | public Token(TokenType pTokenType, string pTokenString, int pLineNr, int pLinePosition) 67 | { 68 | _tokenType = pTokenType; 69 | _tokenString = pTokenString; 70 | _lineNr = pLineNr; 71 | _linePosition = pLinePosition; 72 | } 73 | 74 | public TokenType getTokenType() { return _tokenType; } 75 | public string getTokenString() { return _tokenString; } 76 | 77 | public int LineNr { 78 | set { 79 | _lineNr = value; 80 | } 81 | get { 82 | return _lineNr; 83 | } 84 | } 85 | 86 | public int LinePosition { 87 | set { 88 | _linePosition = value; 89 | } 90 | get { 91 | return _linePosition; 92 | } 93 | } 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /InteractiveDialogueTester/bin/Debug/PixieMeeting1.dia: -------------------------------------------------------------------------------- 1 | 2 | PlayerCharacter "Öööh..." 3 | Pixie "Va?" 4 | 5 | { 6 | "Ehm, ursäkta det var inget": 7 | Pixie "Nä vadå? Du ville ju säga nåt!" 8 | { 9 | "Du dansar bra": 10 | Pixie "Haha tack! Vad snäll du är" 11 | 12 | "Jag måste gå och köpa en till öl": 13 | Pixie "Nej, vänta!" 14 | } 15 | 16 | "Hej": 17 | Pixie "Hej" 18 | Pixie "" 19 | Pixie "Huh?" 20 | PlayerCharacter "Jag ville bara säga hej tror jag..." 21 | } 22 | 23 | Pixie "Vem är du egentligen?" 24 | 25 | { 26 | "Jag heter Sebastian": 27 | Pixie "Pixie. Angenämnt!" 28 | Pixie "" 29 | 30 | "Mitt namn är Sebastian, jag arbetar som kringresande läskförsäljare": 31 | Pixie "Ok..." 32 | Pixie "Jag heter Pixie och jag gillar att gå hit och dansa" 33 | 34 | "Öhhh": 35 | } 36 | 37 | Pixie "Jag tror inte jag sett dig förut" 38 | 39 | { 40 | "Nej, jag kom just hit": 41 | Pixie "Till Dorisburg?" 42 | PlayerCharacter "Mm, precis" 43 | Pixie "Så... vad tycker du om staden?" 44 | { 45 | "Den är jättefin": 46 | Pixie "Tycker du? Jag är så jävla trött på den här hålan" 47 | 48 | "Jag vet inte än, jag kom igår": 49 | PlayerCharacter "Men den verkar riktigt trevlig" 50 | Pixie "Det är ett jävla skitställe kan jag avslöja" 51 | } 52 | Pixie "Men jag älskar att gå hit till BarYvonne" 53 | Pixie "Kom så dansar vi!!!" 54 | 55 | "Jag går inte ut så mycket": 56 | Pixie "Varför inte det?!!! Jag älskar att gå hit. De spelar så bra musik." 57 | 58 | { 59 | "Ja den är jättebra": 60 | Pixie "Ja visst är den?! Kom så dansar vi!" 61 | 62 | "Jag tycker de spelar för högt": 63 | Pixie "Vad är du för tråkmåns egentligen?!" 64 | PlayerCharacter "Eh..." 65 | Pixie "Äh, förlåt! Jag är bara lite full. Vill du inte dansa med mig?" 66 | } 67 | } 68 | 69 | PlayerCharacter "Eh... ok, njae..." 70 | Pixie "Nej oj vänta! Klockan är ju massor" 71 | Pixie "Jag måste hem till Ivan" 72 | 73 | { 74 | "Vem är det?": 75 | Pixie "" 76 | "Vem är Ivan?": 77 | Pixie "" 78 | "Ivan?": 79 | Pixie "" 80 | "Ivan, vem är det?": 81 | Pixie "" 82 | "Ivan??!?!?": 83 | Pixie "" 84 | "???": 85 | Pixie "" 86 | } 87 | 88 | Pixie "Det är min pojkvän! Hej då jag måste dra!" -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/BranchingDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GameTypes; 3 | using RelayLib; 4 | using System.Collections.Generic; 5 | 6 | namespace GrimmLib 7 | { 8 | public class BranchingDialogueNode : DialogueNode 9 | { 10 | ValueEntry CELL_nextNodes; 11 | ValueEntry CELL_unifiedEndNodeForScope; 12 | ValueEntry CELL_eternal; 13 | 14 | protected override void SetupCells() 15 | { 16 | base.SetupCells (); 17 | CELL_nextNodes = EnsureCell("nextNodes", new string[] {}); 18 | CELL_unifiedEndNodeForScope = EnsureCell("unifiedEndNodeForScope", ""); 19 | CELL_eternal = EnsureCell("eternal", false); 20 | } 21 | 22 | public override void Update(float dt) 23 | { 24 | if(nextNode != "") { 25 | Stop(); 26 | StartNextNode(); 27 | nextNode = ""; 28 | } 29 | } 30 | 31 | public void Choose(int pOptionNr) 32 | { 33 | D.assert(pOptionNr >= 0); 34 | D.assert(pOptionNr < nextNodes.Length); 35 | string nameOfChosenNode = nextNodes[pOptionNr]; 36 | if (!eternal && nextNodes.Length > 1) { 37 | RemoveOptionFromNextNodes (pOptionNr); 38 | } 39 | nextNode = nameOfChosenNode; 40 | } 41 | 42 | void RemoveOptionFromNextNodes (int pOptionNr) 43 | { 44 | string[] oldOptions = CELL_nextNodes.data; 45 | List newOptions = new List (); 46 | for (int i = 0; i < oldOptions.Length; i++) { 47 | if (i == pOptionNr) { 48 | continue; 49 | } else { 50 | newOptions.Add (oldOptions [i]); 51 | } 52 | } 53 | CELL_nextNodes.data = newOptions.ToArray(); 54 | } 55 | 56 | #region ACCESSORS 57 | 58 | /// 59 | /// Names of the possible nodes that this branching node can lead to 60 | /// 61 | public string[] nextNodes { 62 | get { 63 | return CELL_nextNodes.data; 64 | } 65 | set { 66 | CELL_nextNodes.data = value; 67 | } 68 | } 69 | 70 | /// 71 | /// Name of the node that will follow after that any branches inside this scope has finished 72 | /// 73 | public string unifiedEndNodeForScope { 74 | get { 75 | return CELL_unifiedEndNodeForScope.data; 76 | } 77 | set { 78 | CELL_unifiedEndNodeForScope.data = value; 79 | } 80 | } 81 | 82 | public bool eternal { 83 | get { 84 | return CELL_eternal.data; 85 | } 86 | set { 87 | CELL_eternal.data = value; 88 | } 89 | } 90 | 91 | #endregion 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/TimedDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelayLib; 3 | 4 | namespace GrimmLib 5 | { 6 | public class TimedDialogueNode : DialogueNode 7 | { 8 | public static float speedScaling = 1.0f; 9 | 10 | ValueEntry CELL_timer; 11 | ValueEntry CELL_timerStartValue; 12 | ValueEntry CELL_speaker; 13 | ValueEntry CELL_line; 14 | 15 | protected override void SetupCells() 16 | { 17 | base.SetupCells (); 18 | CELL_timer = EnsureCell( "timer", 2.0f); 19 | CELL_timerStartValue = EnsureCell("timerStartValue", CELL_timer.data); 20 | CELL_speaker = EnsureCell("speaker", "unknown"); 21 | CELL_line = EnsureCell("line", ""); 22 | } 23 | 24 | public void CalculateAndSetTimeBasedOnLineLength(bool isOptionNode) 25 | { 26 | float baseTime = isOptionNode ? 0.8f : 1.3f; 27 | float timePerChar = isOptionNode ? 0.020f : 0.040f; 28 | timerStartValue = timer = baseTime + line.Length * timePerChar; 29 | } 30 | 31 | public override void OnEnter() 32 | { 33 | _dialogueRunner.SomeoneSaidSomething(new Speech(conversation, name, speaker, line)); 34 | } 35 | 36 | public override void OnExit() 37 | { 38 | _dialogueRunner.SomeoneSaidSomething(new Speech(conversation, name, speaker, "")); 39 | timer = timerStartValue; 40 | } 41 | 42 | public override void Update(float dt) 43 | { 44 | if(timer > 0) { 45 | timer -= dt * speedScaling; 46 | if(timer <= 0.0f) { 47 | Stop(); 48 | StartNextNode(); 49 | } 50 | } 51 | } 52 | 53 | public override string ToString () 54 | { 55 | return string.Format ("[TimedDialogueNode: timer={0}, timerStartValue={1}, speaker={2}, line={3}, conversionat = {4}]", timer, timerStartValue, speaker, line,conversation); 56 | } 57 | 58 | #region ACCESSORS 59 | 60 | public float timer 61 | { 62 | get { 63 | return CELL_timer.data; 64 | } 65 | set { 66 | CELL_timer.data = value; 67 | } 68 | } 69 | 70 | public float timerStartValue 71 | { 72 | get { 73 | return CELL_timerStartValue.data; 74 | } 75 | set { 76 | CELL_timerStartValue.data = value; 77 | } 78 | } 79 | 80 | public string speaker { 81 | get { 82 | return CELL_speaker.data; 83 | } 84 | set { 85 | CELL_speaker.data = value; 86 | } 87 | } 88 | 89 | public string line { 90 | get { 91 | return CELL_line.data; 92 | } 93 | set { 94 | CELL_line.data = value; 95 | } 96 | } 97 | 98 | #endregion 99 | } 100 | } -------------------------------------------------------------------------------- /InteractiveDialogueTester/InteractiveDialogueTester.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.21022 7 | 2.0 8 | {D99ADFED-D2BC-47B8-8BB4-54A87CC93433} 9 | Exe 10 | InteractiveDialogueTester 11 | InteractiveDialogueTester 12 | v3.5 13 | 14 | 15 | true 16 | full 17 | false 18 | bin\Debug 19 | DEBUG; 20 | prompt 21 | 4 22 | false 23 | true 24 | 25 | 26 | none 27 | false 28 | bin\Release 29 | prompt 30 | 4 31 | true 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {EC63C4E3-C5B8-4EB1-90CD-2D2FC17801BD} 44 | GameTypes 45 | 46 | 47 | {04E66DF6-70ED-4DC6-8636-9C32C2EE6D96} 48 | Grimm 49 | 50 | 51 | {9CFC0B9C-EC60-4488-9D4A-79F191AA5AFF} 52 | Relay 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Grimm/LanguageDefinitionPlugins/Notpepad++/Grimm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | "[0"]0 10 | 11 | 12 | " 13 | 1 2 0# 14 | GOTO IF ELSE START WAIT_UNTIL LISTEN_FOR BROADCAST STOP ASSERT 15 | 16 | 17 | llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | A Game Story Scripting Language written by Erik Svedäng. 2 | Requires the Relay database to work. Contains Textmate and Notepad++ bundles for syntax highlighting. 3 | 4 | - LANGUAGE OVERVIEW - 5 | 6 | # 0. Comments start with a '#' 7 | 8 | # 1. Make someone say something 9 | Charlie "What time is it?" 10 | 11 | # 2. Player is presented with dialogue options 12 | CHOICE { 13 | "It's 5.30": 14 | Charlie "Ok, gotta run!" 15 | 16 | "I don't know": 17 | Charlie "Oh, thanks anyway" 18 | } 19 | 20 | # Choices can also be written without the CHOICE-word (deprected but saved for compatibility) 21 | { 22 | "Yes": 23 | Charlie "OK" 24 | "No": 25 | Charlie "OK" 26 | } 27 | 28 | # 3. Jump to places marked with [ ] in the same file 29 | GOTO SKIP_HERE 30 | Charlie "I will not say this" 31 | [SKIP_HERE] 32 | Charlie "We meet again" [MEET_AGAIN] 33 | 34 | # 4. Control the flow by checking expressions in the dialogue runner 35 | IF Charlie.IsSleepy() { 36 | Charlie "Yawn" 37 | } 38 | ELIF Charlie.IsHungry() { 39 | Charlie "I want pizza..." 40 | } 41 | ELSE { 42 | Charlie "Let's go party!" 43 | } 44 | 45 | # 5. Start other dialogue files 46 | START AnotherWorldCinematic 47 | 48 | # 6. Wait here until an expression becomes true (accepts strings, numbers and plain tokens) 49 | WAIT Charlie.IsInRoom(Corridor) 50 | 51 | # 7a. Register a block of code that will execute when an event happens 52 | LISTEN DisasterousEvent { 53 | START EverybodyScreams 54 | } 55 | # An event listener can be given a name for later reference, like this: 56 | LISTEN DisasterousEvent DisasterEventListener { 57 | START EverybodyScreams 58 | } 59 | 60 | # 7b. Wait here until an event is sent out 61 | LISTEN BetterTimes 62 | 63 | # 8. Send out an event 64 | BROADCAST TimeForWork 65 | 66 | # 9. Call a function in the dialogue runner (accepts strings, numbers and plain tokens) 67 | Monkey.Eat(Banana, "Munch munch", 5.0) 68 | 69 | # 10. Stop the execution of the script and unregister all listeners it contains 70 | STOP 71 | 72 | # Stopping another conversation works too 73 | STOP AnotherConversation 74 | 75 | # 11. Unregister event listeners with a specific name in the same file 76 | CANCEL DisasterEventListener 77 | 78 | # 12. Check an expression and throw and exception if it's not true 79 | ASSERT EveryoneIsSane() 80 | 81 | # 13. Start script X and stop execution of the current script until script X has stopped 82 | INTERRUPT X 83 | 84 | # 14. Combine WAIT for expression with LISTEN for event 85 | WAIT IsNice() AND IsSummer() LISTEN IceCreamTime { 86 | Dude "Let's eat ice cream!" 87 | } 88 | 89 | # 15. Wait for a certain time (in seconds) 90 | WAIT 10 91 | 92 | # Notes on syntactical sugar: 93 | # Expressions and functions can be written in two ways 94 | Player.TeleportTo(StartPosition) 95 | # is the same thing as 96 | TeleportTo(Player, StartPosition) 97 | # Both of these ways of writing will send the same two arguments to the 'TeleportTo' function -------------------------------------------------------------------------------- /Grimm_Tests/Grimm_Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.21022 7 | 2.0 8 | {8FCBDD6C-0716-48DF-936D-F109801AE78B} 9 | Library 10 | Grimm_Tests 11 | Grimm_Tests 12 | v3.5 13 | 14 | 15 | true 16 | full 17 | false 18 | bin\Debug 19 | DEBUG; 20 | prompt 21 | 4 22 | false 23 | 24 | 25 | none 26 | false 27 | bin\Release 28 | prompt 29 | 4 30 | false 31 | 32 | 33 | 34 | 35 | 3.5 36 | 37 | 38 | dlls\nunit.framework.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | {EC63C4E3-C5B8-4EB1-90CD-2D2FC17801BD} 51 | GameTypes 52 | 53 | 54 | {04E66DF6-70ED-4DC6-8636-9C32C2EE6D96} 55 | Grimm 56 | 57 | 58 | {9CFC0B9C-EC60-4488-9D4A-79F191AA5AFF} 59 | Relay 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Grimm.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 10.00 3 | # Visual Studio 2008 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Grimm", "Grimm\Grimm.csproj", "{04E66DF6-70ED-4DC6-8636-9C32C2EE6D96}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteractiveDialogueTester", "InteractiveDialogueTester\InteractiveDialogueTester.csproj", "{D99ADFED-D2BC-47B8-8BB4-54A87CC93433}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameTypes", "..\GameTypes\GameTypes\GameTypes.csproj", "{EC63C4E3-C5B8-4EB1-90CD-2D2FC17801BD}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Relay", "..\Relay\Relay\Relay.csproj", "{9CFC0B9C-EC60-4488-9D4A-79F191AA5AFF}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Grimm_Tests", "Grimm_Tests\Grimm_Tests.csproj", "{8FCBDD6C-0716-48DF-936D-F109801AE78B}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {04E66DF6-70ED-4DC6-8636-9C32C2EE6D96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {04E66DF6-70ED-4DC6-8636-9C32C2EE6D96}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {04E66DF6-70ED-4DC6-8636-9C32C2EE6D96}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {04E66DF6-70ED-4DC6-8636-9C32C2EE6D96}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {8FCBDD6C-0716-48DF-936D-F109801AE78B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {8FCBDD6C-0716-48DF-936D-F109801AE78B}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {8FCBDD6C-0716-48DF-936D-F109801AE78B}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {8FCBDD6C-0716-48DF-936D-F109801AE78B}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {9CFC0B9C-EC60-4488-9D4A-79F191AA5AFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {9CFC0B9C-EC60-4488-9D4A-79F191AA5AFF}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {9CFC0B9C-EC60-4488-9D4A-79F191AA5AFF}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {9CFC0B9C-EC60-4488-9D4A-79F191AA5AFF}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {D99ADFED-D2BC-47B8-8BB4-54A87CC93433}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {D99ADFED-D2BC-47B8-8BB4-54A87CC93433}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {D99ADFED-D2BC-47B8-8BB4-54A87CC93433}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {D99ADFED-D2BC-47B8-8BB4-54A87CC93433}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {EC63C4E3-C5B8-4EB1-90CD-2D2FC17801BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {EC63C4E3-C5B8-4EB1-90CD-2D2FC17801BD}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {EC63C4E3-C5B8-4EB1-90CD-2D2FC17801BD}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {EC63C4E3-C5B8-4EB1-90CD-2D2FC17801BD}.Release|Any CPU.Build.0 = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(MonoDevelopProperties) = preSolution 42 | StartupItem = InteractiveDialogueTester\InteractiveDialogueTester.csproj 43 | EndGlobalSection 44 | EndGlobal 45 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/IfDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RelayLib; 3 | using System.Collections.Generic; 4 | 5 | namespace GrimmLib 6 | { 7 | public class IfDialogueNode : DialogueNode 8 | { 9 | ValueEntry CELL_ifTrueNode; 10 | ValueEntry CELL_elifNodes; 11 | ValueEntry CELL_ifFalseNode; 12 | 13 | protected override void SetupCells() 14 | { 15 | base.SetupCells (); 16 | CELL_ifTrueNode = EnsureCell("ifTrueNode", ""); 17 | CELL_elifNodes = EnsureCell("elifNode", new string[] {}); 18 | CELL_ifFalseNode = EnsureCell("ifFalseNode", ""); 19 | } 20 | 21 | public override void OnEnter() 22 | { 23 | string originalNextNode = nextNode; 24 | 25 | /* 26 | if(_dialogueRunner.EvaluateExpression(expression, args)) { 27 | nextNode = ifTrueNode; 28 | } 29 | else if(ifFalseNode != "") { 30 | nextNode = ifFalseNode; 31 | } 32 | */ 33 | 34 | bool hasFoundTruthyExpression = false; 35 | 36 | if(ifTrueNode.Evaluate()) { 37 | hasFoundTruthyExpression = true; 38 | nextNode = ifTrueNode.nextNode; // jumping directly to the node after the expression 39 | } 40 | 41 | if(!hasFoundTruthyExpression) { 42 | foreach(ExpressionDialogueNode e in elifNodes) { 43 | if(e.Evaluate()) { 44 | hasFoundTruthyExpression = true; 45 | nextNode = e.nextNode; 46 | } 47 | } 48 | } 49 | 50 | if(!hasFoundTruthyExpression && ifFalseNode != null) { 51 | nextNode = ifFalseNode.nextNode; 52 | } 53 | 54 | Stop(); 55 | StartNextNode(); 56 | nextNode = originalNextNode; 57 | } 58 | 59 | #region ACCESSORS 60 | 61 | public ExpressionDialogueNode ifTrueNode 62 | { 63 | get { 64 | if(CELL_ifTrueNode.data != "") { 65 | return _dialogueRunner.GetDialogueNode(conversation, CELL_ifTrueNode.data) as ExpressionDialogueNode; 66 | } 67 | else { 68 | return null; 69 | } 70 | } 71 | set { 72 | CELL_ifTrueNode.data = (value != null) ? value.name : ""; 73 | } 74 | } 75 | 76 | public ExpressionDialogueNode[] elifNodes 77 | { 78 | get { 79 | List nodes = new List(); 80 | foreach(string elifNodeNames in CELL_elifNodes.data) 81 | { 82 | nodes.Add(_dialogueRunner.GetDialogueNode(conversation, elifNodeNames) as ExpressionDialogueNode); 83 | } 84 | return nodes.ToArray(); 85 | } 86 | set { 87 | List nodeNames = new List(); 88 | foreach(ExpressionDialogueNode node in value) 89 | { 90 | nodeNames.Add(node.name); 91 | } 92 | CELL_elifNodes.data = nodeNames.ToArray(); 93 | } 94 | } 95 | 96 | public DialogueNode ifFalseNode 97 | { 98 | get { 99 | if(CELL_ifFalseNode.data != "") { 100 | return _dialogueRunner.GetDialogueNode(conversation, CELL_ifFalseNode.data); 101 | } 102 | else { 103 | return null; 104 | } 105 | } 106 | set { 107 | CELL_ifFalseNode.data = (value != null) ? value.name : ""; 108 | } 109 | } 110 | 111 | #endregion 112 | } 113 | } 114 | 115 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/ListeningDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GameTypes; 3 | using RelayLib; 4 | namespace GrimmLib 5 | { 6 | // When the ListeningDialogueNode is activated in a dialogue it registers itself with the dialogue runner 7 | // and then immediately activates the next node. 8 | // The block (starting with 'branchNode') connected with the ListeningDialogueNode will execute when the event happens. 9 | // When the conversation file that contains the ListeningDialogueNode is stopped all listeners in there will be unregistered. 10 | 11 | public class ListeningDialogueNode : DialogueNode, IRegisteredDialogueNode 12 | { 13 | ValueEntry CELL_eventName; 14 | ValueEntry CELL_isListening; 15 | ValueEntry CELL_branchNode; 16 | ValueEntry CELL_hasBranch; 17 | ValueEntry CELL_handle; 18 | 19 | public string ScopeNode() { 20 | return scopeNode; 21 | } 22 | 23 | protected override void SetupCells() 24 | { 25 | base.SetupCells (); 26 | CELL_eventName = EnsureCell("eventName", "undefined"); 27 | CELL_hasBranch = EnsureCell("hasBranch", false); 28 | CELL_branchNode = EnsureCell("branchNode", "undefined"); 29 | CELL_isListening = EnsureCell("isListening", false); 30 | CELL_handle = EnsureCell("handle", ""); 31 | } 32 | 33 | public override void OnEnter() 34 | { 35 | isListening = true; 36 | if(hasBranch) { 37 | Stop(); 38 | StartNextNode(); 39 | } 40 | } 41 | 42 | public void EventHappened() 43 | { 44 | //_dialogueRunner.logger.Log("The event of ListeningDialogueNode '" + name + "' in conversation '" + conversation + "' happened"); 45 | 46 | isListening = false; 47 | if(hasBranch) { 48 | DialogueNode n = _dialogueRunner.GetDialogueNode(conversation, branchNode); 49 | n.Start(); 50 | } 51 | else { 52 | Stop(); 53 | StartNextNode(); 54 | } 55 | } 56 | 57 | public override string ToString () 58 | { 59 | return string.Format ("[ListeningDialogueNode: eventName={0}, hasBranch={1}, branchNode={2}, isListening={3}, handle={4}]", eventName, hasBranch, branchNode, isListening, handle); 60 | } 61 | 62 | #region ACCESSORS 63 | 64 | public string eventName 65 | { 66 | get { 67 | return CELL_eventName.data; 68 | } 69 | set { 70 | CELL_eventName.data = value; 71 | } 72 | } 73 | 74 | public bool hasBranch 75 | { 76 | get { 77 | return CELL_hasBranch.data; 78 | } 79 | set { 80 | CELL_hasBranch.data = value; 81 | } 82 | } 83 | 84 | public string branchNode 85 | { 86 | get { 87 | return CELL_branchNode.data; 88 | } 89 | set { 90 | CELL_branchNode.data = value; 91 | } 92 | } 93 | 94 | public bool isListening 95 | { 96 | get { 97 | return CELL_isListening.data; 98 | } 99 | set { 100 | CELL_isListening.data = value; 101 | } 102 | } 103 | 104 | public string handle 105 | { 106 | get { 107 | return CELL_handle.data; 108 | } 109 | set { 110 | CELL_handle.data = value; 111 | } 112 | } 113 | 114 | #endregion 115 | } 116 | } 117 | 118 | -------------------------------------------------------------------------------- /InteractiveDialogueTester/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GrimmLib; 3 | using RelayLib; 4 | using System.Collections.Generic; 5 | using GameTypes; 6 | 7 | namespace InteractiveDialogueTester 8 | { 9 | class MainClass 10 | { 11 | public static void Main(string[] args) 12 | { 13 | try 14 | { 15 | RunDialogue(); 16 | } 17 | catch(Exception e) 18 | { 19 | Console.WriteLine("Error of type " + e.GetType() + " with message: " + e.Message + " callstack: " + e.StackTrace); 20 | } 21 | } 22 | 23 | static void RunDialogue() 24 | { 25 | string conversationName = "meeting"; // "PixieMeeting1"; 26 | 27 | RelayTwo relay; 28 | DialogueRunner dialogueRunner; 29 | 30 | relay = new RelayTwo(); 31 | relay.CreateTable(DialogueNode.TABLE_NAME); 32 | 33 | dialogueRunner = new DialogueRunner(relay, Language.DEFAULT); 34 | dialogueRunner.AddExpression("CoinFlip", CoinFlip); 35 | dialogueRunner.AddOnSomeoneSaidSomethingListener(OnSpeech); 36 | dialogueRunner.logger.AddListener(Log); 37 | 38 | DialogueScriptLoader scriptLoader = new DialogueScriptLoader(dialogueRunner); 39 | scriptLoader.LoadDialogueNodesFromFile(conversationName + ".dia"); 40 | 41 | DialogueScriptPrinter printer = new DialogueScriptPrinter(dialogueRunner); 42 | printer.PrintConversation(conversationName); 43 | 44 | Console.WriteLine(" - " + conversationName + " - "); 45 | dialogueRunner.StartConversation(conversationName); 46 | 47 | while(dialogueRunner.ConversationIsRunning(conversationName)) 48 | { 49 | //printer.PrintConversation(conversationName); 50 | 51 | dialogueRunner.Update(1.0f); 52 | DialogueNode activeDialogueNode = dialogueRunner.GetActiveBranchingDialogueNode(conversationName); 53 | if(activeDialogueNode is BranchingDialogueNode) 54 | { 55 | BranchingDialogueNode branchingNode = activeDialogueNode as BranchingDialogueNode; 56 | 57 | //printer.PrintConversation(conversationName); 58 | 59 | int i = 1; 60 | Console.WriteLine("Choose an alternative:"); 61 | foreach(string optionNodeName in branchingNode.nextNodes) 62 | { 63 | TimedDialogueNode optionNode = dialogueRunner.GetDialogueNode(conversationName, optionNodeName) as TimedDialogueNode; 64 | Console.WriteLine(i++ + ". " + optionNode.line); 65 | } 66 | 67 | int choice = -1; 68 | while(choice < 0 || choice > branchingNode.nextNodes.Length - 1) { 69 | try { 70 | choice = 0; //Convert.ToInt32(Console.ReadLine()) - 1; 71 | } 72 | catch { 73 | choice = -1; 74 | } 75 | } 76 | 77 | branchingNode.Choose(choice); 78 | } 79 | } 80 | } 81 | 82 | private static void OnSpeech(Speech pSpeech) 83 | { 84 | if(pSpeech.line != "") { 85 | Console.WriteLine(pSpeech.speaker + ": \"" + pSpeech.line + "\""); 86 | } 87 | } 88 | 89 | private static void Log(string pMessage) 90 | { 91 | Console.WriteLine("Log: " + pMessage); 92 | } 93 | 94 | static Random r = new Random((int)DateTime.Now.Millisecond); 95 | 96 | private static bool CoinFlip(string[] args) 97 | { 98 | return r.Next(2) == 0; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/WaitDialogueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RelayLib; 4 | 5 | namespace GrimmLib 6 | { 7 | public class WaitDialogueNode : DialogueNode, IRegisteredDialogueNode 8 | { 9 | ValueEntry CELL_branchNode; 10 | ValueEntry CELL_hasBranch; 11 | ValueEntry CELL_handle; 12 | ValueEntry CELL_isListening; 13 | ValueEntry CELL_expressions; 14 | ValueEntry CELL_eventName; 15 | 16 | public string ScopeNode() { 17 | return scopeNode; 18 | } 19 | 20 | protected override void SetupCells() 21 | { 22 | base.SetupCells(); 23 | CELL_hasBranch = EnsureCell("hasBranch", false); 24 | CELL_branchNode = EnsureCell("branchNode", "undefined"); 25 | CELL_handle = EnsureCell("handle", ""); 26 | CELL_isListening = EnsureCell("isListening", false); 27 | CELL_expressions = EnsureCell("expressions", new string[] {}); 28 | CELL_eventName = EnsureCell("eventName", ""); 29 | } 30 | 31 | public override void OnEnter() 32 | { 33 | if(hasBranch) { 34 | StartNextNode(); 35 | } 36 | isListening = true; 37 | 38 | if(eventName == "") { // If this is set the node can only trigger on events 39 | Evaluate(); 40 | } 41 | } 42 | 43 | public override void Update(float dt) 44 | { 45 | if(eventName != "") { 46 | return; 47 | } 48 | 49 | if(isListening) { 50 | Evaluate(); 51 | } 52 | else { 53 | Stop(); 54 | } 55 | } 56 | 57 | public override string ToString () 58 | { 59 | return string.Format ("[WaitDialogueNode: hasBranch={0}, branchNode={1}, handle={2}, isListening={3}, eventName={4}, conversation={5}]", hasBranch, branchNode, handle, isListening, eventName, conversation); 60 | } 61 | 62 | private void Evaluate() 63 | { 64 | foreach(ExpressionDialogueNode expressionNode in expressions) { 65 | if(expressionNode.Evaluate() == false) return; 66 | } 67 | 68 | isListening = false; 69 | 70 | Stop(); 71 | if(hasBranch) { 72 | _dialogueRunner.GetDialogueNode(conversation, branchNode).Start(); 73 | } 74 | else { 75 | StartNextNode(); 76 | } 77 | } 78 | 79 | public void EventHappened() 80 | { 81 | //_dialogueRunner.logger.Log("The event of WaitDialogueNode '" + name + "' in conversation '" + conversation + "' happened"); 82 | Evaluate(); 83 | } 84 | 85 | public bool hasBranch 86 | { 87 | get { 88 | return CELL_hasBranch.data; 89 | } 90 | set { 91 | CELL_hasBranch.data = value; 92 | } 93 | } 94 | 95 | public string branchNode 96 | { 97 | get { 98 | return CELL_branchNode.data; 99 | } 100 | set { 101 | CELL_branchNode.data = value; 102 | } 103 | } 104 | 105 | public string handle 106 | { 107 | get { 108 | return CELL_handle.data; 109 | } 110 | set { 111 | CELL_handle.data = value; 112 | } 113 | } 114 | 115 | public bool isListening 116 | { 117 | get { 118 | return CELL_isListening.data; 119 | } 120 | set { 121 | CELL_isListening.data = value; 122 | } 123 | } 124 | 125 | public string eventName 126 | { 127 | get { 128 | return CELL_eventName.data; 129 | } 130 | set { 131 | CELL_eventName.data = value; 132 | } 133 | } 134 | 135 | private ExpressionDialogueNode[] _expressionCACHE; 136 | 137 | public ExpressionDialogueNode[] expressions 138 | { 139 | get { 140 | if (_expressionCACHE == null) { 141 | List expressions = new List(); 142 | foreach (string expressionName in CELL_expressions.data) { 143 | expressions.Add(_dialogueRunner.GetDialogueNode(conversation, expressionName) as ExpressionDialogueNode); 144 | } 145 | _expressionCACHE = expressions.ToArray(); 146 | } 147 | return _expressionCACHE; 148 | } 149 | set { 150 | List expressionNames = new List(); 151 | foreach(ExpressionDialogueNode expressionNode in value) { 152 | expressionNames.Add(expressionNode.name); 153 | } 154 | CELL_expressions.data = expressionNames.ToArray(); 155 | } 156 | } 157 | } 158 | } 159 | 160 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/Nodes/DialogueNode.cs: -------------------------------------------------------------------------------- 1 | //#define LOG 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using GameTypes; 6 | using RelayLib; 7 | 8 | namespace GrimmLib 9 | { 10 | public abstract class DialogueNode : RelayObjectTwo 11 | { 12 | public const string TABLE_NAME = "Dialogues"; 13 | 14 | protected DialogueRunner _dialogueRunner; 15 | 16 | ValueEntry CELL_name; 17 | ValueEntry CELL_isOn; 18 | ValueEntry CELL_conversation; 19 | ValueEntry CELL_language; 20 | ValueEntry CELL_nextNode; 21 | ValueEntry CELL_scopeNode; 22 | 23 | bool _isOnCache; 24 | 25 | public void SetRunner(DialogueRunner pRunner) 26 | { 27 | _dialogueRunner = pRunner; 28 | } 29 | 30 | protected void Invariant() { 31 | D.assert(_dialogueRunner != null); 32 | } 33 | 34 | protected override void SetupCells() 35 | { 36 | CELL_name = EnsureCell("name", "unnamed"); 37 | CELL_isOn = EnsureCell("isOn", false); 38 | CELL_nextNode = EnsureCell("nextNode", ""); 39 | CELL_conversation = EnsureCell("conversation", ""); 40 | CELL_language = EnsureCell("language", Language.SWEDISH); 41 | CELL_scopeNode = EnsureCell("scopeNode", ""); 42 | 43 | _isOnCache = CELL_isOn.data; 44 | } 45 | 46 | public void Start() { 47 | #if LOG 48 | D.Log("Starting node " + ToString()); 49 | #endif 50 | Invariant(); 51 | isOn = true; 52 | OnEnter(); 53 | } 54 | 55 | public void Stop() { 56 | #if LOG 57 | D.Log("Stopping node " + ToString()); 58 | #endif 59 | Invariant(); 60 | isOn = false; 61 | OnExit(); 62 | } 63 | 64 | protected void StartNextNode() { 65 | if(nextNode == "") { 66 | throw new GrimmException("No nextNode in dialogue node '" + name + "' in conversation '" + conversation + "'"); 67 | } 68 | DialogueNode n = _dialogueRunner.GetDialogueNode(this.conversation, nextNode); 69 | //_dialogueRunner.logger.Log("DialogueNode '" + name + "' is starting '" + n.name + "'" + " of type " + n.GetType().ToString() + " in conversation " + n.conversation); 70 | //Console.WriteLine("DialogueNode '" + name + "' is starting '" + n.name + "'"); 71 | n.Start(); 72 | } 73 | 74 | public virtual void OnEnter() {} 75 | public virtual void OnExit() {} 76 | public virtual void Update(float dt) {} 77 | 78 | public override string ToString () 79 | { 80 | return string.Format ("[DialogueNode: name={0}, isOn={1}, nextNode={2}, conversation={3}, scopeNode={4}]", name, isOn, nextNode, conversation, scopeNode); 81 | } 82 | 83 | #region ACCESSORS 84 | 85 | public string name { 86 | get { 87 | return CELL_name.data; 88 | } 89 | set { 90 | CELL_name.data = value; 91 | } 92 | } 93 | 94 | public bool isOn { 95 | get 96 | { 97 | return _isOnCache; 98 | //return CELL_isOn.data; 99 | } 100 | private set 101 | { 102 | _isOnCache = value; 103 | CELL_isOn.data = value; 104 | if(value) { 105 | _dialogueRunner.AddToTurnOnNodeList(this); 106 | } 107 | else { 108 | _dialogueRunner.AddToTurnOffNodeList(this); 109 | } 110 | } 111 | } 112 | 113 | public string nextNode { 114 | get { 115 | return CELL_nextNode.data; 116 | } 117 | set { 118 | CELL_nextNode.data = value; 119 | } 120 | } 121 | 122 | public string conversation { 123 | get 124 | { 125 | return CELL_conversation.data; 126 | } 127 | set 128 | { 129 | CELL_conversation.data = value; 130 | } 131 | } 132 | 133 | public string scopeNode { 134 | get 135 | { 136 | return CELL_scopeNode.data; 137 | } 138 | set 139 | { 140 | CELL_scopeNode.data = value; 141 | } 142 | } 143 | 144 | public Language language 145 | { 146 | get { 147 | return CELL_language.data; 148 | } 149 | set { 150 | CELL_language.data = value; 151 | } 152 | } 153 | 154 | #endregion 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Grimm/Grimm.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.21022 7 | 2.0 8 | {04E66DF6-70ED-4DC6-8636-9C32C2EE6D96} 9 | Library 10 | Grimm 11 | Grimm 12 | v3.5 13 | 14 | 15 | True 16 | full 17 | False 18 | bin\Debug 19 | DEBUG; 20 | prompt 21 | 4 22 | False 23 | 24 | 25 | none 26 | False 27 | bin\Release 28 | prompt 29 | 4 30 | False 31 | 32 | 33 | 34 | 35 | 3.5 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 | {9CFC0B9C-EC60-4488-9D4A-79F191AA5AFF} 77 | Relay 78 | 79 | 80 | {EC63C4E3-C5B8-4EB1-90CD-2D2FC17801BD} 81 | GameTypes 82 | 83 | 84 | -------------------------------------------------------------------------------- /Grimm_Tests/bin/conversations/conversations-textmate-project.tmproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | documents 6 | 7 | 8 | filename 9 | conversation1.dia 10 | lastUsed 11 | 2012-03-21T15:18:34Z 12 | 13 | 14 | filename 15 | conversation10.dia 16 | lastUsed 17 | 2012-03-08T14:32:18Z 18 | 19 | 20 | filename 21 | conversation11.dia 22 | lastUsed 23 | 2012-03-08T14:32:18Z 24 | 25 | 26 | filename 27 | conversation12.dia 28 | lastUsed 29 | 2012-03-08T14:32:17Z 30 | 31 | 32 | filename 33 | conversation13.dia 34 | lastUsed 35 | 2012-03-08T14:32:19Z 36 | 37 | 38 | filename 39 | conversation14.dia 40 | lastUsed 41 | 2012-03-08T14:32:19Z 42 | 43 | 44 | filename 45 | conversation15.dia 46 | lastUsed 47 | 2012-03-08T14:32:19Z 48 | 49 | 50 | filename 51 | conversation16.dia 52 | lastUsed 53 | 2012-03-08T14:32:19Z 54 | 55 | 56 | filename 57 | conversation17.dia 58 | lastUsed 59 | 2012-03-21T15:18:34Z 60 | 61 | 62 | filename 63 | conversation18.dia 64 | lastUsed 65 | 2012-03-08T14:32:20Z 66 | 67 | 68 | filename 69 | conversation19.dia 70 | lastUsed 71 | 2012-03-08T14:32:20Z 72 | 73 | 74 | filename 75 | conversation2.dia 76 | lastUsed 77 | 2012-03-08T14:32:20Z 78 | 79 | 80 | filename 81 | conversation20.dia 82 | lastUsed 83 | 2012-03-08T14:32:19Z 84 | 85 | 86 | filename 87 | conversation21.dia 88 | lastUsed 89 | 2012-03-08T14:32:20Z 90 | 91 | 92 | filename 93 | conversation22.dia 94 | lastUsed 95 | 2012-03-08T14:32:17Z 96 | 97 | 98 | filename 99 | conversation3.dia 100 | lastUsed 101 | 2012-03-08T14:32:20Z 102 | 103 | 104 | filename 105 | conversation4.dia 106 | lastUsed 107 | 2012-01-25T12:36:40Z 108 | 109 | 110 | filename 111 | conversation5.dia 112 | lastUsed 113 | 2012-03-08T14:32:20Z 114 | 115 | 116 | filename 117 | conversation6.dia 118 | lastUsed 119 | 2013-01-18T15:30:52Z 120 | 121 | 122 | filename 123 | conversation7.dia 124 | lastUsed 125 | 2012-03-08T14:32:21Z 126 | 127 | 128 | filename 129 | conversation8.dia 130 | lastUsed 131 | 2012-03-08T14:32:21Z 132 | 133 | 134 | filename 135 | conversation9.dia 136 | lastUsed 137 | 2012-03-08T14:32:21Z 138 | 139 | 140 | filename 141 | conversation23.dia 142 | lastUsed 143 | 2012-02-29T09:44:39Z 144 | 145 | 146 | filename 147 | conversation25.dia 148 | lastUsed 149 | 2012-03-08T14:32:18Z 150 | 151 | 152 | filename 153 | conversation24.dia 154 | lastUsed 155 | 2013-01-18T15:30:52Z 156 | 157 | 158 | filename 159 | conversation27.dia 160 | lastUsed 161 | 2013-01-18T15:30:53Z 162 | 163 | 164 | filename 165 | conversation26.dia 166 | lastUsed 167 | 2013-01-18T15:30:53Z 168 | 169 | 170 | filename 171 | conversation28.dia 172 | lastUsed 173 | 2013-01-18T15:30:53Z 174 | 175 | 176 | filename 177 | conversation29.dia 178 | lastUsed 179 | 2013-01-18T15:30:53Z 180 | 181 | 182 | filename 183 | conversation30.dia 184 | lastUsed 185 | 2013-01-18T15:30:53Z 186 | 187 | 188 | filename 189 | conversation33.dia 190 | lastUsed 191 | 2013-01-18T15:30:53Z 192 | 193 | 194 | filename 195 | conversation31.dia 196 | lastUsed 197 | 2013-01-18T15:30:53Z 198 | 199 | 200 | filename 201 | conversation32.dia 202 | lastUsed 203 | 2013-01-18T15:30:54Z 204 | selected 205 | 206 | 207 | 208 | filename 209 | conversation33.dia 210 | 211 | 212 | filename 213 | conversation34.dia 214 | lastUsed 215 | 2013-01-18T15:30:52Z 216 | 217 | 218 | filename 219 | conversation35.dia 220 | lastUsed 221 | 2013-01-18T15:30:52Z 222 | 223 | 224 | fileHierarchyDrawerWidth 225 | 200 226 | metaData 227 | 228 | showFileHierarchyDrawer 229 | 230 | windowFrame 231 | {{413, 148}, {966, 700}} 232 | 233 | 234 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/ScriptLoader/Tokenizer.cs: -------------------------------------------------------------------------------- 1 | 2 | //#define WRITE_DEBUG 3 | 4 | using System; 5 | using System.IO; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Diagnostics; 9 | using GameTypes; 10 | 11 | namespace GrimmLib 12 | { 13 | public class Tokenizer 14 | { 15 | const string s_lettersThatWorksInNames = "ABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖabcdefghijklmnopqrstuvwxyzåäö_$£@!?"; 16 | const string s_digits = "-1234567890"; 17 | 18 | List _tokens; 19 | TextReader _textReader; 20 | 21 | bool _endOfFile; 22 | char _currentChar; 23 | int _currentLine; 24 | int _currentPosition; 25 | int _currentTokenStartPosition; 26 | 27 | public List process(TextReader pTextReader) { 28 | D.isNull(pTextReader); 29 | 30 | _tokens = new List(); 31 | _textReader = pTextReader; 32 | _endOfFile = false; 33 | 34 | readNextChar(); 35 | _currentLine = 1; 36 | _currentPosition = 0; 37 | _currentTokenStartPosition = 0; 38 | 39 | Token t; 40 | 41 | do { 42 | t = readNextToken(); 43 | t.LineNr = _currentLine; 44 | t.LinePosition = _currentTokenStartPosition; 45 | _currentTokenStartPosition = _currentPosition; 46 | 47 | _tokens.Add(t); 48 | 49 | #if WRITE_DEBUG 50 | Console.WriteLine(t.LineNr + ": " + t.getTokenType().ToString() + " " + t.getTokenString()); 51 | #endif 52 | 53 | } while(t.getTokenType() != Token.TokenType.EOF); 54 | 55 | _textReader.Close(); 56 | _textReader.Dispose(); 57 | 58 | return _tokens; 59 | } 60 | 61 | private Token readNextToken() { 62 | 63 | while (!_endOfFile) { 64 | 65 | switch(_currentChar) { 66 | case '\0': 67 | _endOfFile = true; 68 | continue; 69 | 70 | case ' ': case '\t': 71 | readNextChar(); 72 | continue; 73 | 74 | case '#': 75 | stripComment(); 76 | continue; 77 | 78 | case ':': 79 | return COLON(); 80 | 81 | case '.': 82 | return DOT(); 83 | 84 | case '\n': 85 | return NEW_LINE(); 86 | 87 | case '(': 88 | return PARANTHESIS_LEFT(); 89 | 90 | case ')': 91 | return PARANTHESIS_RIGHT(); 92 | 93 | case '{': 94 | return BLOCK_BEGIN(); 95 | 96 | case '}': 97 | return BLOCK_END(); 98 | 99 | case '[': 100 | return BRACKET_LEFT(); 101 | 102 | case ']': 103 | return BRACKET_RIGHT(); 104 | 105 | case '\"': 106 | return QUOTED_STRING(); 107 | 108 | case ',': 109 | return COMMA(); 110 | 111 | default: 112 | if( isLETTER() ) { 113 | return NAME(); 114 | } 115 | else if ( isDIGIT() ) { 116 | return NUMBER(false); 117 | } 118 | else 119 | { 120 | throw new Exception( 121 | "Unrecognized character found: \'" + 122 | _currentChar + " on line " + _currentLine + 123 | " and position" + _currentPosition); 124 | } 125 | } 126 | 127 | } 128 | 129 | return new Token(Token.TokenType.EOF, ""); 130 | } 131 | 132 | private void stripComment() 133 | { 134 | while (_currentChar != '\n' && _currentChar != '\0') 135 | { 136 | readNextChar(); 137 | } 138 | return; 139 | } 140 | 141 | private Token COLON() { 142 | readNextChar(); 143 | return new Token(Token.TokenType.COLON, ":"); 144 | } 145 | 146 | private Token DOT() { 147 | readNextChar(); 148 | return new Token(Token.TokenType.DOT, "."); 149 | } 150 | 151 | private Token COMMA() { 152 | readNextChar(); 153 | return new Token(Token.TokenType.COMMA, ","); 154 | } 155 | 156 | private Token NEW_LINE() { 157 | while(_currentChar == '\n') { // make several new-lines into a single one 158 | _currentLine++; 159 | _currentPosition = 0; 160 | readNextChar(); 161 | } 162 | return new Token(Token.TokenType.NEW_LINE, ""); 163 | } 164 | 165 | private Token PARANTHESIS_LEFT() { 166 | readNextChar(); 167 | return new Token(Token.TokenType.PARANTHESIS_LEFT, "("); 168 | } 169 | 170 | private Token PARANTHESIS_RIGHT() { 171 | readNextChar(); 172 | return new Token(Token.TokenType.PARANTHESIS_RIGHT, ")"); 173 | } 174 | 175 | private Token BRACKET_LEFT() { 176 | readNextChar(); 177 | return new Token(Token.TokenType.BRACKET_LEFT, "["); 178 | } 179 | 180 | private Token BRACKET_RIGHT() { 181 | readNextChar(); 182 | return new Token(Token.TokenType.BRACKET_RIGHT, "]"); 183 | } 184 | 185 | private Token BLOCK_BEGIN() { 186 | readNextChar(); 187 | return new Token(Token.TokenType.BLOCK_BEGIN, "{"); 188 | } 189 | 190 | private Token BLOCK_END() { 191 | readNextChar(); 192 | return new Token(Token.TokenType.BLOCK_END, "}"); 193 | } 194 | 195 | private Token QUOTED_STRING() { 196 | StringBuilder tokenString = new StringBuilder(); 197 | readNextChar(); 198 | while (_currentChar != '\"' && _currentChar != '\n' && _currentChar != '\0') 199 | { 200 | tokenString.Append(_currentChar); 201 | readNextChar(); 202 | } 203 | readNextChar(); 204 | return new Token(Token.TokenType.QUOTED_STRING, tokenString.ToString()); 205 | } 206 | 207 | private Token NAME() { 208 | StringBuilder tokenString = new StringBuilder(); 209 | do { 210 | tokenString.Append(_currentChar); 211 | readNextChar(); 212 | } while( isLETTER() || isDIGIT() ); 213 | 214 | Token.TokenType tokenType = Token.TokenType.NAME; 215 | 216 | // Keywords: 217 | // 218 | 219 | if(tokenString.ToString() == "GOTO") { 220 | tokenType = Token.TokenType.GOTO; 221 | } 222 | else if(tokenString.ToString() == "IF") { 223 | tokenType = Token.TokenType.IF; 224 | } 225 | else if(tokenString.ToString() == "ELSE") { 226 | tokenType = Token.TokenType.ELSE; 227 | } 228 | else if(tokenString.ToString() == "ELIF") { 229 | tokenType = Token.TokenType.ELIF; 230 | } 231 | else if(tokenString.ToString() == "LANGUAGE") { 232 | tokenType = Token.TokenType.LANGUAGE; 233 | } 234 | else if(tokenString.ToString() == "START") { 235 | tokenType = Token.TokenType.START; 236 | } 237 | else if(tokenString.ToString() == "WAIT") { 238 | tokenType = Token.TokenType.WAIT; 239 | } 240 | else if(tokenString.ToString() == "ASSERT") { 241 | tokenType = Token.TokenType.ASSERT; 242 | } 243 | else if(tokenString.ToString() == "LOOP") { 244 | tokenType = Token.TokenType.LOOP; 245 | } 246 | else if(tokenString.ToString() == "STOP") { 247 | tokenType = Token.TokenType.STOP; 248 | } 249 | else if(tokenString.ToString() == "BREAK") { 250 | tokenType = Token.TokenType.BREAK; 251 | } 252 | else if(tokenString.ToString() == "LISTEN") { 253 | tokenType = Token.TokenType.LISTEN; 254 | } 255 | else if(tokenString.ToString() == "BROADCAST") { 256 | tokenType = Token.TokenType.BROADCAST; 257 | } 258 | else if(tokenString.ToString() == "CANCEL") { 259 | tokenType = Token.TokenType.CANCEL; 260 | } 261 | else if(tokenString.ToString() == "FOCUS") { 262 | tokenType = Token.TokenType.FOCUS; 263 | } 264 | else if(tokenString.ToString() == "DEFOCUS") { 265 | tokenType = Token.TokenType.DEFOCUS; 266 | } 267 | else if(tokenString.ToString() == "AND") { 268 | tokenType = Token.TokenType.AND; 269 | } 270 | else if(tokenString.ToString() == "INTERRUPT") { 271 | tokenType = Token.TokenType.INTERRUPT; 272 | } 273 | else if(tokenString.ToString() == "CHOICE") { 274 | tokenType = Token.TokenType.CHOICE; 275 | } 276 | else if(tokenString.ToString() == "ETERNAL") { 277 | tokenType = Token.TokenType.ETERNAL; 278 | } 279 | 280 | return new Token(tokenType, tokenString.ToString()); 281 | } 282 | 283 | private Token NUMBER(bool pNegative) { 284 | StringBuilder tokenString = new StringBuilder(); 285 | if(pNegative) { tokenString.Append("-"); } 286 | bool period = false; 287 | do { 288 | if (_currentChar == '.' && !period) { 289 | tokenString.Append("."); 290 | readNextChar(); 291 | } else if (_currentChar == '.' && period) { 292 | throw new Exception ("Can't have several period signs in a number!"); 293 | } 294 | tokenString.Append(_currentChar); 295 | readNextChar(); 296 | } while( isDIGIT() || _currentChar == '.' ); 297 | 298 | return new Token(Token.TokenType.NUMBER, tokenString.ToString()); 299 | } 300 | 301 | private bool isLETTER() { 302 | 303 | foreach(char letter in s_lettersThatWorksInNames) { 304 | if(_currentChar == letter) return true; 305 | } 306 | return false; 307 | } 308 | 309 | private bool isDIGIT() { 310 | 311 | foreach(char digit in s_digits) { 312 | if(_currentChar == digit) return true; 313 | } 314 | return false; 315 | } 316 | 317 | void readNextChar() { 318 | 319 | int c = _textReader.Read(); 320 | if (c > 0) { 321 | _currentChar = (char)c; 322 | _currentPosition++; 323 | } 324 | else { 325 | _currentChar = '\0'; 326 | _endOfFile = true; 327 | } 328 | } 329 | } 330 | } -------------------------------------------------------------------------------- /Grimm_Tests/tests/DialogueRunnerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | using RelayLib; 5 | using GameTypes; 6 | 7 | namespace GrimmLib.tests 8 | { 9 | [TestFixture()] 10 | public class DialogueRunnerTest 11 | { 12 | [Test()] 13 | public void CreateTimedDialogueNode() 14 | { 15 | RelayTwo relay = new RelayTwo(); 16 | TableTwo table = relay.CreateTable(DialogueNode.TABLE_NAME); 17 | 18 | TimedDialogueNode t = new TimedDialogueNode(); 19 | t.CreateNewRelayEntry(table, "TimedDialogueNode"); 20 | t.timer = 100; 21 | t.Update(1.0f); 22 | 23 | Assert.AreEqual(99, t.timer, 0.001f); 24 | } 25 | 26 | List _dialogueLog; 27 | 28 | [Test()] 29 | public void UsingTheDialogueRunner() 30 | { 31 | RelayTwo relay = new RelayTwo(); 32 | relay.CreateTable(DialogueNode.TABLE_NAME); 33 | DialogueRunner runner = new DialogueRunner(relay, Language.SWEDISH); 34 | runner.AddOnSomeoneSaidSomethingListener(LogDialogue); 35 | _dialogueLog = new List(); 36 | 37 | TimedDialogueNode d1 = runner.Create("FirstConverstation", Language.SWEDISH, "DialogueNode1"); 38 | d1.nextNode = "DialogueNode2"; 39 | d1.timer = 0.5f; 40 | d1.speaker = "Helan"; 41 | d1.line = "Hi, what's up?"; 42 | 43 | TimedDialogueNode d2 = runner.Create("FirstConverstation", Language.SWEDISH, "DialogueNode2"); 44 | d2.speaker = "Halvan"; 45 | d2.line = "I'm fine, thanks"; 46 | 47 | // Frame 0 48 | d1.Start(); 49 | 50 | Assert.IsTrue(d1.isOn); 51 | Assert.IsFalse(d2.isOn); 52 | 53 | runner.LogNodesThatAreOn(); 54 | 55 | // Frame 1 56 | runner.Update(0.2f); 57 | runner.Update(0.2f); 58 | runner.Update(0.2f); 59 | runner.Update(0.2f); 60 | 61 | runner.LogNodesThatAreOn(); 62 | 63 | Assert.IsFalse(d1.isOn); 64 | Assert.IsTrue(d2.isOn); 65 | } 66 | 67 | void LogDialogue(Speech pSpeech) { 68 | Console.WriteLine(pSpeech.speaker + ": " + pSpeech.line); 69 | _dialogueLog.Add(pSpeech.line); 70 | } 71 | 72 | [Test()] 73 | public void InstantiateDialoguesFromDatabase() 74 | { 75 | { 76 | RelayTwo relay = new RelayTwo(); 77 | relay.CreateTable(DialogueNode.TABLE_NAME); 78 | DialogueRunner runner = new DialogueRunner(relay, Language.SWEDISH); 79 | 80 | TimedDialogueNode d1 = runner.Create("c", Language.SWEDISH, "d1") as TimedDialogueNode; 81 | d1.speaker = "A"; 82 | 83 | TimedDialogueNode d2 = runner.Create("c", Language.SWEDISH, "d2"); 84 | d2.speaker = "B"; 85 | 86 | relay.SaveAll("conversation.xml"); 87 | } 88 | 89 | { 90 | RelayTwo relay = new RelayTwo(); 91 | relay.LoadAll("conversation.xml"); 92 | DialogueRunner runner = new DialogueRunner(relay, Language.SWEDISH); 93 | 94 | TimedDialogueNode d1 = runner.GetDialogueNode("c", "d1") as TimedDialogueNode; 95 | TimedDialogueNode d2 = runner.GetDialogueNode("c", "d2") as TimedDialogueNode; 96 | 97 | Assert.AreEqual("A", d1.speaker); 98 | Assert.AreEqual("B", d2.speaker); 99 | } 100 | } 101 | 102 | [Test()] 103 | public void CanNotStartDialogueNodeInAnotherConversation() 104 | { 105 | RelayTwo relay = new RelayTwo(); 106 | relay.CreateTable(DialogueNode.TABLE_NAME); 107 | DialogueRunner runner = new DialogueRunner(relay, Language.SWEDISH); 108 | 109 | TimedDialogueNode n1 = runner.Create("Conversation1", Language.SWEDISH, "DialogueNode1"); 110 | n1.nextNode = "DialogueNode2"; 111 | n1.timer = 1; 112 | 113 | runner.Create("Conversation2", Language.SWEDISH, "DialogueNode2"); 114 | n1.Start(); 115 | 116 | string msg = null; 117 | 118 | D.onDLog += (pMessage) => { 119 | Console.WriteLine("DLog: " + pMessage); 120 | msg = pMessage; 121 | }; 122 | 123 | runner.Update(1.0f); 124 | runner.Update(1.0f); 125 | Assert.NotNull(msg); 126 | } 127 | 128 | [Test()] 129 | public void RemoveConversation() 130 | { 131 | RelayTwo relay = new RelayTwo(); 132 | TableTwo table = relay.CreateTable(DialogueNode.TABLE_NAME); 133 | DialogueRunner runner = new DialogueRunner(relay, Language.SWEDISH); 134 | 135 | runner.Create("Convo1", Language.SWEDISH, "Node1"); 136 | runner.Create("Convo2", Language.SWEDISH, "Node2"); 137 | runner.Create("Convo1", Language.SWEDISH, "Node3"); 138 | runner.Create("Convo2", Language.SWEDISH, "Node4"); 139 | runner.Create("Convo1", Language.SWEDISH, "Node5"); 140 | runner.Create("Convo2", Language.SWEDISH, "Node6"); 141 | 142 | Assert.IsTrue(runner.HasConversation("Convo1")); 143 | Assert.IsTrue(runner.HasConversation("Convo2")); 144 | Assert.AreEqual(6, table.GetRows().Length); 145 | 146 | runner.RemoveConversation("Convo1"); 147 | 148 | Assert.IsFalse(runner.HasConversation("Convo1")); 149 | Assert.IsTrue(runner.HasConversation("Convo2")); 150 | Assert.AreEqual(3, table.GetRows().Length); 151 | } 152 | 153 | private void LogDialogueRunner(string s) 154 | { 155 | Console.WriteLine(s); 156 | } 157 | 158 | private void OnSomeoneSaidSomething(Speech pInfo) 159 | { 160 | _lines.Add(pInfo.line); 161 | } 162 | 163 | List _lines; 164 | 165 | [Test()] 166 | public void PlayerIsPresentedWithDialogueOptions() 167 | { 168 | RelayTwo relay = new RelayTwo(); 169 | relay.CreateTable(DialogueNode.TABLE_NAME); 170 | DialogueRunner dialogueRunner = new DialogueRunner(relay, Language.SWEDISH); 171 | dialogueRunner.logger.AddListener(LogDialogueRunner); 172 | 173 | DialogueNode start = dialogueRunner.Create("Snack", Language.SWEDISH, "Start"); 174 | start.nextNode = "choice"; 175 | 176 | BranchingDialogueNode choice = dialogueRunner.Create("Snack", Language.SWEDISH, "choice"); 177 | choice.nextNodes = new string[] { "a", "b", "c" }; 178 | 179 | TimedDialogueNode a = dialogueRunner.Create("Snack", Language.SWEDISH, "a"); 180 | TimedDialogueNode b = dialogueRunner.Create("Snack", Language.SWEDISH, "b"); 181 | TimedDialogueNode c = dialogueRunner.Create("Snack", Language.SWEDISH, "c"); 182 | 183 | DialogueNode end = dialogueRunner.Create("Snack", Language.SWEDISH, "End"); 184 | 185 | a.line = "Yo"; 186 | b.line = "Howdy"; 187 | c.line = "Hola"; 188 | 189 | a.nextNode = "End"; 190 | b.nextNode = "End"; 191 | c.nextNode = "End"; 192 | a.timer = b.timer = c.timer = 1; 193 | 194 | start.Start(); 195 | start.Update(0.1f); 196 | 197 | BranchingDialogueNode branchingNode = dialogueRunner.GetActiveBranchingDialogueNode("Snack"); 198 | 199 | List options = new List(); 200 | foreach(string nextNodeName in branchingNode.nextNodes) 201 | { 202 | options.Add(nextNodeName); 203 | } 204 | 205 | Assert.AreEqual(3, options.Count); 206 | Assert.AreEqual("a", options[0]); 207 | Assert.AreEqual("b", options[1]); 208 | Assert.AreEqual("c", options[2]); 209 | 210 | DialogueNode activeDialogueNode = dialogueRunner.GetActiveBranchingDialogueNode("Snack"); 211 | 212 | Assert.AreEqual("choice", activeDialogueNode.name); 213 | 214 | dialogueRunner.AddOnSomeoneSaidSomethingListener(OnSomeoneSaidSomething); 215 | _lines = new List(); 216 | 217 | branchingNode.nextNode = "b"; 218 | 219 | for(int i = 0; i < 10; i++) { 220 | dialogueRunner.Update(0.2f); 221 | } 222 | 223 | Assert.IsFalse(start.isOn); 224 | Assert.IsFalse(choice.isOn); 225 | Assert.IsFalse(a.isOn); 226 | Assert.IsFalse(b.isOn); 227 | Assert.IsFalse(c.isOn); 228 | Assert.IsFalse(end.isOn); 229 | Assert.AreEqual(2, _lines.Count); 230 | Assert.AreEqual("Howdy", _lines[0]); 231 | Assert.AreEqual("", _lines[1]); // = the "shut up message" 232 | } 233 | 234 | [Test()] 235 | public void DontTriggerEventOnWaitingNodeUnlessEventHappensWhenExpressionIsTrue() 236 | { 237 | RelayTwo relay = new RelayTwo(); 238 | relay.CreateTable(DialogueNode.TABLE_NAME); 239 | DialogueRunner runner = new DialogueRunner(relay, Language.SWEDISH); 240 | 241 | runner.logger.AddListener (msg => Console.WriteLine ("Dialog runner log: " + msg)); 242 | 243 | bool sunny = false; 244 | 245 | runner.AddExpression ("IsSunny", new DialogueRunner.Expression (args => { 246 | return sunny; 247 | })); 248 | 249 | var expression = runner.Create("Conversation1", Language.SWEDISH, "ExpressionNode"); 250 | expression.expression = "IsSunny"; 251 | expression.args = new string[] { }; 252 | 253 | var waitNode = runner.Create("Conversation1", Language.SWEDISH, "WaitNode"); 254 | waitNode.eventName = "bam!"; 255 | waitNode.expressions = new ExpressionDialogueNode[] { 256 | expression 257 | }; 258 | 259 | var start = runner.Create("Conversation1", Language.SWEDISH, "Start"); 260 | var end = runner.Create("Conversation1", Language.SWEDISH, "End"); 261 | 262 | start.nextNode = waitNode.name; 263 | waitNode.nextNode = end.name; 264 | 265 | runner.StartConversation ("Conversation1"); 266 | runner.Update (0.1f); 267 | runner.Update (0.1f); 268 | runner.Update (0.1f); 269 | Assert.IsTrue (waitNode.isOn); 270 | Assert.IsTrue (runner.ConversationIsRunning("Conversation1")); 271 | 272 | // Event should not occur since it's not sunny yet 273 | runner.EventHappened("bam!"); 274 | runner.Update (0.1f); 275 | Assert.IsTrue (waitNode.isOn); 276 | Assert.IsTrue (runner.ConversationIsRunning("Conversation1")); 277 | 278 | sunny = true; 279 | 280 | // Now, even though it's sunny the event should still not happen since we're not bam!:ing 281 | runner.Update (0.1f); 282 | Assert.IsTrue (waitNode.isOn); 283 | Assert.IsTrue (runner.ConversationIsRunning("Conversation1")); 284 | 285 | // But if the bam! event happens while sunny, the conversation should end 286 | runner.EventHappened("bam!"); 287 | runner.Update (0.1f); 288 | runner.Update (0.1f); 289 | runner.Update (0.1f); 290 | Assert.IsFalse (waitNode.isOn); 291 | Assert.IsFalse (runner.ConversationIsRunning("Conversation1")); 292 | } 293 | } 294 | } 295 | 296 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/DialogueScriptPrinter.cs: -------------------------------------------------------------------------------- 1 | //#define WRITE_DEBUG 2 | 3 | using System; 4 | using GameTypes; 5 | using RelayLib; 6 | using System.Text; 7 | 8 | namespace GrimmLib 9 | { 10 | public class DialogueScriptPrinter 11 | { 12 | DialogueRunner _dialogueRunner; 13 | StringBuilder _output; 14 | string _conversation; 15 | int _indentationLevel; 16 | 17 | public DialogueScriptPrinter(DialogueRunner pDialogueRunner) 18 | { 19 | D.isNull(pDialogueRunner); 20 | _dialogueRunner = pDialogueRunner; 21 | } 22 | 23 | public void PrintConversation(string pConversation) 24 | { 25 | _conversation = pConversation; 26 | _indentationLevel = 0; 27 | DialogueNode startNode = _dialogueRunner.GetDialogueNode(pConversation, "__Start__"); 28 | _output = new StringBuilder(); 29 | SwitchOnNode(startNode); 30 | Console.WriteLine("Printing conversation '" + pConversation + "':"); 31 | Console.WriteLine(_output.ToString()); 32 | } 33 | 34 | private void SwitchOnNode(DialogueNode pDialogueNode) 35 | { 36 | D.isNull(pDialogueNode); 37 | 38 | if(pDialogueNode.isOn) { 39 | _output.Append("ON ---> "); 40 | } 41 | 42 | #if WRITE_DEBUG 43 | Console.WriteLine("Switching on node " + pDialogueNode.name + " with indentation level " + _indentationLevel); 44 | #endif 45 | 46 | if(pDialogueNode is BranchingDialogueNode) 47 | { 48 | PrintBranchingDialogueNode(pDialogueNode as BranchingDialogueNode); 49 | } 50 | else if(pDialogueNode is ConversationEndDialogueNode) 51 | { 52 | PrintConversationEndDialogueNode(pDialogueNode as ConversationEndDialogueNode); 53 | } 54 | else if(pDialogueNode is ConversationStartDialogueNode) 55 | { 56 | PrintConversationStartDialogueNode(pDialogueNode as ConversationStartDialogueNode); 57 | } 58 | else if(pDialogueNode is TimedDialogueNode) 59 | { 60 | PrintTimedDialogueNode(pDialogueNode as TimedDialogueNode); 61 | } 62 | else if(pDialogueNode is UnifiedEndNodeForScope) 63 | { 64 | PrintUnifiedEndNodeForScope(pDialogueNode as UnifiedEndNodeForScope); 65 | } 66 | else if(pDialogueNode is GotoDialogueNode) 67 | { 68 | PrintGotoNode(pDialogueNode as GotoDialogueNode); 69 | } 70 | else if(pDialogueNode is IfDialogueNode) 71 | { 72 | PrintIfNode(pDialogueNode as IfDialogueNode); 73 | } 74 | else if(pDialogueNode is ImmediateNode) 75 | { 76 | PrintImmediateNode(pDialogueNode as ImmediateNode); 77 | } 78 | else if(pDialogueNode is StartCommandoDialogueNode) 79 | { 80 | PrintStartCommandoDialogueNode(pDialogueNode as StartCommandoDialogueNode); 81 | } 82 | else if(pDialogueNode is StopDialogueNode) 83 | { 84 | PrintStopCommandoDialogueNode(pDialogueNode as StopDialogueNode); 85 | } 86 | else if(pDialogueNode is InterruptDialogueNode) 87 | { 88 | PrintInterruptDialogueNode(pDialogueNode as InterruptDialogueNode); 89 | } 90 | else if(pDialogueNode is WaitDialogueNode) 91 | { 92 | PrintWaitDialogueNode(pDialogueNode as WaitDialogueNode); 93 | } 94 | else if(pDialogueNode is CallFunctionDialogueNode) 95 | { 96 | PrintCallFunctionDialogueNode(pDialogueNode as CallFunctionDialogueNode); 97 | } 98 | else if(pDialogueNode is AssertDialogueNode) 99 | { 100 | PrintAssertDialogueNode(pDialogueNode as AssertDialogueNode); 101 | } 102 | else if(pDialogueNode is ListeningDialogueNode) 103 | { 104 | PrintListeningDialogueNode(pDialogueNode as ListeningDialogueNode); 105 | } 106 | else if(pDialogueNode is SilentDialogueNode) 107 | { 108 | PrintSilentDialogueNode(pDialogueNode as SilentDialogueNode); 109 | } 110 | else if(pDialogueNode is BroadcastDialogueNode) 111 | { 112 | PrintBroadcastDialogueNode(pDialogueNode as BroadcastDialogueNode); 113 | } 114 | else if(pDialogueNode is CancelDialogueNode) 115 | { 116 | PrintCancelDialogueNode(pDialogueNode as CancelDialogueNode); 117 | } 118 | else if(pDialogueNode is FocusDialogueNode) 119 | { 120 | PrintFocusNode(pDialogueNode as FocusDialogueNode); 121 | } 122 | else if(pDialogueNode is DefocusDialogueNode) 123 | { 124 | PrintDefocusNode(pDialogueNode as FocusDialogueNode); 125 | } 126 | else if(pDialogueNode is LoopDialogueNode) 127 | { 128 | PrintLoopDialogueNode(pDialogueNode as LoopDialogueNode); 129 | } 130 | else if(pDialogueNode is BreakDialogueNode) 131 | { 132 | PrintBreakDialogueNode(pDialogueNode as BreakDialogueNode); 133 | } 134 | else if(pDialogueNode is ExpressionDialogueNode) 135 | { 136 | PrintExpressionDialogueNode(pDialogueNode as ExpressionDialogueNode); 137 | } 138 | else if(pDialogueNode is TimedWaitDialogueNode) 139 | { 140 | PrintTimedWaitDialogueNode(pDialogueNode as TimedWaitDialogueNode); 141 | } 142 | else 143 | { 144 | throw new GrimmException("Don't understand node type " + pDialogueNode.GetType()); 145 | } 146 | } 147 | 148 | private void PrintTimedDialogueNode(TimedDialogueNode pTimedDialogueNode) 149 | { 150 | Indentation(); 151 | _output.Append(pTimedDialogueNode.speaker + ": \"" + pTimedDialogueNode.line + "\""); 152 | _output.Append("\n"); 153 | 154 | if(pTimedDialogueNode.nextNode != "") 155 | { 156 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pTimedDialogueNode.nextNode); 157 | SwitchOnNode(nextNode); 158 | } 159 | else 160 | { 161 | throw new GrimmException("TimedDialogueNode with name '" + pTimedDialogueNode.name + "' doesn't have a next node"); 162 | } 163 | } 164 | 165 | private void PrintImmediateNode(ImmediateNode pImmediateNode) 166 | { 167 | Indentation(); 168 | _output.Append(pImmediateNode.name + "\n"); 169 | 170 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pImmediateNode.nextNode); 171 | SwitchOnNode(nextNode); 172 | } 173 | 174 | private void PrintGotoNode(GotoDialogueNode pGotoDialogueNode) 175 | { 176 | Indentation(); 177 | _output.Append("GOTO " + pGotoDialogueNode.linkedNode + "\n"); 178 | 179 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pGotoDialogueNode.nextNode); 180 | SwitchOnNode(nextNode); 181 | } 182 | 183 | private void PrintSilentDialogueNode(SilentDialogueNode par1) 184 | { 185 | Indentation(); 186 | _output.Append("SILENT NODE (won't continue from here) \n"); 187 | } 188 | 189 | private void PrintStartCommandoDialogueNode(StartCommandoDialogueNode pStartCommandoNode) 190 | { 191 | Indentation(); 192 | _output.Append("START " + pStartCommandoNode.commando + "\n"); 193 | 194 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pStartCommandoNode.nextNode); 195 | SwitchOnNode(nextNode); 196 | } 197 | 198 | private void PrintInterruptDialogueNode(InterruptDialogueNode pInterruptNode) 199 | { 200 | Indentation(); 201 | _output.Append("INTERRUPT " + pInterruptNode.interruptingConversation + "\n"); 202 | 203 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pInterruptNode.nextNode); 204 | SwitchOnNode(nextNode); 205 | } 206 | 207 | private void PrintStopCommandoDialogueNode(StopDialogueNode pStopNode) 208 | { 209 | Indentation(); 210 | _output.Append("STOP " + pStopNode.conversationToStop + "\n"); 211 | 212 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pStopNode.nextNode); 213 | SwitchOnNode(nextNode); 214 | } 215 | 216 | private void PrintCancelDialogueNode(CancelDialogueNode pCancelNode) 217 | { 218 | Indentation(); 219 | _output.Append("CANCEL " + pCancelNode.handle + "\n"); 220 | 221 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pCancelNode.nextNode); 222 | SwitchOnNode(nextNode); 223 | } 224 | 225 | private void PrintWaitDialogueNode(WaitDialogueNode pWaitNode) 226 | { 227 | Indentation(); 228 | _output.Append("WAIT_UNTIL expressions: "); 229 | 230 | foreach(var e in pWaitNode.expressions) { 231 | _output.Append(e.expression + ", "); 232 | } 233 | if(pWaitNode.eventName != "") { 234 | _output.Append("LISTEN for event: " + pWaitNode.eventName); 235 | } 236 | _output.Append("\n"); 237 | 238 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pWaitNode.nextNode); 239 | SwitchOnNode(nextNode); 240 | } 241 | 242 | private void PrintListeningDialogueNode(ListeningDialogueNode pListeningNode) 243 | { 244 | Indentation(); 245 | _output.Append("LISTEN_FOR " + pListeningNode.eventName + " (scope: " + pListeningNode.scopeNode + ")" + pListeningNode.handle + " {\n"); 246 | 247 | if(pListeningNode.hasBranch) { 248 | _indentationLevel++; 249 | DialogueNode branchStartNode = _dialogueRunner.GetDialogueNode(_conversation, pListeningNode.branchNode); 250 | SwitchOnNode(branchStartNode); 251 | _indentationLevel--; 252 | } 253 | _output.Append("}\n"); 254 | 255 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pListeningNode.nextNode); 256 | SwitchOnNode(nextNode); 257 | } 258 | 259 | private void PrintBroadcastDialogueNode(BroadcastDialogueNode pBroadcastNode) 260 | { 261 | Indentation(); 262 | _output.Append("BROADCAST " + pBroadcastNode.eventName + "\n"); 263 | 264 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pBroadcastNode.nextNode); 265 | SwitchOnNode(nextNode); 266 | } 267 | 268 | void PrintFocusNode(FocusDialogueNode pNode) 269 | { 270 | Indentation(); 271 | _output.Append("FOCUS\n"); 272 | 273 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pNode.nextNode); 274 | SwitchOnNode(nextNode); 275 | } 276 | 277 | void PrintDefocusNode(FocusDialogueNode pNode) 278 | { 279 | Indentation(); 280 | _output.Append("DEFOCUS\n"); 281 | 282 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pNode.nextNode); 283 | SwitchOnNode(nextNode); 284 | } 285 | 286 | private void PrintCallFunctionDialogueNode(CallFunctionDialogueNode pFunctionNode) 287 | { 288 | Indentation(); 289 | 290 | StringBuilder sb = new StringBuilder(); 291 | int i = 0; 292 | foreach(string arg in pFunctionNode.args) { 293 | sb.Append(arg); 294 | i++; 295 | if(i < pFunctionNode.args.Length) { 296 | sb.Append(", "); 297 | } 298 | } 299 | 300 | _output.Append("CALL FUNCTION " + pFunctionNode.function + "(" + sb.ToString() + ")\n"); 301 | 302 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pFunctionNode.nextNode); 303 | SwitchOnNode(nextNode); 304 | } 305 | 306 | private void PrintIfNode(IfDialogueNode pIfDialogueNode) 307 | { 308 | Indentation(); 309 | 310 | _output.Append("IF"); 311 | _indentationLevel++; 312 | SwitchOnNode(pIfDialogueNode.ifTrueNode); 313 | _indentationLevel--; 314 | foreach(ExpressionDialogueNode elifNode in pIfDialogueNode.elifNodes) { 315 | _output.Append("ELIF"); 316 | _indentationLevel++; 317 | SwitchOnNode(elifNode); 318 | _indentationLevel--; 319 | } 320 | if(pIfDialogueNode.ifFalseNode != null) { 321 | _output.Append("ELSE\n"); 322 | _indentationLevel++; 323 | SwitchOnNode(pIfDialogueNode.ifFalseNode); 324 | _indentationLevel--; 325 | } 326 | 327 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pIfDialogueNode.nextNode); 328 | SwitchOnNode(nextNode); 329 | } 330 | 331 | private void PrintAssertDialogueNode(AssertDialogueNode pAssertNode) 332 | { 333 | Indentation(); 334 | 335 | _output.Append("ASSERT " + pAssertNode.expression + "\n"); 336 | 337 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pAssertNode.nextNode); 338 | SwitchOnNode(nextNode); 339 | } 340 | 341 | private void PrintLoopDialogueNode(LoopDialogueNode pLoopNode) 342 | { 343 | Indentation(); 344 | 345 | _output.Append("LOOP{\n"); 346 | 347 | _indentationLevel++; 348 | DialogueNode branchStartNode = _dialogueRunner.GetDialogueNode(_conversation, pLoopNode.branchNode); 349 | SwitchOnNode(branchStartNode); 350 | _indentationLevel--; 351 | 352 | _output.Append("}\n"); 353 | 354 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pLoopNode.nextNode); 355 | SwitchOnNode(nextNode); 356 | } 357 | 358 | private void PrintBreakDialogueNode(BreakDialogueNode pBreakNode) 359 | { 360 | Indentation(); 361 | 362 | _output.Append("BREAK\n"); 363 | 364 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pBreakNode.nextNode); 365 | SwitchOnNode(nextNode); 366 | } 367 | 368 | private void PrintExpressionDialogueNode(ExpressionDialogueNode pExpressionNode) 369 | { 370 | Indentation(); 371 | 372 | _output.Append("Expression " + pExpressionNode.expression + "\n"); 373 | 374 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pExpressionNode.nextNode); 375 | SwitchOnNode(nextNode); 376 | } 377 | 378 | private void PrintTimedWaitDialogueNode(TimedWaitDialogueNode pTimedWaitDialogueNode) 379 | { 380 | Indentation(); 381 | 382 | _output.Append("TimedWaitDialogueNode\n"); 383 | 384 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pTimedWaitDialogueNode.nextNode); 385 | SwitchOnNode(nextNode); 386 | } 387 | 388 | private void PrintBranchingDialogueNode(BranchingDialogueNode pBranchingDialogueNode) 389 | { 390 | D.isNull(pBranchingDialogueNode); 391 | 392 | Indentation(); 393 | _output.Append("{\n"); 394 | _indentationLevel++; 395 | 396 | int optionNr = 1; 397 | foreach(string s in pBranchingDialogueNode.nextNodes) 398 | { 399 | TimedDialogueNode optionNode = _dialogueRunner.GetDialogueNode(_conversation, s) as TimedDialogueNode; 400 | D.isNull(optionNode); 401 | 402 | Indentation(); 403 | _output.Append(optionNr++ + ". \"" + optionNode.line + "\":\n"); 404 | 405 | D.assert(optionNode.nextNode != ""); 406 | DialogueNode nodePointedToByOption = _dialogueRunner.GetDialogueNode(_conversation, optionNode.nextNode); 407 | D.isNull(nodePointedToByOption); 408 | 409 | _indentationLevel++; 410 | SwitchOnNode(nodePointedToByOption); 411 | _indentationLevel--; 412 | } 413 | 414 | _indentationLevel--; 415 | Indentation(); 416 | _output.Append("}\n"); 417 | 418 | D.assert(pBranchingDialogueNode.name != ""); 419 | UnifiedEndNodeForScope unifiedEndNodeForScope = _dialogueRunner.GetDialogueNode(_conversation, pBranchingDialogueNode.unifiedEndNodeForScope) as UnifiedEndNodeForScope; 420 | D.isNull(unifiedEndNodeForScope); 421 | D.assert(unifiedEndNodeForScope.name != ""); 422 | //Console.WriteLine("Unified end node " + unifiedEndNodeForScope.name + " has next node " + unifiedEndNodeForScope.nextNode); 423 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, unifiedEndNodeForScope.nextNode); 424 | //Console.WriteLine("Next node has type " + nextNode.GetType()); 425 | D.isNull(nextNode); 426 | 427 | SwitchOnNode(nextNode); 428 | } 429 | 430 | private void PrintUnifiedEndNodeForScope(UnifiedEndNodeForScope pUnifiedEndNodeForScope) 431 | { 432 | D.isNull(pUnifiedEndNodeForScope); 433 | 434 | //Indentation(); 435 | //_output.Append("end\n"); 436 | } 437 | 438 | private void PrintConversationStartDialogueNode(ConversationStartDialogueNode pConversationStartDialogueNode) 439 | { 440 | _output.Append("Start -> \n"); 441 | DialogueNode nextNode = _dialogueRunner.GetDialogueNode(_conversation, pConversationStartDialogueNode.nextNode); 442 | SwitchOnNode(nextNode); 443 | } 444 | 445 | private void PrintConversationEndDialogueNode(ConversationEndDialogueNode pConversationEndDialogueNode) 446 | { 447 | Indentation(); 448 | _output.Append("-> End\n"); 449 | } 450 | 451 | private void Indentation() 452 | { 453 | for(int i = 0; i < _indentationLevel; i++) 454 | { 455 | _output.Append("\t"); 456 | } 457 | } 458 | } 459 | } 460 | 461 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/DialogueRunner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using RelayLib; 5 | using GameTypes; 6 | using System.Reflection; 7 | using System.IO; 8 | 9 | namespace GrimmLib 10 | { 11 | /// 12 | /// Main responsibilities are to instantiate and update dialogue nodes. 13 | /// DialogueNodes also uses the OnSomeoneSaidSomething-event to send out Speech data. 14 | /// Expression- and Function delegates can be registered so that they are accessible from the scripts. 15 | /// 16 | public class DialogueRunner 17 | { 18 | public delegate void OnSomeoneSaidSomething(Speech pSpeech); 19 | public delegate bool Expression(string[] args); 20 | public delegate void Function (string[] args); 21 | 22 | public delegate void OnFocusConversation (string pConversation); 23 | 24 | public delegate void OnEvent (string pEventName); 25 | 26 | public Logger logger = new Logger(); 27 | private TableTwo _dialogueTable; 28 | private Language _language; 29 | private List _dialogueNodes; 30 | private Dictionary> _nodesForConversation = new Dictionary>(); 31 | 32 | private event OnSomeoneSaidSomething _onSomeoneSaidSomething; 33 | 34 | private Dictionary _expressions = new Dictionary(); 35 | private Dictionary _functions = new Dictionary(); 36 | private List _registeredDialogueNodes = new List(); 37 | 38 | private event OnFocusConversation _onFocusConversation, _onDefocusConversation; 39 | private event OnEvent _onEvent; 40 | 41 | public Action onGrimmError; 42 | 43 | private List _nodesThatAreOn = new List(); 44 | private List _nodesToTurnOn = new List(); 45 | private List _nodesToTurnOff = new List(); 46 | 47 | public void AddToTurnOnNodeList (DialogueNode pNodeToTurnOn) 48 | { 49 | _nodesToTurnOn.Add(pNodeToTurnOn); 50 | } 51 | 52 | public void AddToTurnOffNodeList (DialogueNode pNodeToTurnOff) 53 | { 54 | _nodesToTurnOff.Add(pNodeToTurnOff); 55 | } 56 | 57 | public DialogueRunner(RelayTwo pRelay, Language pLanguage) 58 | { 59 | D.isNull(pRelay); 60 | 61 | _dialogueTable = pRelay.GetTable(DialogueNode.TABLE_NAME); 62 | _language = pLanguage; 63 | _dialogueNodes = InstantiatorTwo.Process(_dialogueTable); 64 | foreach(DialogueNode n in _dialogueNodes) { 65 | n.SetRunner(this); 66 | if(n is IRegisteredDialogueNode) { 67 | IRegisteredDialogueNode ir = n as IRegisteredDialogueNode; 68 | _registeredDialogueNodes.Add(ir); 69 | } 70 | if(n.isOn) { 71 | _nodesThatAreOn.Add(n); 72 | } 73 | AddNodeToNodeForConversationDictionary(n.conversation, n); 74 | } 75 | RegisterBuiltInAPIExpressions(); 76 | } 77 | 78 | void AddNodeToNodeForConversationDictionary (string pConversation, DialogueNode n) 79 | { 80 | List maybeList = null; 81 | if(!_nodesForConversation.TryGetValue(pConversation, out maybeList)) { 82 | maybeList = new List(); 83 | _nodesForConversation.Add(pConversation, maybeList); 84 | } 85 | //D.Log("Adding " + n.name + " to NodeForConversationDictionary, with conversation " + pConversation); 86 | maybeList.Add(n); 87 | } 88 | 89 | public T Create(string pConversation, Language pLanguage, string pName) where T : DialogueNode 90 | { 91 | T newDialogueNode = Activator.CreateInstance(); 92 | 93 | newDialogueNode.CreateNewRelayEntry(_dialogueTable, newDialogueNode.GetType().Name); 94 | newDialogueNode.conversation = pConversation; 95 | newDialogueNode.language = pLanguage; 96 | newDialogueNode.name = pName; 97 | newDialogueNode.SetRunner(this); 98 | _dialogueNodes.Add(newDialogueNode); 99 | AddNodeToNodeForConversationDictionary(pConversation, newDialogueNode); 100 | if(newDialogueNode is IRegisteredDialogueNode) { 101 | IRegisteredDialogueNode ir = newDialogueNode as IRegisteredDialogueNode; 102 | _registeredDialogueNodes.Add(ir); 103 | } 104 | return newDialogueNode; 105 | } 106 | 107 | public void LogNodesThatAreOn() { 108 | Console.WriteLine("Nodes that are on"); 109 | foreach (DialogueNode d in _nodesThatAreOn) { 110 | Console.WriteLine("- " + d.name); 111 | } 112 | Console.WriteLine("-----------------"); 113 | } 114 | 115 | public List GetActiveNodes (string pConversation) 116 | { 117 | var activeNodes = new List(); 118 | 119 | foreach (DialogueNode d in _nodesThatAreOn) { 120 | if(d.conversation == pConversation) { 121 | activeNodes.Add(d); 122 | } 123 | } 124 | 125 | return activeNodes; 126 | } 127 | 128 | public void Update(float dt) 129 | { 130 | foreach (DialogueNode d in _nodesThatAreOn) { 131 | try { 132 | //Console.WriteLine("Will update node " + d.name); 133 | if(d.isOn) { 134 | d.Update(dt); 135 | } 136 | } 137 | catch(Exception e) { 138 | string description = d.name + ": " + e.ToString (); 139 | D.Log ("GRIMM ERROR: " + description); 140 | if (onGrimmError != null) { 141 | onGrimmError (description); 142 | } 143 | } 144 | } 145 | 146 | foreach(var nodeToTurnOn in _nodesToTurnOn) { 147 | //Console.WriteLine("Will turn on " + nodeToTurnOn.name); 148 | if(_nodesThatAreOn.Find(n => n == nodeToTurnOn) != null) { 149 | // already there 150 | // Console.WriteLine("already there!"); 151 | continue; 152 | } else { 153 | _nodesThatAreOn.Add(nodeToTurnOn); 154 | } 155 | } 156 | _nodesToTurnOn.Clear(); 157 | 158 | foreach(var nodeToTurnOff in _nodesToTurnOff) { 159 | // Console.WriteLine("Will turn off " + nodeToTurnOff.name); 160 | if(_nodesThatAreOn.Find(n => n == nodeToTurnOff) != null) { 161 | // Console.WriteLine("couldn't find it!"); 162 | _nodesThatAreOn.Remove(nodeToTurnOff); 163 | } 164 | } 165 | _nodesToTurnOff.Clear(); 166 | } 167 | 168 | public DialogueNode GetDialogueNode(string pConversation, string pName) 169 | { 170 | List nodesInConvo = GetNodesForConversation (pConversation); 171 | 172 | DialogueNode n = nodesInConvo.Find(o => (o.name == pName)); 173 | 174 | /*o.language == _language && */ 175 | 176 | if(n != null) { 177 | return n; 178 | } else { 179 | // DialogueNode ignoreLanguage = _dialogueNodes.Find(o => (o.conversation == pConversation && o.name == pName)); 180 | // if(ignoreLanguage != null) { 181 | // throw new GrimmException("Can't find DialogueNode with name '" + pName + "' in conversation '" + pConversation + "'" + " when using language " + _language); 182 | // } else { 183 | // } 184 | throw new GrimmException("Can't find DialogueNode with name '" + pName + "' in conversation '" + pConversation + "'"); 185 | } 186 | } 187 | 188 | public List GetAllNodes() { 189 | return _dialogueNodes; 190 | } 191 | 192 | public List GetRegisteredDialogueNodes() 193 | { 194 | return _registeredDialogueNodes; 195 | } 196 | 197 | List GetNodesForConversation (string pConversation) 198 | { 199 | if(string.IsNullOrEmpty(pConversation)) { 200 | return new List(); 201 | } 202 | 203 | if(pConversation == runStringAsFunctionCMD) { 204 | return _dialogueNodes.FindAll (n => n.conversation == pConversation); 205 | } 206 | 207 | List maybeNodes = null; 208 | 209 | if(!_nodesForConversation.TryGetValue(pConversation, out maybeNodes)) { 210 | D.Log("Can't find conversation " + pConversation + " in _nodesForConversation dictionary"); 211 | maybeNodes = new List(); 212 | } 213 | 214 | return maybeNodes; 215 | } 216 | 217 | /// 218 | /// Returns null if there is no active branching dialogue node in the conversation 219 | /// 220 | public BranchingDialogueNode GetActiveBranchingDialogueNode(string pConversation) 221 | { 222 | List nodesInConvo = GetNodesForConversation (pConversation); 223 | 224 | BranchingDialogueNode n = nodesInConvo.Find(o => 225 | (o.isOn) && 226 | (o.language == _language) && 227 | (o is BranchingDialogueNode) 228 | ) as BranchingDialogueNode; 229 | return n; 230 | } 231 | 232 | private TimedDialogueNode GetActiveTimedDialogueNode(string pConversation) 233 | { 234 | List nodesInConvo = GetNodesForConversation (pConversation); 235 | 236 | TimedDialogueNode n = nodesInConvo.Find(o => 237 | (o.isOn) && 238 | (o.language == _language) && 239 | (o is TimedDialogueNode) 240 | ) as TimedDialogueNode; 241 | return n; 242 | } 243 | 244 | public void FastForwardCurrentTimedDialogueNode(string pConversation) 245 | { 246 | var activeTimedNode = GetActiveTimedDialogueNode(pConversation); 247 | 248 | if (activeTimedNode == null) { 249 | //D.Log("Can't fast forward in " + pConversation + " since it's not on a timed dialogue node"); 250 | } else if((activeTimedNode.timerStartValue - activeTimedNode.timer) < 0.5f) { // < (activeTimedNode.timerStartValue * 0.5f)) { 251 | //D.Log("Will not fast forward in " + pConversation + " since it just switched to a new node"); 252 | } else { 253 | activeTimedNode.timer = 0.01f; 254 | //D.Log("Fast forwarded timed dialogue node " + activeTimedNode); 255 | } 256 | } 257 | 258 | private void CheckThatThereIsOnlyOneActiveNodeInTheConversation(string pConversation) 259 | { 260 | List nodesInConvo = GetNodesForConversation (pConversation); 261 | 262 | DialogueNode[] nodes = nodesInConvo.FindAll(o => (o.language == _language && o.isOn)).ToArray(); 263 | 264 | if(nodes.Length > 1) { 265 | StringBuilder sb = new StringBuilder(); 266 | foreach(var node in nodes) { 267 | sb.Append(node.name + ", "); 268 | } 269 | throw new GrimmException("There are " + nodes.Length + " active nodes in the conversation " + pConversation + ": " + sb.ToString()); 270 | } 271 | } 272 | 273 | public bool ConversationIsRunning(string pConversation) 274 | { 275 | List nodesInConvo = GetNodesForConversation (pConversation); 276 | 277 | return (nodesInConvo.Find(o => (o.language == _language && o.isOn)) != null); 278 | } 279 | 280 | /// 281 | /// Looks in a conversation for a ConversationStartDialogueNode 282 | /// 283 | public void StartConversation(string pConversation) 284 | { 285 | if(ConversationIsRunning(pConversation)) { 286 | logger.Log("Trying to start conversation " + pConversation + " again, even though it's already running"); 287 | return; 288 | } 289 | 290 | List nodesInConvo = GetNodesForConversation (pConversation); 291 | 292 | DialogueNode conversationStartNode = 293 | nodesInConvo.Find(o => (o.language == _language && o is ConversationStartDialogueNode)); 294 | 295 | if(conversationStartNode != null) { 296 | logger.Log("Starting conversation '" + pConversation + "'"); 297 | conversationStartNode.Start(); 298 | } 299 | else { 300 | if(HasConversation(pConversation)) { 301 | throw new GrimmException("Can't find a ConversationStartDialogueNode in conversations '" + pConversation + "'"); 302 | } 303 | else { 304 | throw new GrimmException("The dialogue runner doesn't contain the conversation '" + pConversation + "'"); 305 | } 306 | } 307 | } 308 | 309 | /// 310 | /// Start all non-running conversations with a name containing some string 311 | /// 312 | public string[] StartAllConversationsContaining(string pPartialName) 313 | { 314 | return DoSomethingToAllConversationsContaining (pPartialName, o => !ConversationIsRunning(o.conversation), convStartNode => convStartNode.Start(), "Started"); 315 | } 316 | 317 | /// 318 | /// Stop all running conversations with a name containing some string 319 | /// 320 | public string[] StopAllConversationsContaining(string pPartialName) 321 | { 322 | return DoSomethingToAllConversationsContaining (pPartialName, o => ConversationIsRunning(o.conversation), convStartNode => StopConversation(convStartNode.conversation), "Stopped"); 323 | } 324 | 325 | private string[] DoSomethingToAllConversationsContaining(string pPartialName, Predicate pPred, Action pAction, string pDescription) 326 | { 327 | var conversations = GetAllConversationsWithNameContaining (pPartialName, pPred); 328 | conversations.ForEach (pAction); 329 | var names = conversations.ConvertAll (o => o.conversation).ToArray (); 330 | if (conversations.Count > 0) { 331 | logger.Log (pDescription + " " + conversations.Count + " conversations with partial name " + pPartialName + ": " + string.Join (", ", names)); 332 | } 333 | return names; 334 | } 335 | 336 | private List GetAllConversationsWithNameContaining(string pPartialName, Predicate pPred) 337 | { 338 | var foundNodes = new List (); 339 | foreach (var node in _dialogueNodes) { 340 | if (node is ConversationStartDialogueNode && 341 | node.conversation.Contains (pPartialName) && 342 | pPred(node)) 343 | { 344 | foundNodes.Add (node as ConversationStartDialogueNode); 345 | } 346 | } 347 | return foundNodes; 348 | } 349 | 350 | public string[] GetNamesOfAllStoppedConversationsWithNameContaining(string pPartialName) 351 | { 352 | return GetAllConversationsWithNameContaining(pPartialName, o => !ConversationIsRunning(o.conversation)).ConvertAll (o => o.conversation).ToArray(); 353 | } 354 | 355 | public void StopConversation(string pConversation) 356 | { 357 | logger.Log("Stopping conversation '" + pConversation + "'"); 358 | 359 | List nodesInConvo = GetNodesForConversation (pConversation); 360 | 361 | foreach(DialogueNode n in nodesInConvo) { 362 | if(n.isOn) { 363 | //D.Log("Stopping node " + n); 364 | n.Stop(); 365 | } 366 | var r = n as IRegisteredDialogueNode; 367 | if(r != null) { 368 | //D.Log("Stopping listening node " + n); 369 | r.isListening = false; 370 | n.Stop(); // <-- THIS IS NEW, MAKE SURE IT DOESN'T CAUSE TROUBLE! Trying to get conversations to stop ALL nodes. 371 | } 372 | } 373 | } 374 | 375 | public void RemoveConversation(string pConversation) 376 | { 377 | var nodes = GetNodesForConversation(pConversation); 378 | 379 | foreach (DialogueNode d in nodes) 380 | { 381 | _nodesToTurnOff.Add(d); 382 | _dialogueNodes.Remove(d); 383 | if(d is IRegisteredDialogueNode) { 384 | IRegisteredDialogueNode ir = d as IRegisteredDialogueNode; 385 | _registeredDialogueNodes.Remove(ir); 386 | } 387 | _dialogueTable.RemoveRowAt(d.objectId); 388 | } 389 | 390 | if(_nodesForConversation.ContainsKey(pConversation)) { 391 | _nodesForConversation.Remove(pConversation); 392 | } 393 | } 394 | 395 | public bool HasConversation(string pConversation) 396 | { 397 | int nrOfNodesForConversation = 0; 398 | foreach(DialogueNode n in _dialogueNodes) 399 | { 400 | if(n.conversation == pConversation) 401 | { 402 | nrOfNodesForConversation++; 403 | } 404 | } 405 | return (nrOfNodesForConversation > 0); 406 | } 407 | 408 | public void AddOnSomeoneSaidSomethingListener(OnSomeoneSaidSomething pOnSomeoneSaidSomething) 409 | { 410 | _onSomeoneSaidSomething += pOnSomeoneSaidSomething; 411 | } 412 | 413 | public void RemoveOnSomeoneSaidSomethingListener(OnSomeoneSaidSomething pOnSomeoneSaidSomething) 414 | { 415 | _onSomeoneSaidSomething -= pOnSomeoneSaidSomething; 416 | } 417 | 418 | internal void SomeoneSaidSomething(Speech pSpeech) 419 | { 420 | D.isNull(pSpeech); 421 | if(_onSomeoneSaidSomething != null) { 422 | _onSomeoneSaidSomething(pSpeech); 423 | } 424 | else { 425 | logger.Log("No listeners to dialogue runner"); 426 | } 427 | } 428 | 429 | public void AddExpression(string pName, Expression pExpression) 430 | { 431 | _expressions[pName] = pExpression; 432 | } 433 | 434 | public bool HasExpression(string pName) 435 | { 436 | return _expressions.ContainsKey(pName); 437 | } 438 | 439 | public bool EvaluateExpression(string pExpressionName, string[] args) 440 | { 441 | #if DEBUG 442 | if(!_expressions.ContainsKey(pExpressionName)) { 443 | string msg = "Can't find expression '" + pExpressionName + "' in Dialogue Runner"; 444 | D.Log("ERROR: " + msg); 445 | 446 | if (onGrimmError != null) { 447 | onGrimmError (msg); 448 | } 449 | 450 | //throw new GrimmException(msg); 451 | } 452 | #endif 453 | //Console.WriteLine(System.Environment.StackTrace.ToString()); 454 | 455 | Expression e = _expressions[pExpressionName]; 456 | bool result = e(args); 457 | if(result) { 458 | //logger.Log("Result of expression '" + pExpressionName + "' was true!"); 459 | } else { 460 | //logger.Log("Result of expression '" + pExpressionName + "' was false..."); 461 | } 462 | return result; 463 | } 464 | 465 | public string GetExpressionsAsString() 466 | { 467 | string[] keys = new string[_expressions.Count]; 468 | int i = 0; 469 | foreach(string s in _expressions.Keys) { 470 | keys[i++] = s; 471 | } 472 | return string.Join(", ", keys); 473 | } 474 | 475 | public void AddFunction(string pName, Function pFunction) 476 | { 477 | _functions[pName] = pFunction; 478 | } 479 | 480 | public bool HasFunction(string pName) 481 | { 482 | return _functions.ContainsKey(pName); 483 | } 484 | 485 | public void CallFunction(string pFunctionName, string[] args) 486 | { 487 | if (_functions.ContainsKey (pFunctionName)) { 488 | Function f = _functions [pFunctionName]; 489 | f (args); 490 | } else { 491 | string msg = "Can't find function '" + pFunctionName + "' in Dialogue Runner"; 492 | D.Log ("ERROR! " + msg); 493 | //throw new GrimmException(msg); 494 | 495 | if (onGrimmError != null) { 496 | onGrimmError (msg); 497 | } 498 | } 499 | } 500 | 501 | public string GetFunctionsAsString() 502 | { 503 | string[] keys = new string[_functions.Count]; 504 | int i = 0; 505 | foreach(string s in _functions.Keys) { 506 | keys[i++] = s; 507 | } 508 | return string.Join(", ", keys); 509 | } 510 | 511 | const string runStringAsFunctionCMD = "CMD"; 512 | 513 | public void RunStringAsFunction(string pCommand) 514 | { 515 | RemoveConversation(runStringAsFunctionCMD); 516 | DialogueScriptLoader d = new DialogueScriptLoader(this); 517 | d.CreateDialogueNodesFromString(pCommand, runStringAsFunctionCMD); 518 | StartConversation(runStringAsFunctionCMD); 519 | } 520 | 521 | public void AddOnEventListener(OnEvent pOnEvent) 522 | { 523 | _onEvent += pOnEvent; 524 | } 525 | 526 | public void RemoveOnEventListener(OnEvent pOnEvent) 527 | { 528 | _onEvent -= pOnEvent; 529 | } 530 | 531 | public void EventHappened(string pEventName) 532 | { 533 | //logger.Log("Event [" + pEventName + "]"); 534 | foreach(IRegisteredDialogueNode listeningNode in _registeredDialogueNodes.ToArray()) 535 | { 536 | if(listeningNode.isListening && listeningNode.eventName == pEventName) { 537 | listeningNode.EventHappened(); 538 | } 539 | } 540 | 541 | if(_onEvent != null) { 542 | _onEvent(pEventName); 543 | } 544 | } 545 | 546 | public void ScopeEnded(string pConversation, string pScopeNode) 547 | { 548 | logger.Log("Scope '" + pScopeNode + "' for conversation '" + pConversation + "' ended"); 549 | foreach(IRegisteredDialogueNode registeredNode in _registeredDialogueNodes) 550 | { 551 | if(registeredNode.conversation == pConversation) { 552 | if (registeredNode.ScopeNode() == pScopeNode) { 553 | if (registeredNode.isListening) { 554 | //logger.Log ("Stop listening: " + registeredNode.name + ": " + registeredNode.eventName); 555 | registeredNode.isListening = false; 556 | } 557 | else { 558 | //logger.Log ("Not listening: " + registeredNode.name + ": " + registeredNode.eventName); 559 | } 560 | } else { 561 | //logger.Log ("Not same scope: " + registeredNode.name + "; it has scope node '" + registeredNode.ScopeNode() + "'"); 562 | } 563 | } 564 | } 565 | } 566 | 567 | public void CancelRegisteredNode(string pConversation, string pListenerHandle) 568 | { 569 | foreach(IRegisteredDialogueNode registeredNode in _registeredDialogueNodes) 570 | { 571 | if(registeredNode.conversation == pConversation && registeredNode.handle == pListenerHandle) { 572 | logger.Log("Cancelled node " + registeredNode.name + " in conversation " + pConversation); 573 | registeredNode.isListening = false; 574 | if (registeredNode is ListeningDialogueNode) { 575 | //logger.Log ("Also end scope for listening node " + registeredNode.name); 576 | var listeningNode = (registeredNode as ListeningDialogueNode); 577 | ScopeEnded (pConversation, listeningNode.name); 578 | } 579 | else if (registeredNode is WaitDialogueNode) { 580 | //logger.Log ("Also end scope for wait node " + registeredNode.name); 581 | var waitingNode = (registeredNode as WaitDialogueNode); 582 | ScopeEnded (pConversation, waitingNode.name); 583 | } 584 | } 585 | } 586 | } 587 | 588 | public bool IsWaitingOnEvent(string pEventName) 589 | { 590 | foreach(IRegisteredDialogueNode l in _registeredDialogueNodes) 591 | { 592 | if(l.isListening && l.eventName == pEventName) { 593 | return true; 594 | } 595 | } 596 | return false; 597 | } 598 | 599 | public void AddFocusConversationListener(OnFocusConversation pOnFocusConversation) 600 | { 601 | _onFocusConversation += pOnFocusConversation; 602 | } 603 | 604 | public void RemoveFocusConversationListener(OnFocusConversation pOnFocusConversation) 605 | { 606 | _onFocusConversation -= pOnFocusConversation; 607 | } 608 | 609 | public void AddDefocusConversationListener(OnFocusConversation pOnDefocusConversation) 610 | { 611 | _onDefocusConversation += pOnDefocusConversation; 612 | } 613 | 614 | public void RemoveDefocusConversationListener(OnFocusConversation pOnDefocusConversation) 615 | { 616 | _onDefocusConversation -= pOnDefocusConversation; 617 | } 618 | 619 | /// 620 | /// The active conversation is the conversation that the player currently is engeged in, and doing input to 621 | /// 622 | public void FocusConversation(string pConversation) 623 | { 624 | if(_onFocusConversation != null) { 625 | _onFocusConversation(pConversation); 626 | } 627 | else { 628 | throw new GrimmException("Trying to focus conversation " + pConversation + " but there is no onFocusConversation listener."); 629 | } 630 | } 631 | 632 | public void DefocusConversation(string pConversation) 633 | { 634 | if(_onDefocusConversation != null) { 635 | _onDefocusConversation(pConversation); 636 | EventHappened("Defocused_" + pConversation); 637 | } 638 | else { 639 | //throw new GrimmException("Trying to defocus conversation " + pConversation + " but there is no onDefocusConversation listener."); 640 | D.Log("Trying to defocus conversation " + pConversation + " but there is no onDefocusConversation listener."); 641 | } 642 | } 643 | 644 | public Language language { 645 | set { 646 | _language = value; 647 | } 648 | } 649 | 650 | private string RegisteredNodesAsString() { 651 | var registeredNodes = GetRegisteredDialogueNodes(); 652 | var registeredNodeNames = new List(); 653 | foreach(var node in registeredNodes) { 654 | registeredNodeNames.Add("[" + node.name + " in '" + node.conversation + "' " + (node.isListening ? "LISTENING" : "NOT listening") + "]"); 655 | } 656 | return "Registered nodes: " + string.Join(", ", registeredNodeNames.ToArray()); 657 | } 658 | 659 | public override string ToString() 660 | { 661 | return string.Format("DialogueRunner ({0} dialogue nodes, {1} registered dialogue nodes)", _dialogueNodes.Count, _registeredDialogueNodes.Count); 662 | } 663 | 664 | public string GetAllDialogueAsString() 665 | { 666 | var sb = new StringBuilder(); 667 | foreach (var node in _dialogueNodes) { 668 | if (node is TimedDialogueNode) { 669 | sb.Append((node as TimedDialogueNode).line + " "); 670 | if (Randomizer.OneIn(10)) { 671 | sb.AppendLine(""); 672 | } 673 | } 674 | } 675 | return sb.ToString(); 676 | } 677 | 678 | public HashSet GetActiveConversations() 679 | { 680 | var activeConversations = new HashSet(); 681 | foreach (var node in _dialogueNodes) { 682 | if (node.isOn) { 683 | activeConversations.Add(node.conversation); 684 | } 685 | } 686 | return activeConversations; 687 | } 688 | 689 | private void RegisterBuiltInAPIExpressions() 690 | { 691 | AddExpression("IsActive", IsActive); 692 | AddFunction ("StartAll", StartAll); 693 | AddFunction ("StopAll", StopAll); 694 | } 695 | 696 | // IsActive(string conversation) 697 | private bool IsActive(string[] args) 698 | { 699 | return ConversationIsRunning(args[0]); 700 | } 701 | 702 | // StartAll(string pPartialName) 703 | private void StartAll(string[] args) { 704 | StartAllConversationsContaining (args [0]); 705 | } 706 | 707 | // StopAll(string pPartialName) 708 | private void StopAll(string[] args) { 709 | StopAllConversationsContaining (args [0]); 710 | } 711 | } 712 | } 713 | 714 | -------------------------------------------------------------------------------- /Grimm/src/Dialogue/ScriptLoader/DialogueScriptLoader.cs: -------------------------------------------------------------------------------- 1 | 2 | //#define DEBUG_WRITE 3 | //#define WRITE_NODE_LINKS 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using RelayLib; 8 | using GameTypes; 9 | using System.Text; 10 | using System.IO; 11 | 12 | namespace GrimmLib 13 | { 14 | public class DialogueScriptLoader 15 | { 16 | const string NAME_OF_START_NODE = "__Start__"; 17 | const string NAME_OF_END_NODE = "__End__"; 18 | 19 | DialogueRunner _dialogueRunner; 20 | string _playerCharacterName = "Sebastian"; 21 | string _conversationName; 22 | Language _language; 23 | List _tokens; 24 | Token[] _lookahead; 25 | int _lookaheadIndex = 0; // circular index 26 | int _nextTokenIndex; 27 | int k = 2; // how many lookahead symbols 28 | int _nodeCounter; 29 | Stack _loopStack; 30 | 31 | public DialogueScriptLoader(DialogueRunner pDialogueRunner) 32 | { 33 | D.isNull(pDialogueRunner); 34 | _dialogueRunner = pDialogueRunner; 35 | } 36 | 37 | public void CreateDialogueNodesFromString(string pString, string pConversation) 38 | { 39 | using(StringReader sr = new StringReader(pString)) 40 | { 41 | CreateDialogueNodes(sr, pConversation); 42 | sr.Close(); 43 | } 44 | } 45 | 46 | public void LoadDialogueNodesFromFile(string pFilepath) 47 | { 48 | 49 | string conversation = DialogueScriptLoader.GetConversationNameFromFilepath(pFilepath); 50 | using(StreamReader sr = File.OpenText(pFilepath)) 51 | { 52 | CreateDialogueNodes(sr, conversation); 53 | sr.Close(); 54 | } 55 | } 56 | 57 | private void CreateDialogueNodes(TextReader pTextReader, string pConversation) 58 | { 59 | _conversationName = pConversation; 60 | 61 | Tokenizer tokenizer = new Tokenizer(); 62 | _tokens = tokenizer.process(pTextReader); 63 | _loopStack = new Stack(); 64 | 65 | #if PRINT_TOKENS 66 | Console.WriteLine("Tokens:"); 67 | foreach(Token t in _tokens) 68 | { 69 | Console.WriteLine(t.getTokenType().ToString() + ": " + t.getTokenString()); 70 | } 71 | #endif 72 | 73 | _nextTokenIndex = 0; 74 | _lookaheadIndex = 0; 75 | _lookahead = new Token[k]; 76 | for (int i = 0; i < k; i++) { 77 | ConsumeCurrentToken(); 78 | } 79 | 80 | Languages(); 81 | //CreateTreeOfDialogueNodes(); 82 | } 83 | 84 | public static string GetConversationNameFromFilepath(string pFilepath) 85 | { 86 | if(pFilepath == null || pFilepath == "") { 87 | throw new GrimmException("Filepath is empty!"); 88 | } 89 | int index = pFilepath.LastIndexOf("/"); 90 | int index2 = pFilepath.LastIndexOf(@"\"); 91 | if (index2 > index) 92 | index = index2; 93 | string filenameWithEnding = pFilepath.Substring(index + 1); 94 | string conversationName = filenameWithEnding; 95 | int i = filenameWithEnding.LastIndexOf("."); 96 | if(i > -1) { 97 | conversationName = filenameWithEnding.Substring(0, i); 98 | } 99 | return conversationName; 100 | } 101 | 102 | private void Languages() 103 | { 104 | #if DEBUG_WRITE 105 | Console.WriteLine("Languages()"); 106 | #endif 107 | 108 | _language = Language.DEFAULT; 109 | 110 | while(lookAheadType(1) != Token.TokenType.EOF) 111 | { 112 | if(lookAheadType(1) == Token.TokenType.LANGUAGE) { 113 | match(Token.TokenType.LANGUAGE); 114 | Token languageNameToken = match(Token.TokenType.NAME); 115 | string languageName = languageNameToken.getTokenString(); 116 | switch(languageName.ToLower()) { 117 | case "swedish": 118 | _language = Language.SWEDISH; 119 | break; 120 | case "english": 121 | _language = Language.ENGLISH; 122 | break; 123 | default: 124 | throw new GrimmException("Can't handle language '" + languageName + "'"); 125 | } 126 | } 127 | 128 | #if DEBUG_WRITE 129 | Console.WriteLine("Creating dialogue nodes for language " + _language); 130 | #endif 131 | 132 | CreateTreeOfDialogueNodes(); 133 | } 134 | } 135 | 136 | private void CreateTreeOfDialogueNodes() 137 | { 138 | _nodeCounter = 0; 139 | 140 | ConversationStartDialogueNode startNode = _dialogueRunner.Create(_conversationName, _language, NAME_OF_START_NODE); 141 | ConversationEndDialogueNode endNode = _dialogueRunner.Create(_conversationName, _language, NAME_OF_END_NODE); 142 | 143 | #if DEBUG_WRITE 144 | Console.WriteLine("Created a ConversationStartDialogueNode with name '" + startNode.name + "'"); 145 | Console.WriteLine("Created a ConversationEndDialogueNode with name '" + endNode.name + "'"); 146 | #endif 147 | 148 | // Start parsing 149 | Nodes(startNode, endNode); 150 | } 151 | 152 | private void Nodes(DialogueNode pPrevious, DialogueNode pScopeEndNode) 153 | { 154 | #if DEBUG_WRITE 155 | Console.WriteLine("Nodes()"); 156 | #endif 157 | 158 | while( lookAheadType(1) != Token.TokenType.EOF && 159 | lookAheadType(1) != Token.TokenType.BLOCK_END && 160 | lookAheadType(1) != Token.TokenType.QUOTED_STRING && 161 | lookAheadType(1) != Token.TokenType.LANGUAGE) 162 | { 163 | DialogueNode n = Statement(pPrevious); 164 | 165 | if(n != null) { 166 | pPrevious = n; 167 | } 168 | } 169 | 170 | AddLinkFromPreviousNode(pPrevious, pScopeEndNode); 171 | } 172 | 173 | private DialogueNode Statement(DialogueNode pPrevious) 174 | { 175 | #if DEBUG_WRITE 176 | //Console.WriteLine("Statement() Lookahead: " + lookAhead(1).getTokenString()); 177 | #endif 178 | 179 | if (lookAheadType(1) == Token.TokenType.NEW_LINE) { 180 | match(Token.TokenType.NEW_LINE); 181 | } 182 | else if (lookAheadType(1) == Token.TokenType.EOF) { 183 | match(Token.TokenType.EOF); 184 | } 185 | else if (lookAheadType(1) == Token.TokenType.NAME && 186 | lookAheadType(2) == Token.TokenType.QUOTED_STRING) 187 | { 188 | return VisitTimedDialogueNode(pPrevious); 189 | } 190 | else if (lookAheadType(1) == Token.TokenType.NAME && 191 | lookAheadType(2) == Token.TokenType.PARANTHESIS_LEFT) 192 | { 193 | return VisitFunctionDialogueNode(pPrevious); 194 | } 195 | else if (lookAheadType(1) == Token.TokenType.NAME && 196 | lookAheadType(2) == Token.TokenType.DOT) 197 | { 198 | return VisitFunctionDialogueNode(pPrevious); 199 | } 200 | else if( lookAheadType(1) == Token.TokenType.GOTO) { 201 | return VisitGotoDialogueNode(pPrevious); 202 | } 203 | else if( lookAheadType(1) == Token.TokenType.START) { 204 | return VisitStartCommandoDialogueNode(pPrevious); 205 | } 206 | else if( lookAheadType(1) == Token.TokenType.INTERRUPT) { 207 | return VisitInterruptDialogueNode(pPrevious); 208 | } 209 | else if( lookAheadType(1) == Token.TokenType.IF) { 210 | return VisitIfDialogueNode(pPrevious); 211 | } 212 | else if( lookAheadType(1) == Token.TokenType.ASSERT) { 213 | return VisitAssertDialogueNode(pPrevious); 214 | } 215 | else if( lookAheadType(1) == Token.TokenType.STOP) { 216 | return VisitStopDialogueNode(pPrevious); 217 | } 218 | else if( lookAheadType(1) == Token.TokenType.BROADCAST) { 219 | return VisitBroadcastDialogueNode(pPrevious); 220 | } 221 | else if( lookAheadType(1) == Token.TokenType.LISTEN) { 222 | return VisitListeningDialogueNode(pPrevious); 223 | } 224 | else if( lookAheadType(1) == Token.TokenType.CANCEL) { 225 | return VisitCancelDialogueNode(pPrevious); 226 | } 227 | else if( lookAheadType(1) == Token.TokenType.WAIT) { 228 | if(lookAheadType(2) == Token.TokenType.NUMBER) { 229 | return VisitTimedWaitDialogueNode(pPrevious); 230 | } 231 | else { 232 | return VisitWaitDialogueNode(pPrevious); 233 | } 234 | } 235 | else if( lookAheadType(1) == Token.TokenType.FOCUS) { 236 | return VisitFocusDialogueNode(pPrevious); 237 | } 238 | else if( lookAheadType(1) == Token.TokenType.DEFOCUS) { 239 | return VisitDefocusDialogueNode(pPrevious); 240 | } 241 | else if ( lookAheadType(1) == Token.TokenType.LOOP ) 242 | { 243 | return VisitLoopDialogueNode(pPrevious); 244 | } 245 | else if(lookAheadType(1) == Token.TokenType.BREAK) 246 | { 247 | return VisitBreakDialogueNode(pPrevious); 248 | } 249 | else if ( lookAheadType(1) == Token.TokenType.BLOCK_BEGIN ) 250 | { 251 | return VisitBranchingDialogueNode(pPrevious); 252 | } 253 | else if(lookAheadType(1) == Token.TokenType.BRACKET_LEFT) { 254 | return VisitEmptyNodeWithName(pPrevious); 255 | } 256 | else if ( lookAheadType(1) == Token.TokenType.CHOICE ) 257 | { 258 | return VisitBranchingDialogueNode(pPrevious); 259 | } 260 | else 261 | { 262 | throw new GrimmException("Can't figure out statement type of token " + 263 | lookAheadType(1) + " with string " + 264 | lookAhead(1).getTokenString() + " on line " + 265 | lookAhead(1).LineNr + " and position" + lookAhead(1).LinePosition + 266 | " in conversation " + _conversationName 267 | ); 268 | } 269 | 270 | return null; 271 | } 272 | 273 | private TimedDialogueNode VisitTimedDialogueNode(DialogueNode pPrevious) 274 | { 275 | #if DEBUG_WRITE 276 | Console.WriteLine("VisitTimedDialogueNode()"); 277 | #endif 278 | 279 | Token speakerToken = match(Token.TokenType.NAME); 280 | string speaker = speakerToken.getTokenString(); 281 | 282 | Token lineToken = match(Token.TokenType.QUOTED_STRING); 283 | string line = lineToken.getTokenString(); 284 | 285 | TimedDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + "_line_" + lineToken.LineNr); // + " (" + line + ")"); 286 | n.speaker = speaker; 287 | n.line = line; 288 | n.CalculateAndSetTimeBasedOnLineLength(false); 289 | 290 | if(lookAheadType(1) == Token.TokenType.BRACKET_LEFT) { 291 | match(Token.TokenType.BRACKET_LEFT); 292 | string nodeCustomName = match(Token.TokenType.NAME).getTokenString(); 293 | n.name = nodeCustomName; 294 | match(Token.TokenType.BRACKET_RIGHT); 295 | } 296 | 297 | #if DEBUG_WRITE 298 | Console.WriteLine("Added TimedDialogueNode with name '" + n.name + "' and line '" + n.line + "'"); 299 | #endif 300 | 301 | AddLinkFromPreviousNode(pPrevious, n); 302 | 303 | return n; 304 | } 305 | 306 | DialogueNode VisitCancelDialogueNode(DialogueNode pPrevious) 307 | { 308 | #if DEBUG_WRITE 309 | Console.WriteLine("VisitCancelDialogueNode()"); 310 | #endif 311 | 312 | match(Token.TokenType.CANCEL); 313 | Token handleNameToken = match(Token.TokenType.NAME); 314 | 315 | CancelDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + "(cancel)"); 316 | n.handle = handleNameToken.getTokenString(); 317 | 318 | AddLinkFromPreviousNode(pPrevious, n); 319 | return n; 320 | } 321 | 322 | DialogueNode VisitBroadcastDialogueNode(DialogueNode pPrevious) 323 | { 324 | #if DEBUG_WRITE 325 | Console.WriteLine("VisitBroadcastDialogueNode()"); 326 | #endif 327 | 328 | match(Token.TokenType.BROADCAST); 329 | string eventName = GetAStringFromNextToken(false, false); 330 | 331 | BroadcastDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + "(broadcaster)"); 332 | n.eventName = eventName; 333 | 334 | AddLinkFromPreviousNode(pPrevious, n); 335 | return n; 336 | } 337 | 338 | string GetAStringFromNextToken(bool pQuotedStringsAreOK, bool pNumbersAreOK) 339 | { 340 | Token token; 341 | 342 | if(lookAheadType(1) == Token.TokenType.NUMBER && pNumbersAreOK) { 343 | token = match(Token.TokenType.NUMBER); 344 | } 345 | else if(lookAheadType(1) == Token.TokenType.QUOTED_STRING && pQuotedStringsAreOK) { 346 | token = match(Token.TokenType.QUOTED_STRING); 347 | } 348 | else { 349 | token = match(Token.TokenType.NAME); 350 | } 351 | 352 | return token.getTokenString(); 353 | } 354 | 355 | DialogueNode VisitListeningDialogueNode(DialogueNode pPrevious) 356 | { 357 | #if DEBUG_WRITE 358 | Console.WriteLine("VisitListeningDialogueNode()"); 359 | #endif 360 | 361 | match(Token.TokenType.LISTEN); 362 | 363 | string eventName = GetAStringFromNextToken(false, false); 364 | 365 | string handleName = ""; 366 | if (lookAheadType (1) == Token.TokenType.BRACKET_LEFT) { 367 | match (Token.TokenType.BRACKET_LEFT); 368 | Token handleToken = match (Token.TokenType.NAME); 369 | handleName = handleToken.getTokenString (); 370 | match (Token.TokenType.BRACKET_RIGHT); 371 | } else if ( 372 | (lookAheadType (1) != Token.TokenType.EOF) && 373 | (lookAheadType (1) != Token.TokenType.NEW_LINE) && 374 | (lookAheadType (1) != Token.TokenType.BLOCK_BEGIN)) { 375 | throw new GrimmException ("Can't follow LISTEN statement with token of type " + lookAheadType (1) + " at line " + lookAhead(1).LineNr + " and position " + lookAhead(1).LinePosition + " in " + _conversationName); 376 | } 377 | 378 | ListeningDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + "(event listener)"); 379 | n.eventName = eventName; 380 | n.handle = handleName; 381 | 382 | if(_loopStack.Count > 0) { 383 | // Add this listening dialogue node to the scope of the loop so that it is automatically removed as a listener when the loop ends 384 | n.scopeNode = _loopStack.Peek().name; 385 | } 386 | SilentDialogueNode silentNode = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + "(silent stop node)"); 387 | 388 | AllowLineBreak(); 389 | 390 | if(lookAheadType(1) == Token.TokenType.BLOCK_BEGIN) { 391 | _loopStack.Push(n); 392 | 393 | ImmediateNode eventBranchStartNode = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + "(eventBranchStartNode)"); 394 | n.branchNode = eventBranchStartNode.name; 395 | n.hasBranch = true; 396 | match(Token.TokenType.BLOCK_BEGIN); 397 | Nodes(eventBranchStartNode, silentNode); 398 | match(Token.TokenType.BLOCK_END); 399 | 400 | _loopStack.Pop (); 401 | } 402 | else { 403 | #if DEBUG_WRITE 404 | Console.WriteLine("this listening dialogue node had no body"); 405 | #endif 406 | } 407 | 408 | AddLinkFromPreviousNode(pPrevious, n); 409 | return n; 410 | } 411 | 412 | private CallFunctionDialogueNode VisitFunctionDialogueNode(DialogueNode pPrevious) 413 | { 414 | #if DEBUG_WRITE 415 | Console.WriteLine("VisitFunctionDialogueNode()"); 416 | #endif 417 | 418 | string functionName = ""; 419 | 420 | string[] args = VisitFunctionCall(out functionName); 421 | 422 | CallFunctionDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + "_" + functionName); 423 | n.function = functionName; 424 | n.args = args; 425 | 426 | if(!_dialogueRunner.HasFunction(functionName)) { 427 | //throw new GrimmException("There is no '" + functionName + "' function registered in the dialogue runner"); 428 | } 429 | 430 | #if DEBUG_WRITE 431 | Console.WriteLine("Added CallFunctionDialogueNode() with name '" + n.name + "'"); 432 | #endif 433 | 434 | AddLinkFromPreviousNode(pPrevious, n); 435 | 436 | return n; 437 | } 438 | 439 | private string[] VisitFunctionCall(out string pFunctionName) 440 | { 441 | List arguments = new List(); 442 | 443 | if(lookAheadType(2) == Token.TokenType.DOT) { 444 | // Function looks like this: cat.foo("dog") 445 | Token tokenBeforeDot = match(Token.TokenType.NAME); 446 | string arg0 = tokenBeforeDot.getTokenString(); 447 | arguments.Add(arg0); 448 | #if DEBUG_WRITE 449 | Console.WriteLine("Added argument 0 based on token before dot: " + arg0); 450 | #endif 451 | match(Token.TokenType.DOT); 452 | } 453 | 454 | Token functionNameToken = match(Token.TokenType.NAME); 455 | pFunctionName = functionNameToken.getTokenString(); 456 | 457 | match(Token.TokenType.PARANTHESIS_LEFT); 458 | 459 | while(true) 460 | { 461 | if(lookAheadType(1) == Token.TokenType.PARANTHESIS_RIGHT) { 462 | break; 463 | } 464 | else if(lookAheadType(1) == Token.TokenType.NEW_LINE) { 465 | match(Token.TokenType.NEW_LINE); 466 | } 467 | else { 468 | string argumentString = GetAStringFromNextToken(true, true); 469 | #if DEBUG_WRITE 470 | Console.WriteLine("Matched argument " + argumentString); 471 | #endif 472 | arguments.Add(argumentString); 473 | if(lookAheadType(1) == Token.TokenType.COMMA) { 474 | match(Token.TokenType.COMMA); 475 | } 476 | else { 477 | break; 478 | } 479 | } 480 | } 481 | 482 | match(Token.TokenType.PARANTHESIS_RIGHT); 483 | 484 | return arguments.ToArray(); 485 | } 486 | 487 | private DialogueNode VisitEmptyNodeWithName(DialogueNode pPreviousNode) 488 | { 489 | match(Token.TokenType.BRACKET_LEFT); 490 | string nodeCustomName = match(Token.TokenType.NAME).getTokenString(); 491 | ImmediateNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString()); 492 | n.name = nodeCustomName; 493 | match(Token.TokenType.BRACKET_RIGHT); 494 | AddLinkFromPreviousNode(pPreviousNode, n); 495 | return n; 496 | } 497 | 498 | void AddLinkFromPreviousNode(DialogueNode pPreviousNode, DialogueNode pNewNode) 499 | { 500 | D.isNull(pPreviousNode); 501 | D.isNull(pNewNode); 502 | pPreviousNode.nextNode = pNewNode.name; 503 | #if WRITE_NODE_LINKS 504 | Console.WriteLine(pPreviousNode.name + ".nextNode = '" + pNewNode.name + "'"); 505 | #endif 506 | } 507 | 508 | private int CalculateTimeout(string pLine) { 509 | int nrOfCharacters = pLine.Length; 510 | return (int)(30 + nrOfCharacters * 1.0f); 511 | } 512 | 513 | private GotoDialogueNode VisitGotoDialogueNode(DialogueNode pPrevious) { 514 | #if DEBUG_WRITE 515 | Console.WriteLine("GotoDialogueNode()"); 516 | #endif 517 | 518 | match(Token.TokenType.GOTO); 519 | Token targetNameToken = match(Token.TokenType.NAME); 520 | 521 | GotoDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (goto)"); 522 | n.linkedNode = targetNameToken.getTokenString(); 523 | 524 | #if DEBUG_WRITE 525 | Console.WriteLine("Added GotoDialogueNode() with name '" + n.name + "'"); 526 | #endif 527 | 528 | AddLinkFromPreviousNode(pPrevious, n); 529 | 530 | return n; 531 | } 532 | 533 | private DialogueNode VisitStopDialogueNode(DialogueNode pPrevious) { 534 | #if DEBUG_WRITE 535 | Console.WriteLine("VisitStopDialogueNode()"); 536 | #endif 537 | 538 | match(Token.TokenType.STOP); 539 | 540 | StopDialogueNode n = null; 541 | string nameOfConversationToStop = ""; 542 | 543 | if(lookAheadType(1) == Token.TokenType.NAME || lookAheadType(1) == Token.TokenType.QUOTED_STRING) { 544 | nameOfConversationToStop = GetAStringFromNextToken(true, false); 545 | } 546 | else { 547 | nameOfConversationToStop = _conversationName; 548 | } 549 | 550 | n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (stop)"); 551 | n.conversationToStop = nameOfConversationToStop; 552 | 553 | #if DEBUG_WRITE 554 | Console.WriteLine("Added stopping node with name '" + n.name + "'"); 555 | #endif 556 | 557 | AddLinkFromPreviousNode(pPrevious, n); 558 | 559 | return n; 560 | } 561 | 562 | private StartCommandoDialogueNode VisitStartCommandoDialogueNode(DialogueNode pPrevious) 563 | { 564 | #if DEBUG_WRITE 565 | Console.WriteLine("StartCommandoDialogueNode()"); 566 | #endif 567 | 568 | match(Token.TokenType.START); 569 | string commandoName = GetAStringFromNextToken(false, false); 570 | 571 | StartCommandoDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (start commando)"); 572 | n.commando = commandoName; 573 | 574 | #if DEBUG_WRITE 575 | Console.WriteLine("Added StartCommandoDialogueNode() with name '" + n.name + "'"); 576 | #endif 577 | 578 | AddLinkFromPreviousNode(pPrevious, n); 579 | 580 | return n; 581 | } 582 | 583 | private InterruptDialogueNode VisitInterruptDialogueNode(DialogueNode pPrevious) 584 | { 585 | #if DEBUG_WRITE 586 | Console.WriteLine("InterruptDialogueNode()"); 587 | #endif 588 | 589 | match(Token.TokenType.INTERRUPT); 590 | string interruptingConversation = GetAStringFromNextToken(false, false); 591 | 592 | InterruptDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (interrupt commando)"); 593 | n.interruptingConversation = interruptingConversation; 594 | 595 | #if DEBUG_WRITE 596 | Console.WriteLine("Added InterruptDialogueNode() with name '" + n.name + "'"); 597 | #endif 598 | 599 | AddLinkFromPreviousNode(pPrevious, n); 600 | 601 | return n; 602 | } 603 | 604 | private TimedWaitDialogueNode VisitTimedWaitDialogueNode(DialogueNode pPrevious) 605 | { 606 | #if DEBUG_WRITE 607 | Console.WriteLine("TimedWaitDialogueNode()"); 608 | #endif 609 | 610 | match(Token.TokenType.WAIT); 611 | Token time = match(Token.TokenType.NUMBER); 612 | 613 | TimedWaitDialogueNode node = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (timed wait node)"); 614 | node.timer = node.timerStartValue = Convert.ToSingle(time.getTokenString()); 615 | 616 | #if DEBUG_WRITE 617 | Console.WriteLine("Added TimedWaitDialogueNode() with name '" + node.name + "'"); 618 | #endif 619 | 620 | AddLinkFromPreviousNode(pPrevious, node); 621 | 622 | return node; 623 | } 624 | 625 | private WaitDialogueNode VisitWaitDialogueNode(DialogueNode pPrevious) 626 | { 627 | #if DEBUG_WRITE 628 | Console.WriteLine("WaitDialogueNode()"); 629 | #endif 630 | 631 | match(Token.TokenType.WAIT); 632 | 633 | WaitDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (start commando)"); 634 | 635 | List expressionNodes = new List(); 636 | 637 | bool hasEventListener = false; 638 | 639 | while(true) { 640 | 641 | if(lookAheadType(1) == Token.TokenType.NAME) 642 | { 643 | string expressionName = ""; 644 | string[] args = VisitFunctionCall(out expressionName); 645 | 646 | ExpressionDialogueNode expressionNode = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (expression)"); 647 | expressionNode.expression = expressionName; 648 | expressionNode.args = args; 649 | expressionNodes.Add(expressionNode); 650 | } 651 | else if(lookAheadType(1) == Token.TokenType.AND) 652 | { 653 | ConsumeCurrentToken(); 654 | } 655 | else if(lookAheadType(1) == Token.TokenType.LISTEN) 656 | { 657 | if(hasEventListener) { 658 | throw new GrimmException(_conversationName + " already has a event listener attached to the wait statement on line " + lookAhead(1).LineNr); 659 | } 660 | ConsumeCurrentToken(); 661 | n.eventName = match(Token.TokenType.NAME).getTokenString(); 662 | hasEventListener = true; 663 | #if DEBUG_WRITE 664 | Console.WriteLine("This WaitDialogueNode has an event called " + n.eventName); 665 | #endif 666 | } 667 | else { 668 | break; 669 | } 670 | } 671 | 672 | n.expressions = expressionNodes.ToArray(); 673 | 674 | string handleName = ""; 675 | if(lookAheadType(1) == Token.TokenType.BRACKET_LEFT) { 676 | match(Token.TokenType.BRACKET_LEFT); 677 | Token handleToken = match(Token.TokenType.NAME); 678 | handleName = handleToken.getTokenString(); 679 | match(Token.TokenType.BRACKET_RIGHT); 680 | } 681 | 682 | n.handle = handleName; 683 | 684 | if(_loopStack.Count > 0) { 685 | // Add this listening dialogue node to the scope of the loop so that it is automatically removed when the loop ends 686 | n.scopeNode = _loopStack.Peek().name; 687 | } 688 | 689 | #if DEBUG_WRITE 690 | Console.WriteLine("Added WaitDialogueNode() with name '" + n.name + "'"); 691 | #endif 692 | 693 | //if(!_dialogueRunner.HasExpression(expressionName)) { 694 | //throw new GrimmException("There is no '" + expressionName + "' expression registered in the dialogue runner"); 695 | //} 696 | 697 | SilentDialogueNode silentEndNode = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + "(silent stop node)"); 698 | 699 | AllowLineBreak(); 700 | 701 | if(lookAheadType(1) == Token.TokenType.BLOCK_BEGIN) { 702 | _loopStack.Push(n); 703 | 704 | ImmediateNode eventBranchStartNode = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + "(waitBranchStartNode)"); 705 | n.branchNode = eventBranchStartNode.name; 706 | n.hasBranch = true; 707 | match(Token.TokenType.BLOCK_BEGIN); 708 | Nodes(eventBranchStartNode, silentEndNode); 709 | match(Token.TokenType.BLOCK_END); 710 | 711 | _loopStack.Pop (); 712 | } 713 | else { 714 | #if DEBUG_WRITE 715 | Console.WriteLine("this wait dialogue node had no body"); 716 | #endif 717 | } 718 | 719 | AddLinkFromPreviousNode(pPrevious, n); 720 | 721 | return n; 722 | } 723 | 724 | private FocusDialogueNode VisitFocusDialogueNode(DialogueNode pPrevious) 725 | { 726 | #if DEBUG_WRITE 727 | Console.WriteLine("FocusConversationNode()"); 728 | #endif 729 | 730 | match(Token.TokenType.FOCUS); 731 | 732 | FocusDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (focus)"); 733 | 734 | #if DEBUG_WRITE 735 | Console.WriteLine("Added FocusConversationNode() with name '" + n.name + "'"); 736 | #endif 737 | 738 | AddLinkFromPreviousNode(pPrevious, n); 739 | 740 | return n; 741 | } 742 | 743 | private DefocusDialogueNode VisitDefocusDialogueNode(DialogueNode pPrevious) 744 | { 745 | #if DEBUG_WRITE 746 | Console.WriteLine("DefocusConversationNode()"); 747 | #endif 748 | 749 | match(Token.TokenType.DEFOCUS); 750 | 751 | DefocusDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (defocus)"); 752 | 753 | #if DEBUG_WRITE 754 | Console.WriteLine("Added DefocusConversationNode() with name '" + n.name + "'"); 755 | #endif 756 | 757 | AddLinkFromPreviousNode(pPrevious, n); 758 | 759 | return n; 760 | } 761 | 762 | private void AllowLineBreak() { 763 | if(lookAheadType(1) == Token.TokenType.NEW_LINE) { 764 | match(Token.TokenType.NEW_LINE); 765 | } 766 | } 767 | 768 | private DialogueNode VisitLoopDialogueNode(DialogueNode pPrevious) 769 | { 770 | #if DEBUG_WRITE 771 | Console.WriteLine("VisitLoopDialogueNode()"); 772 | #endif 773 | 774 | match(Token.TokenType.LOOP); 775 | 776 | LoopDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + " (loop)"); 777 | AddLinkFromPreviousNode(pPrevious, n); 778 | 779 | //ImmediateNode finalNode = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + "(final node)"); 780 | //n.nextNode = finalNode.name; 781 | 782 | AllowLineBreak(); 783 | 784 | _loopStack.Push(n); 785 | match(Token.TokenType.BLOCK_BEGIN); 786 | 787 | ImmediateNode branchStartNode = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + " (loop branch node)"); 788 | n.branchNode = branchStartNode.name; 789 | 790 | SilentDialogueNode unifiedEndNode = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + " (unified end node for loop)"); 791 | Nodes(branchStartNode, unifiedEndNode); 792 | 793 | match(Token.TokenType.BLOCK_END); 794 | _loopStack.Pop(); 795 | 796 | return n; 797 | } 798 | 799 | private DialogueNode VisitBreakDialogueNode(DialogueNode pPrevious) 800 | { 801 | #if DEBUG_WRITE 802 | Console.WriteLine("VisitBreakDialogueNode()"); 803 | #endif 804 | 805 | Token breakToken = match(Token.TokenType.BREAK); 806 | 807 | BreakDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + "(break)"); 808 | 809 | if(_loopStack.Count > 0) { 810 | n.breakTargetLoop = _loopStack.Peek().name; 811 | } 812 | else { 813 | throw new GrimmException("Trying to break at weird position? Line: " + breakToken.LineNr + " in conversation '" + _conversationName + "'"); 814 | } 815 | 816 | AddLinkFromPreviousNode(pPrevious, n); 817 | 818 | return n; 819 | } 820 | 821 | private DialogueNode VisitAssertDialogueNode(DialogueNode pPrevious) { 822 | #if DEBUG_WRITE 823 | Console.WriteLine("VisitAssertDialogueNode()"); 824 | #endif 825 | 826 | match(Token.TokenType.ASSERT); 827 | 828 | string expressionName = ""; 829 | string[] args = VisitFunctionCall(out expressionName); 830 | 831 | AssertDialogueNode n = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (assert)"); 832 | n.expression = expressionName; 833 | n.args = args; 834 | 835 | if(!_dialogueRunner.HasExpression(expressionName)) { 836 | throw new GrimmException("There is no '" + expressionName + "' expression registered in the dialogue runner"); 837 | } 838 | 839 | AddLinkFromPreviousNode(pPrevious, n); 840 | 841 | return n; 842 | } 843 | 844 | private DialogueNode VisitIfDialogueNode(DialogueNode pPrevious) { 845 | #if DEBUG_WRITE 846 | Console.WriteLine("IfDialogueNode()"); 847 | #endif 848 | 849 | match(Token.TokenType.IF); 850 | 851 | string expressionName = ""; 852 | string[] args = VisitFunctionCall(out expressionName); 853 | 854 | AllowLineBreak(); 855 | match(Token.TokenType.BLOCK_BEGIN); 856 | 857 | UnifiedEndNodeForScope unifiedEndNode = 858 | _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (unified end of if)"); 859 | 860 | ExpressionDialogueNode ifTrue = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (if true)"); 861 | Nodes(ifTrue, unifiedEndNode); 862 | ifTrue.expression = expressionName; 863 | ifTrue.args = args; 864 | 865 | #if DEBUG_WRITE 866 | Console.WriteLine("Added IfTrue node with expression '" + ifTrue.expression + "'"); 867 | #endif 868 | 869 | match(Token.TokenType.BLOCK_END); 870 | AllowLineBreak(); 871 | 872 | ImmediateNode ifFalse = null; 873 | 874 | List elifNodes = new List(); 875 | 876 | while(lookAheadType(1) == Token.TokenType.ELIF) 877 | { 878 | match(Token.TokenType.ELIF); 879 | 880 | string elifExpressionName = ""; 881 | string[] elifArgs = VisitFunctionCall(out elifExpressionName); 882 | 883 | AllowLineBreak(); 884 | match(Token.TokenType.BLOCK_BEGIN); 885 | 886 | ExpressionDialogueNode elifNode = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (elif)"); 887 | Nodes(elifNode, unifiedEndNode); 888 | elifNode.expression = elifExpressionName; 889 | elifNode.args = elifArgs; 890 | 891 | elifNodes.Add(elifNode); 892 | 893 | #if DEBUG_WRITE 894 | Console.WriteLine("Added Elif node with expression '" + elifNode.expression + "'"); 895 | #endif 896 | 897 | match(Token.TokenType.BLOCK_END); 898 | AllowLineBreak(); 899 | } 900 | 901 | if(lookAheadType(1) == Token.TokenType.ELSE) { 902 | match(Token.TokenType.ELSE); 903 | AllowLineBreak(); 904 | match(Token.TokenType.BLOCK_BEGIN); 905 | 906 | ifFalse = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (if false)"); 907 | Nodes(ifFalse, unifiedEndNode); 908 | 909 | match(Token.TokenType.BLOCK_END); 910 | } 911 | 912 | IfDialogueNode ifNode = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (if)"); 913 | 914 | #if DEBUG_WRITE 915 | Console.WriteLine("Added IfDialogueNode() with name '" + ifNode.name + "'"); 916 | //foreach(DialogueNode elif in elifNodes) { 917 | // Console.WriteLine("Added ElifDialogueNode() with name '" + elif.name + "'"); 918 | //} 919 | #endif 920 | 921 | AddLinkFromPreviousNode(pPrevious, ifNode); 922 | 923 | ifNode.nextNode = unifiedEndNode.name; 924 | ifNode.ifTrueNode = ifTrue; 925 | ifNode.elifNodes = elifNodes.ToArray(); 926 | if(ifFalse != null) { 927 | ifNode.ifFalseNode = ifFalse; 928 | } 929 | else { 930 | ifNode.ifFalseNode = null; 931 | } 932 | 933 | if(!_dialogueRunner.HasExpression(expressionName)) { 934 | //throw new GrimmException("There is no '" + expressionName + "' expression registered in the dialogue runner"); 935 | } 936 | 937 | return unifiedEndNode; 938 | } 939 | 940 | private DialogueNode VisitBranchingDialogueNode(DialogueNode pPrevious) 941 | { 942 | #if DEBUG_WRITE 943 | Console.WriteLine("NodesWithPlayerChoiceLinks()"); 944 | #endif 945 | 946 | if(lookAheadType(1) == Token.TokenType.CHOICE) { 947 | match(Token.TokenType.CHOICE); 948 | } 949 | 950 | bool eternal = lookAheadType(1) == Token.TokenType.ETERNAL; 951 | if(eternal) { 952 | match(Token.TokenType.ETERNAL); 953 | } 954 | 955 | match(Token.TokenType.BLOCK_BEGIN); 956 | 957 | BranchingDialogueNode bn = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString() + " (branching node)"); 958 | pPrevious.nextNode = bn.name; 959 | 960 | UnifiedEndNodeForScope unifiedEndNodeForScope = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++) + " (unified end of options)"); 961 | 962 | bn.unifiedEndNodeForScope = unifiedEndNodeForScope.name; 963 | 964 | #if DEBUG_WRITE 965 | Console.WriteLine("Created a branching node with name '" + bn.name + "'"); 966 | #endif 967 | 968 | List nameOfPossibleOptions = new List(); 969 | 970 | while (lookAheadType(1) != Token.TokenType.EOF && 971 | lookAheadType(1) != Token.TokenType.BLOCK_END) 972 | { 973 | DialogueNode n = FigureOutOptionStatement(unifiedEndNodeForScope); 974 | if(n != null) 975 | { 976 | nameOfPossibleOptions.Add(n.name); 977 | } 978 | } 979 | 980 | bn.nextNodes = nameOfPossibleOptions.ToArray(); 981 | bn.eternal = eternal; 982 | 983 | if (bn.nextNodes.Length < 2) { 984 | Console.WriteLine("\nWarning! Branching node " + bn.name + " with only " + bn.nextNodes.Length + " nodes in " + _conversationName); 985 | } 986 | 987 | match(Token.TokenType.BLOCK_END); 988 | 989 | return unifiedEndNodeForScope; 990 | } 991 | 992 | private DialogueNode FigureOutOptionStatement(DialogueNode pScopeEndNode) 993 | { 994 | #if DEBUG_WRITE 995 | Console.WriteLine("FigureOutOptionStatement()"); 996 | #endif 997 | 998 | if (lookAheadType(1) == Token.TokenType.NEW_LINE) { 999 | match(Token.TokenType.NEW_LINE); 1000 | #if DEBUG_WRITE 1001 | Console.Write(" (newline)"); 1002 | #endif 1003 | } 1004 | else if (lookAheadType(1) == Token.TokenType.EOF) { 1005 | match(Token.TokenType.EOF); 1006 | } 1007 | else if ( lookAheadType(1) == Token.TokenType.QUOTED_STRING && 1008 | lookAheadType(2) == Token.TokenType.COLON ) 1009 | { 1010 | return VisitOption(pScopeEndNode); 1011 | } 1012 | else { 1013 | throw new GrimmException("Can't figure out player option statement type of token " + 1014 | lookAheadType(1) + " with string " + 1015 | lookAhead(1).getTokenString() + " on line " + 1016 | lookAhead(1).LineNr + " and position" + lookAhead(1).LinePosition + " in conversation " + 1017 | _conversationName); 1018 | } 1019 | 1020 | return null; 1021 | } 1022 | 1023 | private DialogueNode VisitOption(DialogueNode pScopeEndNode) 1024 | { 1025 | #if DEBUG_WRITE 1026 | Console.WriteLine("VisitOption()"); 1027 | #endif 1028 | 1029 | Token t = match(Token.TokenType.QUOTED_STRING); 1030 | match(Token.TokenType.COLON); 1031 | 1032 | TimedDialogueNode optionNode = _dialogueRunner.Create(_conversationName, _language, (_nodeCounter++).ToString()); 1033 | optionNode.line = t.getTokenString(); 1034 | optionNode.speaker = _playerCharacterName; 1035 | optionNode.CalculateAndSetTimeBasedOnLineLength(true); 1036 | 1037 | #if DEBUG_WRITE 1038 | Console.WriteLine("Created an option node with the name '" + optionNode.name + "'" + " and line " + "'" + optionNode.line + "'"); 1039 | #endif 1040 | 1041 | Nodes(optionNode, pScopeEndNode); 1042 | 1043 | return optionNode; 1044 | } 1045 | 1046 | // ************************* // 1047 | 1048 | private Token match(Token.TokenType expectedTokenType) 1049 | { 1050 | Token matchedToken = lookAhead(1); 1051 | 1052 | if(lookAheadType(1) == expectedTokenType) { 1053 | ConsumeCurrentToken(); 1054 | } 1055 | else { 1056 | throw new GrimmException( 1057 | "The code word \"" + lookAhead(1).getTokenString() + "\"" + 1058 | " doesn't match the expected (" + expectedTokenType + ")." + 1059 | " at line " + 1060 | lookAhead(1).LineNr + 1061 | " and position " + 1062 | lookAhead(1).LinePosition + 1063 | " in conversation '" + 1064 | _conversationName + "'" 1065 | ); 1066 | } 1067 | 1068 | return matchedToken; 1069 | } 1070 | 1071 | private void ConsumeCurrentToken() { 1072 | 1073 | Token nextToken; 1074 | 1075 | if (_nextTokenIndex < _tokens.Count) { 1076 | nextToken = _tokens[_nextTokenIndex]; 1077 | _nextTokenIndex++; 1078 | } 1079 | else { 1080 | nextToken = new Token(Token.TokenType.EOF, ""); 1081 | } 1082 | 1083 | _lookahead[_lookaheadIndex] = nextToken; 1084 | _lookaheadIndex = (_lookaheadIndex + 1) % k; 1085 | } 1086 | 1087 | private Token lookAhead(int i) { 1088 | return _lookahead[(_lookaheadIndex + i - 1) % k]; 1089 | } 1090 | 1091 | private Token.TokenType lookAheadType(int i) { 1092 | return lookAhead(i).getTokenType(); 1093 | } 1094 | 1095 | private void SkipStuffUntilNextLine() { 1096 | #if DEBUG_WRITE 1097 | Console.WriteLine("SkipStuffUntilNextLine()"); 1098 | #endif 1099 | while( lookAheadType(1) != Token.TokenType.NEW_LINE ) 1100 | { 1101 | ConsumeCurrentToken(); 1102 | } 1103 | } 1104 | 1105 | public string playerCharacterName { 1106 | get { 1107 | return _playerCharacterName; 1108 | } 1109 | set { 1110 | _playerCharacterName = value; 1111 | } 1112 | } 1113 | } 1114 | } 1115 | --------------------------------------------------------------------------------