diff --git a/lib/game/behaviors/camera_focusing_behavior.dart b/lib/game/behaviors/camera_focusing_behavior.dart index ec3e918f..6279f30e 100644 --- a/lib/game/behaviors/camera_focusing_behavior.dart +++ b/lib/game/behaviors/camera_focusing_behavior.dart @@ -51,7 +51,7 @@ class CameraFocusingBehavior extends Component position: _foci[GameStatus.waiting]?.position ?? Vector2(0, -112), ), GameStatus.playing: _FocusData( - zoom: size.y / 165, + zoom: size.y / 160, position: _foci[GameStatus.playing]?.position ?? Vector2(0, -7.8), ), GameStatus.gameOver: _FocusData( diff --git a/lib/game/components/dino_desert/dino_desert.dart b/lib/game/components/dino_desert/dino_desert.dart index 1d7b9072..561466fb 100644 --- a/lib/game/components/dino_desert/dino_desert.dart +++ b/lib/game/components/dino_desert/dino_desert.dart @@ -42,7 +42,7 @@ class _BarrierBehindDino extends BodyComponent { Body createBody() { final shape = EdgeShape() ..set( - Vector2(25.3, -14.2), + Vector2(24.2, -14.8), Vector2(25.3, -7.7), ); diff --git a/lib/game/components/game_bloc_status_listener.dart b/lib/game/components/game_bloc_status_listener.dart index 6ffd2ad3..fb5fc485 100644 --- a/lib/game/components/game_bloc_status_listener.dart +++ b/lib/game/components/game_bloc_status_listener.dart @@ -5,7 +5,6 @@ import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:platform_helper/platform_helper.dart'; /// Listens to the [GameBloc] and updates the game accordingly. class GameBlocStatusListener extends Component @@ -68,31 +67,25 @@ class GameBlocStatusListener extends Component gameRef.descendants().whereType().single.bloc.onReset(); } - void _addPlungerBehaviors(Plunger plunger) { - final platformHelper = readProvider(); - const pullingStrength = 7.0; - final provider = - plunger.firstChild>()!; - - if (platformHelper.isMobile) { - provider.add( - PlungerAutoPullingBehavior(strength: pullingStrength), - ); - } else { - provider.addAll( + void _addPlungerBehaviors(Plunger plunger) => plunger + .firstChild>()! + .addAll( [ - PlungerKeyControllingBehavior(), - PlungerPullingBehavior(strength: pullingStrength), + PlungerPullingBehavior(strength: 7), + PlungerAutoPullingBehavior(), + PlungerKeyControllingBehavior() ], ); - } - } void _removePlungerBehaviors(Plunger plunger) { plunger .descendants() .whereType() .forEach(plunger.remove); + plunger + .descendants() + .whereType() + .forEach(plunger.remove); plunger .descendants() .whereType() diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index ad81425f..2b003207 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -155,19 +155,18 @@ class PinballGame extends PinballForge2DGame @override void onTapDown(int pointerId, TapDownInfo info) { - if (info.raw.kind == PointerDeviceKind.touch) { + if (info.raw.kind == PointerDeviceKind.touch && + _gameBloc.state.status.isPlaying) { final rocket = descendants().whereType().first; final bounds = rocket.topLeftPosition & rocket.size; - // NOTE: As long as Flame does not have https://github.com/flame-engine/flame/issues/1586 - // we need to check it at the highest level manually. final tappedRocket = bounds.contains(info.eventPosition.game.toOffset()); if (tappedRocket) { descendants() .whereType>() .first .bloc - .pulled(); + .autoPulled(); } else { final tappedLeftSide = info.eventPosition.widget.x < canvasSize.x / 2; focusedBoardSide[pointerId] = diff --git a/packages/pinball_audio/lib/src/pinball_audio.dart b/packages/pinball_audio/lib/src/pinball_audio.dart index c62f197d..4a3d04d8 100644 --- a/packages/pinball_audio/lib/src/pinball_audio.dart +++ b/packages/pinball_audio/lib/src/pinball_audio.dart @@ -279,10 +279,11 @@ class PinballAudioPlayer { playSingleAudio: _playSingleAudio, path: Assets.sfx.sparky, ), - PinballAudio.dino: _SimplePlayAudio( + PinballAudio.dino: _ThrottledAudio( preCacheSingleAudio: _preCacheSingleAudio, playSingleAudio: _playSingleAudio, path: Assets.sfx.dino, + duration: const Duration(seconds: 6), ), PinballAudio.dash: _SimplePlayAudio( preCacheSingleAudio: _preCacheSingleAudio, diff --git a/packages/pinball_audio/test/src/pinball_audio_test.dart b/packages/pinball_audio/test/src/pinball_audio_test.dart index 554e9752..1c82815d 100644 --- a/packages/pinball_audio/test/src/pinball_audio_test.dart +++ b/packages/pinball_audio/test/src/pinball_audio_test.dart @@ -454,6 +454,32 @@ void main() { ), ).called(1); }); + + test('only plays the sound again after 6 seconds', () async { + final clock = _MockClock(); + await withClock(clock, () async { + when(clock.now).thenReturn(DateTime(2022)); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); + audioPlayer + ..play(PinballAudio.dino) + ..play(PinballAudio.dino); + + verify( + () => playSingleAudio + .onCall('packages/pinball_audio/${Assets.sfx.dino}'), + ).called(1); + + when(clock.now).thenReturn(DateTime(2022, 1, 1, 1, 6)); + audioPlayer.play(PinballAudio.dino); + + verify( + () => playSingleAudio + .onCall('packages/pinball_audio/${Assets.sfx.dino}'), + ).called(1); + }); + }); }); group('android', () { diff --git a/packages/pinball_components/lib/src/components/plunger/behaviors/plunger_pulling_behavior.dart b/packages/pinball_components/lib/src/components/plunger/behaviors/plunger_pulling_behavior.dart index db6bcaa3..7dc0d99b 100644 --- a/packages/pinball_components/lib/src/components/plunger/behaviors/plunger_pulling_behavior.dart +++ b/packages/pinball_components/lib/src/components/plunger/behaviors/plunger_pulling_behavior.dart @@ -28,14 +28,19 @@ class PlungerPullingBehavior extends Component } } -class PlungerAutoPullingBehavior extends PlungerPullingBehavior { - PlungerAutoPullingBehavior({ - required double strength, - }) : super(strength: strength); +class PlungerAutoPullingBehavior extends Component + with FlameBlocReader { + late final Plunger _plunger; + + @override + Future onLoad() async { + await super.onLoad(); + _plunger = parent!.parent! as Plunger; + } @override void update(double dt) { - super.update(dt); + if (!bloc.state.isAutoPulling) return; final joint = _plunger.body.joints.whereType().single; final reachedBottom = joint.getJointTranslation() <= joint.getLowerLimit(); diff --git a/packages/pinball_components/lib/src/components/plunger/cubit/plunger_cubit.dart b/packages/pinball_components/lib/src/components/plunger/cubit/plunger_cubit.dart index ce845197..694f7c94 100644 --- a/packages/pinball_components/lib/src/components/plunger/cubit/plunger_cubit.dart +++ b/packages/pinball_components/lib/src/components/plunger/cubit/plunger_cubit.dart @@ -8,4 +8,6 @@ class PlungerCubit extends Cubit { void pulled() => emit(PlungerState.pulling); void released() => emit(PlungerState.releasing); + + void autoPulled() => emit(PlungerState.autoPulling); } diff --git a/packages/pinball_components/lib/src/components/plunger/cubit/plunger_state.dart b/packages/pinball_components/lib/src/components/plunger/cubit/plunger_state.dart index 8b82ef96..0590d6fd 100644 --- a/packages/pinball_components/lib/src/components/plunger/cubit/plunger_state.dart +++ b/packages/pinball_components/lib/src/components/plunger/cubit/plunger_state.dart @@ -4,9 +4,13 @@ enum PlungerState { pulling, releasing, + + autoPulling, } extension PlungerStateX on PlungerState { - bool get isPulling => this == PlungerState.pulling; + bool get isPulling => + this == PlungerState.pulling || this == PlungerState.autoPulling; bool get isReleasing => this == PlungerState.releasing; + bool get isAutoPulling => this == PlungerState.autoPulling; } diff --git a/packages/pinball_components/lib/src/components/plunger/plunger.dart b/packages/pinball_components/lib/src/components/plunger/plunger.dart index fbb7a437..488c79bb 100644 --- a/packages/pinball_components/lib/src/components/plunger/plunger.dart +++ b/packages/pinball_components/lib/src/components/plunger/plunger.dart @@ -132,6 +132,7 @@ class _PlungerSpriteAnimationGroupComponent animations = { PlungerState.releasing: pullAnimation.reversed(), PlungerState.pulling: pullAnimation, + PlungerState.autoPulling: pullAnimation, }; current = readBloc().state; diff --git a/packages/pinball_components/test/src/components/plunger/behaviors/plunger_pulling_behavior_test.dart b/packages/pinball_components/test/src/components/plunger/behaviors/plunger_pulling_behavior_test.dart index 4eec7029..c289edee 100644 --- a/packages/pinball_components/test/src/components/plunger/behaviors/plunger_pulling_behavior_test.dart +++ b/packages/pinball_components/test/src/components/plunger/behaviors/plunger_pulling_behavior_test.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:bloc_test/bloc_test.dart'; +import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; @@ -12,7 +13,7 @@ import 'package:pinball_components/pinball_components.dart'; class _TestGame extends Forge2DGame { Future pump( - PlungerPullingBehavior behavior, { + Component behavior, { PlungerCubit? plungerBloc, }) async { final plunger = Plunger.test(); @@ -85,62 +86,28 @@ void main() { group('PlungerAutoPullingBehavior', () { test('can be instantiated', () { expect( - PlungerAutoPullingBehavior(strength: 0), + PlungerAutoPullingBehavior(), isA(), ); }); flameTester.test('can be loaded', (game) async { - final behavior = PlungerAutoPullingBehavior(strength: 0); + final behavior = PlungerAutoPullingBehavior(); await game.pump(behavior); expect(game.descendants(), contains(behavior)); }); - flameTester.test( - "pulls while joint hasn't reached limit", - (game) async { - final plungerBloc = _MockPlungerCubit(); - whenListen( - plungerBloc, - Stream.value(PlungerState.pulling), - initialState: PlungerState.pulling, - ); - - const strength = 2.0; - final behavior = PlungerAutoPullingBehavior( - strength: strength, - ); - await game.pump( - behavior, - plungerBloc: plungerBloc, - ); - final plunger = behavior.ancestors().whereType().single; - final joint = _MockPrismaticJoint(); - when(joint.getJointTranslation).thenReturn(2); - when(joint.getLowerLimit).thenReturn(0); - plunger.body.joints.add(joint); - - game.update(0); - - expect(plunger.body.linearVelocity.x, equals(0)); - expect(plunger.body.linearVelocity.y, equals(strength)); - }, - ); - flameTester.test( 'releases when joint reaches limit', (game) async { final plungerBloc = _MockPlungerCubit(); whenListen( plungerBloc, - Stream.value(PlungerState.pulling), - initialState: PlungerState.pulling, + Stream.value(PlungerState.autoPulling), + initialState: PlungerState.autoPulling, ); - const strength = 2.0; - final behavior = PlungerAutoPullingBehavior( - strength: strength, - ); + final behavior = PlungerAutoPullingBehavior(); await game.pump( behavior, plungerBloc: plungerBloc, diff --git a/packages/pinball_components/test/src/components/plunger/cubit/plunger_cubit_test.dart b/packages/pinball_components/test/src/components/plunger/cubit/plunger_cubit_test.dart new file mode 100644 index 00000000..31c802f9 --- /dev/null +++ b/packages/pinball_components/test/src/components/plunger/cubit/plunger_cubit_test.dart @@ -0,0 +1,25 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group('PlungerCubit', () { + test('can be instantiated', () { + expect(PlungerCubit(), isA()); + }); + + blocTest( + 'overrides previous pulling state', + build: PlungerCubit.new, + act: (cubit) => cubit + ..pulled() + ..autoPulled() + ..pulled(), + expect: () => [ + PlungerState.pulling, + PlungerState.autoPulling, + PlungerState.pulling, + ], + ); + }); +} diff --git a/test/game/components/game_bloc_status_listener_test.dart b/test/game/components/game_bloc_status_listener_test.dart index 069731fd..4f7f7222 100644 --- a/test/game/components/game_bloc_status_listener_test.dart +++ b/test/game/components/game_bloc_status_listener_test.dart @@ -36,7 +36,6 @@ class _TestGame extends Forge2DGame with HasTappables { Future pump( Iterable children, { PinballAudioPlayer? pinballAudioPlayer, - PlatformHelper? platformHelper, GoogleWordCubit? googleWordBloc, }) async { return ensureAdd( @@ -62,7 +61,7 @@ class _TestGame extends Forge2DGame with HasTappables { _MockAppLocalizations(), ), FlameProvider.value( - platformHelper ?? PlatformHelper(), + PlatformHelper(), ), ], children: children, @@ -80,8 +79,6 @@ class _MockLeaderboardRepository extends Mock implements LeaderboardRepository { class _MockShareRepository extends Mock implements ShareRepository {} -class _MockPlatformHelper extends Mock implements PlatformHelper {} - class _MockPlungerCubit extends Mock implements PlungerCubit {} class _MockGoogleWordCubit extends Mock implements GoogleWordCubit {} @@ -278,7 +275,7 @@ void main() { create: PlungerCubit.new, children: [ PlungerPullingBehavior(strength: 0), - PlungerAutoPullingBehavior(strength: 0) + PlungerAutoPullingBehavior() ], ), ); @@ -460,10 +457,8 @@ void main() { ); flameTester.test( - 'adds PlungerKeyControllingBehavior to Plunger when on desktop', + 'adds PlungerKeyControllingBehavior to Plunger', (game) async { - final platformHelper = _MockPlatformHelper(); - when(() => platformHelper.isMobile).thenReturn(false); final component = GameBlocStatusListener(); final leaderboardRepository = _MockLeaderboardRepository(); final shareRepository = _MockShareRepository(); @@ -482,7 +477,6 @@ void main() { bloc: _MockSignpostCubit(), ), ], - platformHelper: platformHelper, ); await plunger.ensureAdd( FlameBlocProvider( @@ -506,10 +500,8 @@ void main() { ); flameTester.test( - 'adds PlungerPullingBehavior to Plunger when on desktop', + 'adds PlungerPullingBehavior to Plunger', (game) async { - final platformHelper = _MockPlatformHelper(); - when(() => platformHelper.isMobile).thenReturn(false); final component = GameBlocStatusListener(); final leaderboardRepository = _MockLeaderboardRepository(); final shareRepository = _MockShareRepository(); @@ -528,7 +520,6 @@ void main() { bloc: _MockSignpostCubit(), ), ], - platformHelper: platformHelper, ); await plunger.ensureAdd( FlameBlocProvider( @@ -549,10 +540,8 @@ void main() { ); flameTester.test( - 'adds PlungerAutoPullingBehavior to Plunger when on mobile', + 'adds PlungerAutoPullingBehavior to Plunger', (game) async { - final platformHelper = _MockPlatformHelper(); - when(() => platformHelper.isMobile).thenReturn(true); final component = GameBlocStatusListener(); final leaderboardRepository = _MockLeaderboardRepository(); final shareRepository = _MockShareRepository(); @@ -571,7 +560,6 @@ void main() { bloc: _MockSignpostCubit(), ), ], - platformHelper: platformHelper, ); await plunger.ensureAdd( FlameBlocProvider( diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index 05b9442c..92608a1d 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -246,9 +246,61 @@ void main() { }); group('flipper control', () { + flameTester.test('tap control only works if game is playing', + (game) async { + await game.ready(); + + final gameBloc = game + .descendants() + .whereType>() + .first + .bloc; + + final eventPosition = _MockEventPosition(); + when(() => eventPosition.game).thenReturn(Vector2.zero()); + when(() => eventPosition.widget).thenReturn(Vector2.zero()); + + final raw = _MockTapDownDetails(); + when(() => raw.kind).thenReturn(PointerDeviceKind.touch); + + final tapDownEvent = _MockTapDownInfo(); + when(() => tapDownEvent.eventPosition).thenReturn(eventPosition); + when(() => tapDownEvent.raw).thenReturn(raw); + + final flipperBloc = game + .descendants() + .whereType() + .where((flipper) => flipper.side == BoardSide.left) + .single + .descendants() + .whereType>() + .first + .bloc; + + gameBloc.emit(gameBloc.state.copyWith(status: GameStatus.gameOver)); + + game.onTapDown(0, tapDownEvent); + await Future.delayed(Duration.zero); + expect(flipperBloc.state, FlipperState.movingDown); + + gameBloc.emit(gameBloc.state.copyWith(status: GameStatus.playing)); + + game.onTapDown(0, tapDownEvent); + await Future.delayed(Duration.zero); + expect(flipperBloc.state, FlipperState.movingUp); + }); + flameTester.test('tap down moves left flipper up', (game) async { await game.ready(); + final gameBloc = game + .descendants() + .whereType>() + .first + .bloc; + + gameBloc.emit(gameBloc.state.copyWith(status: GameStatus.playing)); + final eventPosition = _MockEventPosition(); when(() => eventPosition.game).thenReturn(Vector2.zero()); when(() => eventPosition.widget).thenReturn(Vector2.zero()); @@ -278,6 +330,14 @@ void main() { flameTester.test('tap down moves right flipper up', (game) async { await game.ready(); + final gameBloc = game + .descendants() + .whereType>() + .first + .bloc; + + gameBloc.emit(gameBloc.state.copyWith(status: GameStatus.playing)); + final eventPosition = _MockEventPosition(); when(() => eventPosition.game).thenReturn(Vector2.zero()); when(() => eventPosition.widget).thenReturn(game.canvasSize); @@ -307,6 +367,14 @@ void main() { flameTester.test('tap up moves flipper down', (game) async { await game.ready(); + final gameBloc = game + .descendants() + .whereType>() + .first + .bloc; + + gameBloc.emit(gameBloc.state.copyWith(status: GameStatus.playing)); + final eventPosition = _MockEventPosition(); when(() => eventPosition.game).thenReturn(Vector2.zero()); when(() => eventPosition.widget).thenReturn(Vector2.zero()); @@ -332,6 +400,14 @@ void main() { flameTester.test('tap cancel moves flipper down', (game) async { await game.ready(); + final gameBloc = game + .descendants() + .whereType>() + .first + .bloc; + + gameBloc.emit(gameBloc.state.copyWith(status: GameStatus.playing)); + final eventPosition = _MockEventPosition(); when(() => eventPosition.game).thenReturn(Vector2.zero()); when(() => eventPosition.widget).thenReturn(Vector2.zero()); @@ -363,6 +439,14 @@ void main() { (game) async { await game.ready(); + final gameBloc = game + .descendants() + .whereType>() + .first + .bloc; + + gameBloc.emit(gameBloc.state.copyWith(status: GameStatus.playing)); + final raw = _MockTapDownDetails(); when(() => raw.kind).thenReturn(PointerDeviceKind.touch); @@ -416,6 +500,14 @@ void main() { flameTester.test('plunger control tap down emits plunging', (game) async { await game.ready(); + final gameBloc = game + .descendants() + .whereType>() + .first + .bloc; + + gameBloc.emit(gameBloc.state.copyWith(status: GameStatus.playing)); + final eventPosition = _MockEventPosition(); when(() => eventPosition.game).thenReturn(Vector2(40, 60)); @@ -434,7 +526,7 @@ void main() { .single .bloc; - expect(plungerBloc.state, PlungerState.pulling); + expect(plungerBloc.state, PlungerState.autoPulling); }); }); });