diff --git a/lib/game/components/controlled_ball.dart b/lib/game/components/controlled_ball.dart index 659dd994..27a339c8 100644 --- a/lib/game/components/controlled_ball.dart +++ b/lib/game/components/controlled_ball.dart @@ -66,9 +66,8 @@ class BallController extends ComponentController // given animations. component.stop(); await Future.delayed(const Duration(seconds: 1)); - component - ..resume() - ..boost(Vector2(200, 500)); + component.resume(); + await component.boost(Vector2(40, 110)); } @override diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index e7c7a343..533c7bd1 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -7,7 +7,8 @@ extension PinballGameAssetsX on PinballGame { /// Returns a list of assets to be loaded List preLoadAssets() { return [ - images.load(components.Assets.images.ball.keyName), + images.load(components.Assets.images.ball.ball.keyName), + images.load(components.Assets.images.ball.flameEffect.keyName), images.load(components.Assets.images.flutterSignPost.keyName), images.load(components.Assets.images.flipper.left.keyName), images.load(components.Assets.images.flipper.right.keyName), @@ -21,6 +22,9 @@ extension PinballGameAssetsX on PinballGame { images.load( components.Assets.images.launchRamp.foregroundRailing.keyName, ), + images.load( + components.Assets.images.launchRamp.backgroundRailing.keyName, + ), images.load(components.Assets.images.dino.dinoLandTop.keyName), images.load(components.Assets.images.dino.dinoLandBottom.keyName), images.load(components.Assets.images.dash.animatronic.keyName), diff --git a/packages/pinball_components/assets/images/ball.png b/packages/pinball_components/assets/images/ball/ball.png similarity index 100% rename from packages/pinball_components/assets/images/ball.png rename to packages/pinball_components/assets/images/ball/ball.png diff --git a/packages/pinball_components/assets/images/ball/flame_effect.png b/packages/pinball_components/assets/images/ball/flame_effect.png new file mode 100644 index 00000000..03a6fca6 Binary files /dev/null and b/packages/pinball_components/assets/images/ball/flame_effect.png differ diff --git a/packages/pinball_components/assets/images/boundary/bottom.png b/packages/pinball_components/assets/images/boundary/bottom.png index 2effb7ac..90bfa493 100644 Binary files a/packages/pinball_components/assets/images/boundary/bottom.png and b/packages/pinball_components/assets/images/boundary/bottom.png differ diff --git a/packages/pinball_components/assets/images/dino/dino-land-bottom.png b/packages/pinball_components/assets/images/dino/dino-land-bottom.png index 1839dda3..9aa42e12 100644 Binary files a/packages/pinball_components/assets/images/dino/dino-land-bottom.png and b/packages/pinball_components/assets/images/dino/dino-land-bottom.png differ diff --git a/packages/pinball_components/assets/images/dino/dino-land-top.png b/packages/pinball_components/assets/images/dino/dino-land-top.png index 85c2619a..18b92541 100644 Binary files a/packages/pinball_components/assets/images/dino/dino-land-top.png and b/packages/pinball_components/assets/images/dino/dino-land-top.png differ diff --git a/packages/pinball_components/assets/images/launch_ramp/background-railing.png b/packages/pinball_components/assets/images/launch_ramp/background-railing.png new file mode 100644 index 00000000..aa7d5774 Binary files /dev/null and b/packages/pinball_components/assets/images/launch_ramp/background-railing.png differ diff --git a/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png b/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png index 7dd0de96..f953fdf5 100644 Binary files a/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png and b/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png differ diff --git a/packages/pinball_components/assets/images/launch_ramp/ramp.png b/packages/pinball_components/assets/images/launch_ramp/ramp.png index c811dd83..b024860a 100644 Binary files a/packages/pinball_components/assets/images/launch_ramp/ramp.png and b/packages/pinball_components/assets/images/launch_ramp/ramp.png differ diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index f45543b0..836f9495 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -13,10 +13,7 @@ class $AssetsImagesGen { $AssetsImagesAlienBumperGen get alienBumper => const $AssetsImagesAlienBumperGen(); $AssetsImagesBackboardGen get backboard => const $AssetsImagesBackboardGen(); - - /// File path: assets/images/ball.png - AssetGenImage get ball => const AssetGenImage('assets/images/ball.png'); - + $AssetsImagesBallGen get ball => const $AssetsImagesBallGen(); $AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen(); $AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen(); $AssetsImagesChromeDinoGen get chromeDino => @@ -57,10 +54,23 @@ class $AssetsImagesBackboardGen { /// File path: assets/images/backboard/backboard_scores.png AssetGenImage get backboardScores => const AssetGenImage('assets/images/backboard/backboard_scores.png'); + + /// File path: assets/images/backboard/display.png AssetGenImage get display => const AssetGenImage('assets/images/backboard/display.png'); } +class $AssetsImagesBallGen { + const $AssetsImagesBallGen(); + + /// File path: assets/images/ball/ball.png + AssetGenImage get ball => const AssetGenImage('assets/images/ball/ball.png'); + + /// File path: assets/images/ball/flame_effect.png + AssetGenImage get flameEffect => + const AssetGenImage('assets/images/ball/flame_effect.png'); +} + class $AssetsImagesBaseboardGen { const $AssetsImagesBaseboardGen(); @@ -174,6 +184,10 @@ class $AssetsImagesKickerGen { class $AssetsImagesLaunchRampGen { const $AssetsImagesLaunchRampGen(); + /// File path: assets/images/launch_ramp/background-railing.png + AssetGenImage get backgroundRailing => + const AssetGenImage('assets/images/launch_ramp/background-railing.png'); + /// File path: assets/images/launch_ramp/foreground-railing.png AssetGenImage get foregroundRailing => const AssetGenImage('assets/images/launch_ramp/foreground-railing.png'); @@ -309,8 +323,11 @@ class $AssetsImagesSparkyBumperGen { class $AssetsImagesSparkyComputerGen { const $AssetsImagesSparkyComputerGen(); + /// File path: assets/images/sparky/computer/base.png AssetGenImage get base => const AssetGenImage('assets/images/sparky/computer/base.png'); + + /// File path: assets/images/sparky/computer/top.png AssetGenImage get top => const AssetGenImage('assets/images/sparky/computer/top.png'); } @@ -354,8 +371,11 @@ class $AssetsImagesDashBumperMainGen { class $AssetsImagesSparkyBumperAGen { const $AssetsImagesSparkyBumperAGen(); + /// File path: assets/images/sparky/bumper/a/active.png AssetGenImage get active => const AssetGenImage('assets/images/sparky/bumper/a/active.png'); + + /// File path: assets/images/sparky/bumper/a/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/sparky/bumper/a/inactive.png'); } @@ -363,8 +383,11 @@ class $AssetsImagesSparkyBumperAGen { class $AssetsImagesSparkyBumperBGen { const $AssetsImagesSparkyBumperBGen(); + /// File path: assets/images/sparky/bumper/b/active.png AssetGenImage get active => const AssetGenImage('assets/images/sparky/bumper/b/active.png'); + + /// File path: assets/images/sparky/bumper/b/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/sparky/bumper/b/inactive.png'); } @@ -372,8 +395,11 @@ class $AssetsImagesSparkyBumperBGen { class $AssetsImagesSparkyBumperCGen { const $AssetsImagesSparkyBumperCGen(); + /// File path: assets/images/sparky/bumper/c/active.png AssetGenImage get active => const AssetGenImage('assets/images/sparky/bumper/c/active.png'); + + /// File path: assets/images/sparky/bumper/c/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/sparky/bumper/c/inactive.png'); } diff --git a/packages/pinball_components/lib/src/components/ball.dart b/packages/pinball_components/lib/src/components/ball.dart index 36059cfd..abbfefc8 100644 --- a/packages/pinball_components/lib/src/components/ball.dart +++ b/packages/pinball_components/lib/src/components/ball.dart @@ -4,6 +4,7 @@ import 'dart:ui'; import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/widgets.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template ball} @@ -49,9 +50,6 @@ class Ball extends BodyComponent /// The base [Color] used to tint this [Ball]. final Color baseColor; - double _boostTimer = 0; - static const _boostDuration = 2.0; - @override Body createBody() { final shape = CircleShape()..radius = size.x / 2; @@ -87,32 +85,20 @@ class Ball extends BodyComponent body.gravityScale = Vector2(0, 1); } + /// Applies a boost and [_TurboChargeSpriteAnimationComponent] on this [Ball]. + Future boost(Vector2 impulse) async { + body.linearVelocity = impulse; + await add(_TurboChargeSpriteAnimationComponent()); + } + @override void update(double dt) { super.update(dt); - if (_boostTimer > 0) { - _boostTimer -= dt; - final direction = body.linearVelocity.normalized(); - final effect = FireEffect( - burstPower: _boostTimer, - direction: direction, - position: Vector2(body.position.x, body.position.y), - priority: priority - 1, - ); - - unawaited(gameRef.add(effect)); - } _rescaleSize(); _setPositionalGravity(); } - /// Applies a boost on this [Ball]. - void boost(Vector2 impulse) { - body.linearVelocity = impulse; - _boostTimer = _boostDuration; - } - void _rescaleSize() { final boardHeight = BoardDimensions.bounds.height; const maxShrinkValue = BoardDimensions.perspectiveShrinkFactor; @@ -153,10 +139,61 @@ class _BallSpriteComponent extends SpriteComponent with HasGameRef { Future onLoad() async { await super.onLoad(); final sprite = await gameRef.loadSprite( - Assets.images.ball.keyName, + Assets.images.ball.ball.keyName, ); this.sprite = sprite; size = sprite.originalSize / 10; anchor = Anchor.center; } } + +class _TurboChargeSpriteAnimationComponent extends SpriteAnimationComponent + with HasGameRef { + _TurboChargeSpriteAnimationComponent() + : super( + anchor: const Anchor(0.53, 0.72), + priority: Ball.boardPriority + 1, + removeOnFinish: true, + ); + + late final Vector2 _textureSize; + + @override + Future onLoad() async { + await super.onLoad(); + + final spriteSheet = await gameRef.images.load( + Assets.images.ball.flameEffect.keyName, + ); + + const amountPerRow = 8; + const amountPerColumn = 4; + _textureSize = Vector2( + spriteSheet.width / amountPerRow, + spriteSheet.height / amountPerColumn, + ); + + animation = SpriteAnimation.fromFrameData( + spriteSheet, + SpriteAnimationData.sequenced( + amount: amountPerRow * amountPerColumn, + amountPerRow: amountPerRow, + stepTime: 1 / 24, + textureSize: _textureSize, + loop: false, + ), + ); + } + + @override + void update(double dt) { + super.update(dt); + + if (parent != null) { + final body = (parent! as BodyComponent).body; + final direction = -body.linearVelocity.normalized(); + angle = math.atan2(direction.x, -direction.y); + size = (_textureSize / 45) * body.fixtures.first.shape.radius; + } + } +} diff --git a/packages/pinball_components/lib/src/components/boundaries.dart b/packages/pinball_components/lib/src/components/boundaries.dart index 983b4839..2ac8fe76 100644 --- a/packages/pinball_components/lib/src/components/boundaries.dart +++ b/packages/pinball_components/lib/src/components/boundaries.dart @@ -26,7 +26,8 @@ class _BottomBoundary extends BodyComponent with InitialPosition { /// {@macro bottom_boundary} _BottomBoundary() : super( - priority: 1, + // TODO(ruimiguel): set final priority when RenderPriority PR merged. + priority: Ball.boardPriority + 2, children: [_BottomBoundarySpriteComponent()], ) { renderBody = false; @@ -78,7 +79,7 @@ class _BottomBoundarySpriteComponent extends SpriteComponent with HasGameRef { this.sprite = sprite; size = sprite.originalSize / 10; anchor = Anchor.center; - position = Vector2(-5.4, 55.8); + position = Vector2(-5.4, 55.6); } } diff --git a/packages/pinball_components/lib/src/components/dino_walls.dart b/packages/pinball_components/lib/src/components/dino_walls.dart index 9bc0ba09..4e0dbee2 100644 --- a/packages/pinball_components/lib/src/components/dino_walls.dart +++ b/packages/pinball_components/lib/src/components/dino_walls.dart @@ -31,7 +31,8 @@ class _DinoTopWall extends BodyComponent with InitialPosition { ///{@macro dino_top_wall} _DinoTopWall() : super( - priority: 1, + // TODO(ruimiguel): set final priority when RenderPriority PR merged. + priority: Ball.boardPriority + 1, children: [_DinoTopWallSpriteComponent()], ) { renderBody = false; @@ -42,7 +43,7 @@ class _DinoTopWall extends BodyComponent with InitialPosition { final topStraightShape = EdgeShape() ..set( - Vector2(28.4, -35.1), + Vector2(28.65, -35.1), Vector2(29.5, -35.1), ); final topStraightFixtureDef = FixtureDef(topStraightShape); @@ -69,8 +70,8 @@ class _DinoTopWall extends BodyComponent with InitialPosition { final bottomCurveShape = BezierCurveShape( controlPoints: [ middleCurveShape.vertices.last, - Vector2(21.15, -16), - Vector2(25.6, -15.2), + Vector2(21.5, -15.8), + Vector2(25.8, -14.8), ], ); fixturesDef.add(FixtureDef(bottomCurveShape)); @@ -126,6 +127,8 @@ class _DinoBottomWall extends BodyComponent with InitialPosition { ///{@macro dino_top_wall} _DinoBottomWall() : super( + // TODO(ruimiguel): set final priority when RenderPriority PR merged. + priority: Ball.boardPriority + 1, children: [_DinoBottomWallSpriteComponent()], ) { renderBody = false; @@ -136,7 +139,7 @@ class _DinoBottomWall extends BodyComponent with InitialPosition { const restitution = 1.0; final topStraightControlPoints = [ - Vector2(32.4, -8.3), + Vector2(32.4, -8.8), Vector2(25, -7.7), ]; final topStraightShape = EdgeShape() @@ -220,6 +223,6 @@ class _DinoBottomWallSpriteComponent extends SpriteComponent with HasGameRef { ); this.sprite = sprite; size = sprite.originalSize / 10; - position = Vector2(23.8, -9.5); + position = Vector2(23.6, -9.5); } } diff --git a/packages/pinball_components/lib/src/components/launch_ramp.dart b/packages/pinball_components/lib/src/components/launch_ramp.dart index c3d7624d..c144f38d 100644 --- a/packages/pinball_components/lib/src/components/launch_ramp.dart +++ b/packages/pinball_components/lib/src/components/launch_ramp.dart @@ -42,10 +42,21 @@ class LaunchRamp extends Forge2DBlueprint { /// {@endtemplate} class _LaunchRampBase extends BodyComponent with InitialPosition, Layered { /// {@macro launch_ramp_base} - _LaunchRampBase() : super(priority: Ball.launchRampPriority - 1) { + _LaunchRampBase() + : super( + priority: Ball.launchRampPriority - 1, + children: [ + _LaunchRampBackgroundRailingSpriteComponent(), + _LaunchRampBaseSpriteComponent(), + ], + ) { layer = Layer.launcher; + renderBody = false; } + // TODO(ruimiguel): final asset differs slightly from the current shape. We + // need to fix shape with correct vertices, but right now merge them to have + // final assets at game and not be blocked. List _createFixtureDefs() { final fixturesDef = []; @@ -114,28 +125,36 @@ class _LaunchRampBase extends BodyComponent with InitialPosition, Layered { return body; } +} +class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef { @override Future onLoad() async { await super.onLoad(); - renderBody = false; - await add(_LaunchRampBaseSpriteComponent()); + final sprite = await gameRef.loadSprite( + Assets.images.launchRamp.ramp.keyName, + ); + this.sprite = sprite; + size = sprite.originalSize / 10; + anchor = Anchor.center; + position = Vector2(25.65, 0.7); } } -class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef { +class _LaunchRampBackgroundRailingSpriteComponent extends SpriteComponent + with HasGameRef { @override Future onLoad() async { await super.onLoad(); final sprite = await gameRef.loadSprite( - Assets.images.launchRamp.ramp.keyName, + Assets.images.launchRamp.backgroundRailing.keyName, ); this.sprite = sprite; size = sprite.originalSize / 10; anchor = Anchor.center; - position = Vector2(25.65, 0); + position = Vector2(25.6, -1.3); } } @@ -145,7 +164,13 @@ class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef { /// {@endtemplate} class _LaunchRampForegroundRailing extends BodyComponent with InitialPosition { /// {@macro launch_ramp_foreground_railing} - _LaunchRampForegroundRailing() : super(priority: Ball.launchRampPriority + 1); + _LaunchRampForegroundRailing() + : super( + priority: Ball.launchRampPriority + 1, + children: [_LaunchRampForegroundRailingSpriteComponent()], + ) { + renderBody = false; + } List _createFixtureDefs() { final fixturesDef = []; @@ -153,7 +178,7 @@ class _LaunchRampForegroundRailing extends BodyComponent with InitialPosition { final rightStraightShape = EdgeShape() ..set( Vector2(27.6, -57.9), - Vector2(30, -35.1), + Vector2(38.1, 42.6), ); final rightStraightFixtureDef = FixtureDef(rightStraightShape); fixturesDef.add(rightStraightFixtureDef); @@ -189,14 +214,6 @@ class _LaunchRampForegroundRailing extends BodyComponent with InitialPosition { return body; } - - @override - Future onLoad() async { - await super.onLoad(); - renderBody = false; - - await add(_LaunchRampForegroundRailingSpriteComponent()); - } } class _LaunchRampForegroundRailingSpriteComponent extends SpriteComponent @@ -211,7 +228,7 @@ class _LaunchRampForegroundRailingSpriteComponent extends SpriteComponent this.sprite = sprite; size = sprite.originalSize / 10; anchor = Anchor.center; - position = Vector2(22.8, 0); + position = Vector2(22.8, 0.5); } } diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index be06949c..da4446c1 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -39,6 +39,7 @@ flutter: assets: - assets/images/ + - assets/images/ball/ - assets/images/baseboard/ - assets/images/boundary/ - assets/images/dino/ diff --git a/packages/pinball_components/test/src/components/ball_test.dart b/packages/pinball_components/test/src/components/ball_test.dart index a0a73e2b..26a03886 100644 --- a/packages/pinball_components/test/src/components/ball_test.dart +++ b/packages/pinball_components/test/src/components/ball_test.dart @@ -1,5 +1,6 @@ // ignore_for_file: cascade_invocations +import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter/material.dart'; @@ -169,20 +170,41 @@ void main() { expect(ball.body.linearVelocity, equals(Vector2.zero())); - ball.boost(Vector2.all(10)); + await ball.boost(Vector2.all(10)); expect(ball.body.linearVelocity.x, greaterThan(0)); expect(ball.body.linearVelocity.y, greaterThan(0)); }); - flameTester.test('adds fire effect components to the game', (game) async { + flameTester.test('adds TurboChargeSpriteAnimation', (game) async { final ball = Ball(baseColor: Colors.blue); await game.ensureAdd(ball); - ball.boost(Vector2.all(10)); + await ball.boost(Vector2.all(10)); game.update(0); - await game.ready(); - expect(game.children.whereType().length, greaterThan(0)); + expect( + ball.children.whereType().single, + isNotNull, + ); + }); + + flameTester.test('removes TurboChargeSpriteAnimation after it finishes', + (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ensureAdd(ball); + + await ball.boost(Vector2.all(10)); + game.update(0); + + final turboChargeSpriteAnimation = + ball.children.whereType().single; + + expect(ball.contains(turboChargeSpriteAnimation), isTrue); + + game.update(turboChargeSpriteAnimation.animation!.totalDuration()); + game.update(0.1); + + expect(ball.contains(turboChargeSpriteAnimation), isFalse); }); }); }); diff --git a/packages/pinball_components/test/src/components/golden/boundaries.png b/packages/pinball_components/test/src/components/golden/boundaries.png index f184e3eb..4ac8f5eb 100644 Binary files a/packages/pinball_components/test/src/components/golden/boundaries.png and b/packages/pinball_components/test/src/components/golden/boundaries.png differ diff --git a/packages/pinball_components/test/src/components/golden/dino-bottom-wall.png b/packages/pinball_components/test/src/components/golden/dino-bottom-wall.png deleted file mode 100644 index 14ae9c0d..00000000 Binary files a/packages/pinball_components/test/src/components/golden/dino-bottom-wall.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/dino-top-wall.png b/packages/pinball_components/test/src/components/golden/dino-top-wall.png deleted file mode 100644 index 0d434d69..00000000 Binary files a/packages/pinball_components/test/src/components/golden/dino-top-wall.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/dino-walls.png b/packages/pinball_components/test/src/components/golden/dino-walls.png index 80b07157..8c2ee569 100644 Binary files a/packages/pinball_components/test/src/components/golden/dino-walls.png and b/packages/pinball_components/test/src/components/golden/dino-walls.png differ diff --git a/packages/pinball_components/test/src/components/golden/launch-ramp.png b/packages/pinball_components/test/src/components/golden/launch-ramp.png index e872c533..52ab2510 100644 Binary files a/packages/pinball_components/test/src/components/golden/launch-ramp.png and b/packages/pinball_components/test/src/components/golden/launch-ramp.png differ diff --git a/packages/pinball_components/test/src/components/launch_ramp_test.dart b/packages/pinball_components/test/src/components/launch_ramp_test.dart index 1f5d6f26..2defc168 100644 --- a/packages/pinball_components/test/src/components/launch_ramp_test.dart +++ b/packages/pinball_components/test/src/components/launch_ramp_test.dart @@ -16,6 +16,7 @@ void main() { 'renders correctly', setUp: (game, tester) async { await game.addFromBlueprint(LaunchRamp()); + await game.ready(); game.camera.followVector2(Vector2.zero()); game.camera.zoom = 4.1; }, diff --git a/test/game/components/controlled_ball_test.dart b/test/game/components/controlled_ball_test.dart index 96c67dd4..e615d508 100644 --- a/test/game/components/controlled_ball_test.dart +++ b/test/game/components/controlled_ball_test.dart @@ -94,6 +94,7 @@ void main() { final controller = WrappedBallController(ball, gameRef); when(() => gameRef.read()).thenReturn(gameBloc); when(() => ball.controller).thenReturn(controller); + when(() => ball.boost(any())).thenAnswer((_) async {}); await controller.turboCharge(); @@ -109,6 +110,7 @@ void main() { final controller = WrappedBallController(ball, gameRef); when(() => gameRef.read()).thenReturn(gameBloc); when(() => ball.controller).thenReturn(controller); + when(() => ball.boost(any())).thenAnswer((_) async {}); await controller.turboCharge(); @@ -124,6 +126,7 @@ void main() { final controller = WrappedBallController(ball, gameRef); when(() => gameRef.read()).thenReturn(gameBloc); when(() => ball.controller).thenReturn(controller); + when(() => ball.boost(any())).thenAnswer((_) async {}); await controller.turboCharge();