From 2fb20717a6611869343850017d89e47a3beb009d Mon Sep 17 00:00:00 2001 From: projectitis Date: Fri, 18 Aug 2023 08:51:39 +1200 Subject: [PATCH 1/4] Change DialogueView to mixin class Support both subclassing and mixin on DialogueView --- .../jenny/lib/src/dialogue_view.dart | 2 +- .../jenny/test/dialogue_view_test.dart | 116 ++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/packages/flame_jenny/jenny/lib/src/dialogue_view.dart b/packages/flame_jenny/jenny/lib/src/dialogue_view.dart index ddb558c8fb3..bca2db1ddea 100644 --- a/packages/flame_jenny/jenny/lib/src/dialogue_view.dart +++ b/packages/flame_jenny/jenny/lib/src/dialogue_view.dart @@ -27,7 +27,7 @@ import 'package:meta/meta.dart'; /// be implemented either synchronously or asynchronously. In the latter case /// the dialogue runner will wait for the future to resolve before proceeding /// (futures from several dialogue views will be awaited simultaneously). -abstract class DialogueView { +mixin class DialogueView { DialogueRunner? _dialogueRunner; /// The owner of this `DialogueView`. This property will be `null` when the diff --git a/packages/flame_jenny/jenny/test/dialogue_view_test.dart b/packages/flame_jenny/jenny/test/dialogue_view_test.dart index dfd559a5291..71af99da9a4 100644 --- a/packages/flame_jenny/jenny/test/dialogue_view_test.dart +++ b/packages/flame_jenny/jenny/test/dialogue_view_test.dart @@ -52,6 +52,50 @@ void main() { ); }); + test('run a sample dialogue using mixin', () async { + final yarn = YarnProject() + ..commands.addOrphanedCommand('myCommand') + ..parse( + dedent(''' + title: Start + --- + First line + -> Option 1 + Continuation line 1 + -> Option 2 + Continuation line 2 + Last line + <> + === + '''), + ); + final view1 = _DefaultDialogueView(); + final view2 = _RecordingDialogueViewAsMixin(); + final dialogueRunner = DialogueRunner( + yarnProject: yarn, + dialogueViews: [view1, view2], + ); + await dialogueRunner.startDialogue('Start'); + expect( + view2.events, + const [ + 'onDialogueStart', + 'onNodeStart(Start)', + 'onLineStart(First line)', + 'onLineFinish(First line)', + 'onChoiceStart([-> Option 1][-> Option 2])', + 'onChoiceFinish(-> Option 2)', + 'onLineStart(Continuation line 2)', + 'onLineFinish(Continuation line 2)', + 'onLineStart(Last line)', + 'onLineFinish(Last line)', + 'onCommand(<>)', + 'onNodeFinish(Start)', + 'onDialogueFinish()', + ], + ); + }); + test('jumps and visits', () async { final yarn = YarnProject() ..parse( @@ -233,6 +277,78 @@ class _RecordingDialogueView extends DialogueView { } } +class _SomeOtherBaseClass {} + +class _RecordingDialogueViewAsMixin extends _SomeOtherBaseClass with DialogueView { + _RecordingDialogueViewAsMixin([this.waitDuration = Duration.zero]); + final List events = []; + final Duration waitDuration; + + @override + FutureOr onDialogueStart() { + events.add('onDialogueStart'); + } + + @override + FutureOr onNodeStart(Node node) { + events.add('onNodeStart(${node.title})'); + } + + @override + FutureOr onNodeFinish(Node node) { + events.add('onNodeFinish(${node.title})'); + } + + @override + FutureOr onLineStart(DialogueLine line) async { + events.add('onLineStart(${line.text})'); + if (waitDuration != Duration.zero) { + await Future.delayed(waitDuration, () {}); + } + return true; + } + + @override + void onLineSignal(DialogueLine line, dynamic signal) { + super.onLineSignal(line, signal); + events.add('onLineSignal(line="${line.text}", signal=<$signal>)'); + } + + @override + FutureOr onLineStop(DialogueLine line) { + super.onLineStop(line); + events.add('onLineStop(${line.text})'); + } + + @override + FutureOr onLineFinish(DialogueLine line) { + events.add('onLineFinish(${line.text})'); + } + + @override + Future onChoiceStart(DialogueChoice choice) async { + final options = + [for (final option in choice.options) '[-> ${option.text}]'].join(); + events.add('onChoiceStart($options)'); + return 1; + } + + @override + FutureOr onChoiceFinish(DialogueOption option) { + events.add('onChoiceFinish(-> ${option.text})'); + } + + @override + FutureOr onCommand(UserDefinedCommand command) { + events.add('onCommand(<<$command>>)'); + } + + @override + FutureOr onDialogueFinish() { + events.add('onDialogueFinish()'); + } +} + class _InterruptingCow extends DialogueView { @override FutureOr onLineStart(DialogueLine line) async { From a691d527bfc378f9dfc004e7225be1057242251f Mon Sep 17 00:00:00 2001 From: projectitis Date: Fri, 18 Aug 2023 09:18:02 +1200 Subject: [PATCH 2/4] Updated class comments --- .../flame_jenny/jenny/lib/src/dialogue_view.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/flame_jenny/jenny/lib/src/dialogue_view.dart b/packages/flame_jenny/jenny/lib/src/dialogue_view.dart index bca2db1ddea..50847941987 100644 --- a/packages/flame_jenny/jenny/lib/src/dialogue_view.dart +++ b/packages/flame_jenny/jenny/lib/src/dialogue_view.dart @@ -13,10 +13,15 @@ import 'package:meta/meta.dart'; /// a game engine. This class describes how {ref}`line `s and /// {ref}`option `s are presented to the user. /// -/// The class is abstract, which means you must create a concrete -/// implementation in order to use Jenny's dialogue system. The concrete -/// `DialogueView` objects will then be passed to a [DialogueRunner], which -/// will orchestrate the dialogue's progression. +/// There are two ways to use this class: +/// +/// - Extending DialogueView +/// - Adding DialogueView as a mixin +/// +/// In both cases you will need to create concrete implementations of the +/// abstract event handler methods in order to use Jenny's dialogue system. +/// The concrete `DialogueView` objects will then be passed to a +/// [DialogueRunner], which will orchestrate the dialogue's progression. /// /// The class defines a number of "event handler" methods, which can be /// overridden in subclasses in order to respond to the corresponding event. From 12c5a167c7aa1d17344473828f86a0c2181d40f8 Mon Sep 17 00:00:00 2001 From: projectitis Date: Fri, 18 Aug 2023 09:55:52 +1200 Subject: [PATCH 3/4] Formatting --- packages/flame_jenny/jenny/lib/src/dialogue_view.dart | 4 ++-- packages/flame_jenny/jenny/test/dialogue_view_test.dart | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/flame_jenny/jenny/lib/src/dialogue_view.dart b/packages/flame_jenny/jenny/lib/src/dialogue_view.dart index 50847941987..9c6fad84860 100644 --- a/packages/flame_jenny/jenny/lib/src/dialogue_view.dart +++ b/packages/flame_jenny/jenny/lib/src/dialogue_view.dart @@ -14,10 +14,10 @@ import 'package:meta/meta.dart'; /// {ref}`option `s are presented to the user. /// /// There are two ways to use this class: -/// +/// /// - Extending DialogueView /// - Adding DialogueView as a mixin -/// +/// /// In both cases you will need to create concrete implementations of the /// abstract event handler methods in order to use Jenny's dialogue system. /// The concrete `DialogueView` objects will then be passed to a diff --git a/packages/flame_jenny/jenny/test/dialogue_view_test.dart b/packages/flame_jenny/jenny/test/dialogue_view_test.dart index 71af99da9a4..60bb10fb367 100644 --- a/packages/flame_jenny/jenny/test/dialogue_view_test.dart +++ b/packages/flame_jenny/jenny/test/dialogue_view_test.dart @@ -279,7 +279,8 @@ class _RecordingDialogueView extends DialogueView { class _SomeOtherBaseClass {} -class _RecordingDialogueViewAsMixin extends _SomeOtherBaseClass with DialogueView { +class _RecordingDialogueViewAsMixin extends _SomeOtherBaseClass + with DialogueView { _RecordingDialogueViewAsMixin([this.waitDuration = Duration.zero]); final List events = []; final Duration waitDuration; From 4639b2bb7e6a64d21bc397293d0e7b98268163f4 Mon Sep 17 00:00:00 2001 From: Peter Vullings Date: Fri, 18 Aug 2023 20:27:58 +1200 Subject: [PATCH 4/4] Make DialogueView abstract Co-authored-by: Lukas Klingsbo --- packages/flame_jenny/jenny/lib/src/dialogue_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flame_jenny/jenny/lib/src/dialogue_view.dart b/packages/flame_jenny/jenny/lib/src/dialogue_view.dart index 9c6fad84860..095f3334eb7 100644 --- a/packages/flame_jenny/jenny/lib/src/dialogue_view.dart +++ b/packages/flame_jenny/jenny/lib/src/dialogue_view.dart @@ -32,7 +32,7 @@ import 'package:meta/meta.dart'; /// be implemented either synchronously or asynchronously. In the latter case /// the dialogue runner will wait for the future to resolve before proceeding /// (futures from several dialogue views will be awaited simultaneously). -mixin class DialogueView { +abstract mixin class DialogueView { DialogueRunner? _dialogueRunner; /// The owner of this `DialogueView`. This property will be `null` when the