Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Change DialogueView to a mixin class #2652

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions packages/flame_jenny/jenny/lib/src/dialogue_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ import 'package:meta/meta.dart';
/// a game engine. This class describes how {ref}`line <DialogueLine>`s and
/// {ref}`option <DialogueOption>`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.
Expand All @@ -27,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).
abstract class DialogueView {
abstract mixin class DialogueView {
DialogueRunner? _dialogueRunner;

/// The owner of this `DialogueView`. This property will be `null` when the
Expand Down
117 changes: 117 additions & 0 deletions packages/flame_jenny/jenny/test/dialogue_view_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,50 @@
);
});

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
<<myCommand 123 boo>>
===
'''),
);
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(<<Command(myCommand)>>)',
'onNodeFinish(Start)',
'onDialogueFinish()',
],
);
});

test('jumps and visits', () async {
final yarn = YarnProject()
..parse(
Expand Down Expand Up @@ -233,6 +277,79 @@
}
}

class _SomeOtherBaseClass {}

class _RecordingDialogueViewAsMixin extends _SomeOtherBaseClass
with DialogueView {
_RecordingDialogueViewAsMixin([this.waitDuration = Duration.zero]);

Check notice on line 284 in packages/flame_jenny/jenny/test/dialogue_view_test.dart

View workflow job for this annotation

GitHub Actions / analyze

A value for optional parameter 'waitDuration' isn't ever given.

Try removing the unused parameter. See https://dart.dev/diagnostics/unused_element to learn more about this problem.
final List<String> events = [];
final Duration waitDuration;

@override
FutureOr<void> onDialogueStart() {
events.add('onDialogueStart');
}

@override
FutureOr<void> onNodeStart(Node node) {
events.add('onNodeStart(${node.title})');
}

@override
FutureOr<void> onNodeFinish(Node node) {
events.add('onNodeFinish(${node.title})');
}

@override
FutureOr<bool> 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<void> onLineStop(DialogueLine line) {
super.onLineStop(line);
events.add('onLineStop(${line.text})');
}

@override
FutureOr<void> onLineFinish(DialogueLine line) {
events.add('onLineFinish(${line.text})');
}

@override
Future<int> onChoiceStart(DialogueChoice choice) async {
final options =
[for (final option in choice.options) '[-> ${option.text}]'].join();
events.add('onChoiceStart($options)');
return 1;
}

@override
FutureOr<void> onChoiceFinish(DialogueOption option) {
events.add('onChoiceFinish(-> ${option.text})');
}

@override
FutureOr<void> onCommand(UserDefinedCommand command) {
events.add('onCommand(<<$command>>)');
}

@override
FutureOr<void> onDialogueFinish() {
events.add('onDialogueFinish()');
}
}

class _InterruptingCow extends DialogueView {
@override
FutureOr<bool> onLineStart(DialogueLine line) async {
Expand Down