pixi-rainman-game-engine 0.3.43 → 0.3.44-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/ComponentRegistry/ComponentRegistry.js +5 -3
  2. package/dist/ComponentRegistry/types.d.ts +2 -1
  3. package/dist/ComponentRegistry/types.js +1 -1
  4. package/dist/DescribedPlayableAction/DescribedPlayableAction.d.ts +3 -0
  5. package/dist/DescribedPlayableAction/DescribedPlayableAction.js +1 -0
  6. package/dist/application/ButtonsEventManager/ButtonsEventManager.d.ts +1 -0
  7. package/dist/application/ButtonsEventManager/ButtonsEventManager.js +9 -0
  8. package/dist/application/SoundManager/SoundManager.d.ts +1 -10
  9. package/dist/application/SoundManager/SoundManager.js +17 -56
  10. package/dist/components/AbstractMainContainer/AbstractMainContainer.d.ts +4 -1
  11. package/dist/components/AbstractMainContainer/AbstractMainContainer.js +29 -8
  12. package/dist/components/buttons/buttonGroups/MiddleButtons.d.ts +3 -2
  13. package/dist/components/buttons/buttonGroups/MiddleButtons.js +18 -4
  14. package/dist/components/buttons/buttonGroups/RightButtonsMobile.d.ts +3 -3
  15. package/dist/components/buttons/buttonGroups/RightButtonsMobile.js +10 -7
  16. package/dist/components/buttons/default/SpeedControlButtonMobile.d.ts +22 -0
  17. package/dist/components/buttons/default/SpeedControlButtonMobile.js +51 -0
  18. package/dist/components/buttons/default/index.d.ts +1 -0
  19. package/dist/components/buttons/default/index.js +1 -0
  20. package/dist/components/common/AnimatedNumber.js +10 -1
  21. package/dist/components/symbols/AbstractSymbolBase.d.ts +2 -0
  22. package/dist/components/symbols/AbstractSymbolBase.js +35 -8
  23. package/dist/controllers/AbstractController.d.ts +3 -0
  24. package/dist/controllers/AbstractController.js +31 -14
  25. package/dist/controllers/UiController.d.ts +0 -1
  26. package/dist/controllers/UiController.js +7 -4
  27. package/dist/stores/SettingsStore.d.ts +1 -0
  28. package/dist/stores/SettingsStore.js +1 -0
  29. package/dist/utils/common/sound.js +8 -9
  30. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { isIncentiveComponentInterface, isSpeedAdapterInterface, SpeedLevelHandler, TimedIncentiveController } from "../application";
2
- import { AbstractSymbolsColumn, SpeedControlButton } from "../components";
2
+ import { AbstractSymbolsColumn, SpeedControlButton, SpeedControlButtonMobile } from "../components";
3
3
  import { RainMan } from "../Rainman";
4
4
  import { openFullscreen } from "../utils";
5
5
  /**
@@ -49,7 +49,8 @@ export class ComponentRegistry {
49
49
  this.speedLevelHandler.setSpeed(newSpeedLevel);
50
50
  RainMan.settingsStore.setSpeedLevel(this.speedLevelHandler.currentSpeed);
51
51
  this.updateAllSpeedAdaptable(RainMan.settingsStore.currentSpeed);
52
- this.get(SpeedControlButton.registryName).forceTextureUpdate();
52
+ this.getIfExists(SpeedControlButton.registryName)?.forceTextureUpdate();
53
+ this.getIfExists(SpeedControlButtonMobile.registryName)?.forceTextureUpdate();
53
54
  }
54
55
  /**
55
56
  * Setting the next available speed level
@@ -60,7 +61,8 @@ export class ComponentRegistry {
60
61
  this.speedLevelHandler.nextSpeed();
61
62
  RainMan.settingsStore.setSpeedLevel(this.speedLevelHandler.currentSpeed);
62
63
  this.updateAllSpeedAdaptable(RainMan.settingsStore.currentSpeed);
63
- this.get(SpeedControlButton.registryName).forceTextureUpdate();
64
+ this.getIfExists(SpeedControlButton.registryName)?.forceTextureUpdate();
65
+ this.getIfExists(SpeedControlButtonMobile.registryName)?.forceTextureUpdate();
64
66
  }
65
67
  /**
66
68
  * Getter for current speed level
@@ -1,4 +1,4 @@
1
- import { AbstractColumnsContainer, AbstractFrame, AbstractInnerFrame, AutoplayButton, Background, BUTTONS, COMPONENTS, FreeSpinButton, GambleButton, InfoButton, MessageBox, MoreButton, NegButton, PlusButton, RefreshButton, SpeedControlButton, StylefulUpdatableText, TakeButton, UpdatableTextComponent, UpdatableValueComponent, VolumeButton } from "../components";
1
+ import { AbstractColumnsContainer, AbstractFrame, AbstractInnerFrame, AutoplayButton, Background, BUTTONS, COMPONENTS, FreeSpinButton, GambleButton, InfoButton, MessageBox, MoreButton, NegButton, PlusButton, RefreshButton, SpeedControlButton, SpeedControlButtonMobile, StylefulUpdatableText, TakeButton, UpdatableTextComponent, UpdatableValueComponent, VolumeButton } from "../components";
2
2
  /**
3
3
  * RegistryMap is a map of all components that are registered in the ComponentRegistry.
4
4
  * It is used to type-check the components that are registered in the ComponentRegistry.
@@ -11,6 +11,7 @@ export interface RegistryMap {
11
11
  [MessageBox.gamePaysTextRegistryName]: UpdatableTextComponent;
12
12
  [MessageBox.registryName]: MessageBox;
13
13
  [SpeedControlButton.registryName]: SpeedControlButton;
14
+ [SpeedControlButtonMobile.registryName]: SpeedControlButtonMobile;
14
15
  [Background.registryName]: Background;
15
16
  [VolumeButton.registryName]: VolumeButton;
16
17
  [AutoplayButton.registryName]: AutoplayButton;
@@ -1 +1 @@
1
- import { AbstractColumnsContainer, AbstractFrame, AbstractInnerFrame, AutoplayButton, Background, BUTTONS, COMPONENTS, FreeSpinButton, InfoButton, MessageBox, MoreButton, NegButton, PlusButton, RefreshButton, SpeedControlButton, VolumeButton } from "../components";
1
+ import { AbstractColumnsContainer, AbstractFrame, AbstractInnerFrame, AutoplayButton, Background, BUTTONS, COMPONENTS, FreeSpinButton, InfoButton, MessageBox, MoreButton, NegButton, PlusButton, RefreshButton, SpeedControlButton, SpeedControlButtonMobile, VolumeButton } from "../components";
@@ -10,5 +10,8 @@ export declare class DescribedPlayableAction<T> {
10
10
  winAmount: Nullable<number>;
11
11
  skipLooping: boolean;
12
12
  isFirstStreak: boolean;
13
+ animationTarget: {
14
+ isCurrentAnimationPastProgressThreshold?: () => boolean;
15
+ } | null;
13
16
  constructor(type: T, action: PlayableAction, winAmount?: Nullable<number>, skipLooping?: boolean);
14
17
  }
@@ -9,6 +9,7 @@ export class DescribedPlayableAction {
9
9
  winAmount;
10
10
  skipLooping;
11
11
  isFirstStreak = false;
12
+ animationTarget = null;
12
13
  constructor(type, action, winAmount = null, skipLooping = false) {
13
14
  this.type = type;
14
15
  this.action = action;
@@ -154,6 +154,7 @@ export declare class ButtonsEventManager {
154
154
  * @returns {void}
155
155
  */
156
156
  private handleModalInvocation;
157
+ private closeAllModals;
157
158
  /**
158
159
  * Function for closing all other modals except the one with given name
159
160
  * @param {string} activeLayerName - name of the layer that should remain open
@@ -62,6 +62,7 @@ export class ButtonsEventManager {
62
62
  RainMan.settingsStore.handleModalInvocation = this.handleModalInvocation.bind(this);
63
63
  RainMan.settingsStore.enableButtons = this.enableButtons.bind(this);
64
64
  RainMan.settingsStore.closeOtherModals = this.closeOtherModals.bind(this);
65
+ RainMan.settingsStore.closeAllModals = this.closeAllModals.bind(this);
65
66
  RainMan.settingsStore.closeCurrentlyOpenModal = this.closeCurrentlyOpenModal.bind(this);
66
67
  }
67
68
  /**
@@ -359,6 +360,14 @@ export class ButtonsEventManager {
359
360
  }
360
361
  }
361
362
  }
363
+ closeAllModals() {
364
+ for (const itemId of allUiItems) {
365
+ const layer = window.document.getElementById(itemId);
366
+ if (layer !== null && layer.style.display === "flex") {
367
+ layer.style.display = "none";
368
+ }
369
+ }
370
+ }
362
371
  /**
363
372
  * Function for closing all other modals except the one with given name
364
373
  * @param {string} activeLayerName - name of the layer that should remain open
@@ -16,7 +16,6 @@ export declare class SoundManagerInstance implements SpeedAdapterInterface<Sound
16
16
  readonly speedLevelMapper: SpeedLevelDirectory<SoundOverrides>;
17
17
  private musicCatalogue;
18
18
  private currentAmbientTrack;
19
- private currentAmbientOffset;
20
19
  private loadedSoundAssets;
21
20
  private audioContextRecoveryPromise;
22
21
  constructor();
@@ -41,7 +40,7 @@ export declare class SoundManagerInstance implements SpeedAdapterInterface<Sound
41
40
  * @returns {void}
42
41
  */
43
42
  stopPlayingFxSounds(): void;
44
- pauseSoundsForPageHide(): void;
43
+ stopSoundsForPageHide(): void;
45
44
  /**
46
45
  * Function for resuming and pausing sound in game
47
46
  * @public
@@ -67,14 +66,6 @@ export declare class SoundManagerInstance implements SpeedAdapterInterface<Sound
67
66
  private getOppositeAmbientTrack;
68
67
  private getCurrentAmbientTrack;
69
68
  private getSound;
70
- private getAmbientPlaybackOffset;
71
- private trackAmbientPlaybackProgress;
72
- /**
73
- * Saves active ambient position before the browser interrupts WebAudio playback.
74
- * @public
75
- * @returns {void}
76
- */
77
- captureAmbientPlaybackPositionForPageHide(): void;
78
69
  private isIOSWebAudioRuntime;
79
70
  private recreatePixiSoundContext;
80
71
  private recreatePixiSoundContextOnce;
@@ -31,7 +31,6 @@ export class SoundManagerInstance {
31
31
  };
32
32
  musicCatalogue = new Map();
33
33
  currentAmbientTrack = null;
34
- currentAmbientOffset = 0;
35
34
  loadedSoundAssets = {};
36
35
  audioContextRecoveryPromise = null;
37
36
  constructor() {
@@ -101,16 +100,19 @@ export class SoundManagerInstance {
101
100
  sound.stop();
102
101
  }
103
102
  }
104
- pauseSoundsForPageHide() {
105
- this.musicCatalogue.forEach((sound, soundName) => {
106
- if (!sound.isPlaying || sound.paused) {
107
- return;
108
- }
109
- if (soundName === SoundTracks.freeSpinsMusic || soundName === SoundTracks.music) {
110
- this.currentAmbientTrack = soundName;
111
- }
112
- });
113
- sound.pauseAll();
103
+ stopSoundsForPageHide() {
104
+ const music = this.getSound(SoundTracks.music);
105
+ const freeSpinsMusic = this.getSound(SoundTracks.freeSpinsMusic);
106
+ const isMusicPlaying = Boolean(music?.isPlaying && !music.paused);
107
+ const isFreeSpinsMusicPlaying = Boolean(freeSpinsMusic?.isPlaying && !freeSpinsMusic.paused);
108
+ if (isFreeSpinsMusicPlaying) {
109
+ this.currentAmbientTrack = SoundTracks.freeSpinsMusic;
110
+ }
111
+ else if (isMusicPlaying) {
112
+ this.currentAmbientTrack = SoundTracks.music;
113
+ }
114
+ this.stopAmbientMusic();
115
+ this.stopPlayingFxSounds();
114
116
  }
115
117
  /**
116
118
  * Function for resuming and pausing sound in game
@@ -154,15 +156,12 @@ export class SoundManagerInstance {
154
156
  return;
155
157
  }
156
158
  }
157
- const instance = sound.play({
159
+ sound.play({
158
160
  singleInstance: true,
159
161
  loop: soundsToLoop.includes(soundName),
160
162
  volume: this.getVolumeLevel(soundName),
161
163
  speed: soundName === "cardDrawing" ? 0.8 : 1
162
164
  });
163
- if (this.isAmbientTrack(soundName) && !(instance instanceof Promise)) {
164
- this.trackAmbientPlaybackProgress(soundName, sound, instance);
165
- }
166
165
  }
167
166
  /**
168
167
  * Returns the volume level for a given sound track.
@@ -193,41 +192,6 @@ export class SoundManagerInstance {
193
192
  const overrideSoundName = this.overrides[soundName];
194
193
  return this.musicCatalogue.get(overrideSoundName ?? soundName);
195
194
  }
196
- getAmbientPlaybackOffset(sound) {
197
- if (!sound.isPlayable || !Number.isFinite(sound.duration) || sound.duration <= 0) {
198
- return 0;
199
- }
200
- return this.currentAmbientOffset % sound.duration;
201
- }
202
- trackAmbientPlaybackProgress(soundName, sound, instance) {
203
- if (!sound.isPlayable || sound.duration <= 0) {
204
- return;
205
- }
206
- instance.on("progress", (progress) => {
207
- if (this.currentAmbientTrack !== soundName || !Number.isFinite(progress)) {
208
- return;
209
- }
210
- this.currentAmbientOffset = (progress * sound.duration) % sound.duration;
211
- });
212
- }
213
- /**
214
- * Saves active ambient position before the browser interrupts WebAudio playback.
215
- * @public
216
- * @returns {void}
217
- */
218
- captureAmbientPlaybackPositionForPageHide() {
219
- if (!RainMan.settingsStore.isSoundStarted || !RainMan.settingsStore.shouldPlayAmbientMusic) {
220
- return;
221
- }
222
- const ambientTrack = this.getCurrentAmbientTrack();
223
- const ambientSound = this.getSound(ambientTrack);
224
- const ambientInstance = ambientSound?.instances.find((instance) => Number.isFinite(instance.progress) && instance.progress > 0);
225
- if (!ambientSound || !ambientInstance || !ambientSound.isPlayable || ambientSound.duration <= 0) {
226
- return;
227
- }
228
- this.currentAmbientTrack = ambientTrack;
229
- this.currentAmbientOffset = (ambientInstance.progress * ambientSound.duration) % ambientSound.duration;
230
- }
231
195
  isIOSWebAudioRuntime() {
232
196
  const userAgent = navigator.userAgent;
233
197
  const platform = navigator.platform;
@@ -320,16 +284,12 @@ export class SoundManagerInstance {
320
284
  this.stop(this.getOppositeAmbientTrack(ambientTrack));
321
285
  ambientSound.stop();
322
286
  this.currentAmbientTrack = ambientTrack;
323
- const instance = ambientSound.play({
287
+ ambientSound.play({
324
288
  singleInstance: true,
325
289
  loop: soundsToLoop.includes(ambientTrack),
326
- start: this.getAmbientPlaybackOffset(ambientSound),
327
290
  volume: this.getVolumeLevel(ambientTrack),
328
291
  speed: 1
329
292
  });
330
- if (!(instance instanceof Promise)) {
331
- this.trackAmbientPlaybackProgress(ambientTrack, ambientSound, instance);
332
- }
333
293
  }
334
294
  /**
335
295
  * Restores ambient playback after page visibility change or iOS app interruption.
@@ -437,7 +397,8 @@ export class SoundManagerInstance {
437
397
  if (!isAudioContextRunning) {
438
398
  return false;
439
399
  }
440
- return this.restorePlaybackAfterPageHide();
400
+ this.restoreAmbientPlayback();
401
+ return true;
441
402
  }
442
403
  /**
443
404
  * Returns information if audio context is ready to play sounds.
@@ -6,7 +6,7 @@ import { RainMan } from "../../Rainman";
6
6
  import { Nullable, PromiseResolverType } from "../../utils";
7
7
  import { BigWinContainer, MysteryWinContainer, UpdatableSpineContainer } from "../../winComponents";
8
8
  import { AbstractFreeSpinContainer } from "../AbstractFreeSpinContainer";
9
- import { LeftButtons, LeftButtonsLandscape, LeftButtonsMobile, MiddleButtons, RightButtons, RightButtonsLandscape, RightButtonsMobile } from "../buttons";
9
+ import { LeftButtons, LeftButtonsLandscape, LeftButtonsMobile, MiddleButtons, RightButtons, RightButtonsLandscape, RightButtonsMobile, SpeedControlButtonMobile } from "../buttons";
10
10
  import { FreeSpinButton, Logo, ResponsiveContainer } from "../common";
11
11
  import { IResizableContainer } from "../common/IResizableContainer";
12
12
  import { AbstractFrame } from "../frame";
@@ -34,6 +34,7 @@ export declare abstract class AbstractMainContainer extends Container {
34
34
  static readonly registryName = "mainContainer";
35
35
  messageBox: MessageBox;
36
36
  protected bigWinContainer: Nullable<UpdatableSpineContainer> | BigWinContainer;
37
+ protected readonly mobileSpeedButton: SpeedControlButtonMobile;
37
38
  protected mysteryWinContainer: Nullable<UpdatableSpineContainer> | MysteryWinContainer;
38
39
  protected bonusWinContainer: Nullable<UpdatableSpineContainer>;
39
40
  protected superBonusWinContainer: Nullable<UpdatableSpineContainer>;
@@ -75,6 +76,7 @@ export declare abstract class AbstractMainContainer extends Container {
75
76
  * @returns {Promise<void>} - A promise that resolves when the animation is complete.
76
77
  */
77
78
  invokeMysteryWinPlate(_animationName: string, _popupAnimation: string, _winType: WinTypeId, _amount: number, _isAdditionalFreeSpins: boolean): Promise<void>;
79
+ protected attachMobileSpeedButton(parent: Container): void;
78
80
  /**
79
81
  * This function initializes the spines that should be paused when the battery saver is on
80
82
  * @protected
@@ -318,6 +320,7 @@ export declare abstract class AbstractMainContainer extends Container {
318
320
  * @returns {Promise<void>} - A promise that resolves when the gamble game is finished.
319
321
  */
320
322
  invokeGambleGame(): Promise<void>;
323
+ private destroyDisplayContainer;
321
324
  /**
322
325
  * Function for creating the under frame panel.
323
326
  * This panel is used in mobile portrait mode to display additional information.
@@ -3,7 +3,7 @@ import { ButtonsEventManager, SoundManager, SoundTracks } from "../../applicatio
3
3
  import { AnimationLayer, BackgroundLayer, FrameLayer, UXLayer } from "../../layers";
4
4
  import { RainMan } from "../../Rainman";
5
5
  import { getDeviceOrientation, globalMinRatio } from "../../utils";
6
- import { BUTTONS, LeftButtons, LeftButtonsLandscape, LeftButtonsMobile, MiddleButtons, RefreshButton, RightButtons, RightButtonsLandscape, RightButtonsMobile } from "../buttons";
6
+ import { BUTTONS, LeftButtons, LeftButtonsLandscape, LeftButtonsMobile, MiddleButtons, RefreshButton, RightButtons, RightButtonsLandscape, RightButtonsMobile, SpeedControlButtonMobile } from "../buttons";
7
7
  import { GambleGame } from "../GambleGame";
8
8
  import { MessageBox } from "../messageBox";
9
9
  /**
@@ -29,6 +29,7 @@ export class AbstractMainContainer extends Container {
29
29
  static registryName = "mainContainer";
30
30
  messageBox;
31
31
  bigWinContainer = null;
32
+ mobileSpeedButton = new SpeedControlButtonMobile();
32
33
  mysteryWinContainer = null;
33
34
  bonusWinContainer = null;
34
35
  superBonusWinContainer = null;
@@ -59,6 +60,9 @@ export class AbstractMainContainer extends Container {
59
60
  this.name = AbstractMainContainer.registryName;
60
61
  this.buttonsEventManager = new ButtonsEventManager();
61
62
  this.messageBox = new MessageBox();
63
+ if (!RainMan.componentRegistry.has(SpeedControlButtonMobile.registryName)) {
64
+ RainMan.componentRegistry.add(this.mobileSpeedButton);
65
+ }
62
66
  SoundManager.setPlayStatus(SoundTracks.music, RainMan.settingsStore.shouldPlayAmbientMusic);
63
67
  RainMan.settingsStore.setHandleInvokeFreeSpinPlate(this.invokeFreeSpinPlate.bind(this));
64
68
  RainMan.settingsStore.setHandleBuyFreeSpinPopupActivatio(this.invokeBuyFreeSpinPlate.bind(this));
@@ -75,6 +79,13 @@ export class AbstractMainContainer extends Container {
75
79
  * @returns {Promise<void>} - A promise that resolves when the animation is complete.
76
80
  */
77
81
  async invokeMysteryWinPlate(_animationName, _popupAnimation, _winType, _amount, _isAdditionalFreeSpins) { }
82
+ attachMobileSpeedButton(parent) {
83
+ if (this.mobileSpeedButton.parent) {
84
+ this.mobileSpeedButton.parent.removeChild(this.mobileSpeedButton);
85
+ }
86
+ parent.addChild(this.mobileSpeedButton);
87
+ this.mobileSpeedButton.adaptToSpeed(RainMan.componentRegistry.getSpeedLevel());
88
+ }
78
89
  /**
79
90
  * This function initializes the spines that should be paused when the battery saver is on
80
91
  * @protected
@@ -232,7 +243,7 @@ export class AbstractMainContainer extends Container {
232
243
  await this._freeSpinPlate.onStart();
233
244
  });
234
245
  if (this._freeSpinPlate !== null) {
235
- this.removeChild(this._freeSpinPlate);
246
+ this.destroyDisplayContainer(this._freeSpinPlate);
236
247
  this._freeSpinPlate = null;
237
248
  }
238
249
  this.hideOverlay();
@@ -241,6 +252,7 @@ export class AbstractMainContainer extends Container {
241
252
  invokeBuyFreeSpinPlate() {
242
253
  const refreshButton = RainMan.componentRegistry.get(RefreshButton.registryName);
243
254
  refreshButton.flipDisabledFlag(true);
255
+ RainMan.settingsStore.closeAllModals?.();
244
256
  this._buyFreeSpinsPlate = this.createBuyFreeSpinPlate();
245
257
  if (!this._buyFreeSpinsPlate) {
246
258
  refreshButton.flipDisabledFlag(false);
@@ -292,7 +304,7 @@ export class AbstractMainContainer extends Container {
292
304
  this.addChild(this._freeSpinSummary);
293
305
  });
294
306
  if (this._freeSpinSummary !== null) {
295
- this.removeChild(this._freeSpinSummary);
307
+ this.destroyDisplayContainer(this._freeSpinSummary);
296
308
  this._freeSpinSummary = null;
297
309
  }
298
310
  this.hideOverlay();
@@ -309,7 +321,7 @@ export class AbstractMainContainer extends Container {
309
321
  return;
310
322
  }
311
323
  this.freeSpinSummary.onClick();
312
- this.removeChild(this.freeSpinSummary);
324
+ this.destroyDisplayContainer(this.freeSpinSummary);
313
325
  this._freeSpinSummary = null;
314
326
  this.hideOverlay();
315
327
  }
@@ -559,7 +571,7 @@ export class AbstractMainContainer extends Container {
559
571
  this.addChild(this.gambleGame);
560
572
  });
561
573
  if (this.gambleGame) {
562
- this.removeChild(this.gambleGame);
574
+ this.destroyDisplayContainer(this.gambleGame);
563
575
  this.messageBox.visible = true;
564
576
  RainMan.settingsStore.isGambleGameActive = false;
565
577
  this._controller.updateGambleValues(RainMan.settingsStore.gambleGameWin);
@@ -571,6 +583,15 @@ export class AbstractMainContainer extends Container {
571
583
  }
572
584
  this.hideOverlay();
573
585
  }
586
+ destroyDisplayContainer(container) {
587
+ if (container.destroyed) {
588
+ return;
589
+ }
590
+ if (container.parent) {
591
+ container.parent.removeChild(container);
592
+ }
593
+ container.destroy({ children: true });
594
+ }
574
595
  /**
575
596
  * Function for creating the under frame panel.
576
597
  * This panel is used in mobile portrait mode to display additional information.
@@ -771,7 +792,7 @@ export class AbstractMainContainer extends Container {
771
792
  * @returns {RightButtonsMobile} - Returns a new instance of RightButtonsMobile for mobile portrait orientation.
772
793
  */
773
794
  createRightButtonsMobile() {
774
- return new RightButtonsMobile();
795
+ return new RightButtonsMobile(this.mobileSpeedButton);
775
796
  }
776
797
  /**
777
798
  * Function for creating the left buttons for landscape orientation
@@ -795,7 +816,7 @@ export class AbstractMainContainer extends Container {
795
816
  * @returns {MiddleButtons} - Returns a new instance of MiddleButtons for portrait orientation.
796
817
  */
797
818
  createPortraitPlayButtons() {
798
- return new MiddleButtons();
819
+ return new MiddleButtons(false);
799
820
  }
800
821
  /**
801
822
  * Function for creating the landscape play buttons
@@ -803,7 +824,7 @@ export class AbstractMainContainer extends Container {
803
824
  * @returns {MiddleButtons} - Returns a new instance of MiddleButtons for landscape orientation.
804
825
  */
805
826
  createLandscapePlayButtons() {
806
- return new MiddleButtons(true);
827
+ return new MiddleButtons(true, this.mobileSpeedButton);
807
828
  }
808
829
  /**
809
830
  * This method initializes the components for the current screen ratio.
@@ -1,6 +1,6 @@
1
1
  import { Graphics } from "pixi.js";
2
2
  import { ResponsiveContainer } from "../../common";
3
- import { AutoplayButton, GambleButton, PlusButton, RefreshButton, TakeButton } from "../default";
3
+ import { AutoplayButton, GambleButton, PlusButton, RefreshButton, SpeedControlButtonMobile, TakeButton } from "../default";
4
4
  /**
5
5
  * Class for managing middle buttons in game (MOBILE ONLY).
6
6
  * The class uses {@link AutoplayButton}, {@link PlusButton}, {@link RefreshButton}.
@@ -8,13 +8,14 @@ import { AutoplayButton, GambleButton, PlusButton, RefreshButton, TakeButton } f
8
8
  */
9
9
  export declare class MiddleButtons extends ResponsiveContainer {
10
10
  private landscape;
11
+ private readonly speedButtonMobile?;
11
12
  protected readonly autoplayButton: AutoplayButton;
12
13
  protected readonly betButton: PlusButton;
13
14
  protected readonly refreshButton: RefreshButton;
14
15
  protected readonly positioningFrame: Graphics;
15
16
  protected readonly gambleButton: GambleButton;
16
17
  protected readonly takeButton: TakeButton;
17
- constructor(landscape?: boolean);
18
+ constructor(landscape?: boolean, speedButtonMobile?: SpeedControlButtonMobile | undefined);
18
19
  /**
19
20
  * Function for removing children from the registry
20
21
  * @public
@@ -11,26 +11,37 @@ import { REGISTRY } from "../registrynames";
11
11
  */
12
12
  export class MiddleButtons extends ResponsiveContainer {
13
13
  landscape;
14
+ speedButtonMobile;
14
15
  autoplayButton = new AutoplayButton(true);
15
16
  betButton = new PlusButton(true);
16
17
  refreshButton = new RefreshButton(true);
17
18
  positioningFrame = new Graphics();
18
19
  gambleButton = new GambleButton(true);
19
20
  takeButton = new TakeButton(true);
20
- constructor(landscape = false) {
21
+ constructor(landscape = false, speedButtonMobile) {
21
22
  super();
22
23
  this.landscape = landscape;
24
+ this.speedButtonMobile = speedButtonMobile;
23
25
  this.name = REGISTRY.middleButtons;
24
26
  this.parentLayer = UXLayer;
25
27
  const buttons = [this.autoplayButton, this.betButton, this.refreshButton];
26
28
  this.addChild(...buttons);
27
29
  RainMan.componentRegistry.addMany(buttons);
30
+ if (this.speedButtonMobile) {
31
+ this.speedButtonMobile.visible = true;
32
+ this.speedButtonMobile.scale.set(1);
33
+ this.speedButtonMobile.adaptToSpeed(RainMan.componentRegistry.getSpeedLevel());
34
+ this.addChild(this.speedButtonMobile);
35
+ }
28
36
  this.addGambleAndTakeButtons();
29
37
  this.positioningFrame.beginFill(0x000000, 0);
30
38
  this.positioningFrame.drawRect(0, 0, 800, 250);
31
39
  this.addChild(this.positioningFrame);
32
40
  this.refreshButton.scale.set(0.75);
33
41
  this.autoplayButton.scale.set(0.75);
42
+ if (this.speedButtonMobile) {
43
+ this.speedButtonMobile.scale.set(1);
44
+ }
34
45
  this.betButton.scale.set(0.75);
35
46
  this.takeButton.scale.set(0.75);
36
47
  this.gambleButton.scale.set(0.75);
@@ -63,7 +74,6 @@ export class MiddleButtons extends ResponsiveContainer {
63
74
  this.refreshButton,
64
75
  this.autoplayButton,
65
76
  this.betButton,
66
- this.positioningFrame,
67
77
  ...(this.gambleButton && RainMan.config.gambleGameEnabled ? [this.gambleButton] : []),
68
78
  ...(this.takeButton ? [this.takeButton] : [])
69
79
  ]);
@@ -92,11 +102,15 @@ export class MiddleButtons extends ResponsiveContainer {
92
102
  const { width, height } = this.positioningFrame;
93
103
  this.refreshButton.x = width / 2;
94
104
  this.refreshButton.y = height / 2;
105
+ if (this.speedButtonMobile) {
106
+ this.speedButtonMobile.x = width / 2;
107
+ this.speedButtonMobile.y = 150;
108
+ }
95
109
  if (this.landscape) {
96
110
  this.betButton.x = width / 2;
97
- this.betButton.y = 0;
111
+ this.betButton.y = -25;
98
112
  this.autoplayButton.x = width / 2;
99
- this.autoplayButton.y = height;
113
+ this.autoplayButton.y = height + 35;
100
114
  }
101
115
  else {
102
116
  this.autoplayButton.x = -width;
@@ -1,17 +1,17 @@
1
1
  import { ITextStyle } from "pixi.js";
2
2
  import { ResponsiveContainer } from "../../common";
3
3
  import { UpdatableTextComponent } from "../../updatable";
4
- import { InfoButton, NegButton, SpeedControlButton } from "../default";
4
+ import { InfoButton, NegButton, SpeedControlButtonMobile } from "../default";
5
5
  /**
6
6
  * Buttons for mobile version. It has a current bet and game information button.
7
7
  * @augments {ResponsiveContainer}
8
8
  */
9
9
  export declare class RightButtonsMobile extends ResponsiveContainer {
10
+ private readonly speedButtonMobile;
10
11
  protected readonly infoButton: InfoButton;
11
12
  protected readonly negButton: NegButton;
12
- protected readonly speedControlButton: SpeedControlButton;
13
13
  protected bet: UpdatableTextComponent;
14
- constructor();
14
+ constructor(speedButtonMobile: SpeedControlButtonMobile);
15
15
  /**
16
16
  * Function for removing children from the registry.
17
17
  * @public
@@ -3,7 +3,7 @@ import { UXLayer } from "../../../layers";
3
3
  import { RainMan } from "../../../Rainman";
4
4
  import { ResponsiveContainer } from "../../common";
5
5
  import { UPDATABLE_MODES, UpdatableTextComponent } from "../../updatable";
6
- import { InfoButton, NegButton, SpeedControlButton } from "../default";
6
+ import { InfoButton, NegButton } from "../default";
7
7
  import { COMPONENTS, REGISTRY } from "../registrynames";
8
8
  import { MOBILE_BUTTONS_GAP, MOBILE_PANEL_BUTTON_SCALE, MOBILE_X_OFFSET, MOBILE_Y_BOTTOM_OFFSET } from "./constants";
9
9
  /**
@@ -11,22 +11,25 @@ import { MOBILE_BUTTONS_GAP, MOBILE_PANEL_BUTTON_SCALE, MOBILE_X_OFFSET, MOBILE_
11
11
  * @augments {ResponsiveContainer}
12
12
  */
13
13
  export class RightButtonsMobile extends ResponsiveContainer {
14
+ speedButtonMobile;
14
15
  infoButton = new InfoButton(true);
15
16
  negButton = new NegButton();
16
- speedControlButton = new SpeedControlButton();
17
17
  bet = new UpdatableTextComponent(COMPONENTS.betText, i18next.t("bet"), UPDATABLE_MODES.money, RainMan.config.mobileFontSize);
18
- constructor() {
18
+ constructor(speedButtonMobile) {
19
19
  super(-1);
20
+ this.speedButtonMobile = speedButtonMobile;
20
21
  this.name = REGISTRY.rightButtons;
21
22
  this.parentLayer = UXLayer;
22
23
  this.negButton.visible = false;
23
- this.speedControlButton.visible = false;
24
24
  this.infoButton.anchor.set(1, 0.5);
25
25
  this.infoButton.scale.set(-MOBILE_PANEL_BUTTON_SCALE);
26
26
  this.bet.pivot.set(this.bet.width, this.bet.height / 2);
27
27
  this.bet.scale.set(-1);
28
- this.addChild(this.infoButton, this.bet);
29
- RainMan.componentRegistry.addMany([this.infoButton, this.bet, this.negButton, this.speedControlButton]);
28
+ this.speedButtonMobile.scale.set(-1);
29
+ this.speedButtonMobile.x = 100;
30
+ this.speedButtonMobile.y = 200;
31
+ this.addChild(this.infoButton, this.bet, this.speedButtonMobile);
32
+ RainMan.componentRegistry.addMany([this.infoButton, this.bet, this.negButton]);
30
33
  this.resize();
31
34
  }
32
35
  /**
@@ -35,7 +38,7 @@ export class RightButtonsMobile extends ResponsiveContainer {
35
38
  * @returns {void}
36
39
  */
37
40
  removeChildrenFromRegistry() {
38
- RainMan.componentRegistry.removeMany([this.infoButton, this.bet, this.negButton, this.speedControlButton]);
41
+ RainMan.componentRegistry.removeMany([this.infoButton, this.bet, this.negButton]);
39
42
  }
40
43
  /**
41
44
  * Function for repositioning components on mobile screens.
@@ -0,0 +1,22 @@
1
+ import { SpeedAdapterInterface, SpeedLevel, SpeedLevelDirectory } from "../../../application";
2
+ import { BaseButton, TextureMap } from "../BaseButton";
3
+ /**
4
+ * Button for speed control {@link SpeedLevel}
5
+ * It needs following textures:
6
+ * - turtle - `speed-turtle.png`
7
+ * - rabbit - `speed-rabbit.png`
8
+ * - cheetah - `speed-cheetah.png`
9
+ * @class
10
+ */
11
+ export declare class SpeedControlButtonMobile extends BaseButton implements SpeedAdapterInterface<TextureMap> {
12
+ static readonly registryName = "speedControlButtonMobile";
13
+ readonly speedLevelMapper: SpeedLevelDirectory<TextureMap>;
14
+ constructor();
15
+ /**
16
+ * Change button's texture according to speed level
17
+ * @public
18
+ * @param {SpeedLevel} speedLevel - new speed level
19
+ * @returns {void}
20
+ */
21
+ adaptToSpeed(speedLevel: SpeedLevel): void;
22
+ }
@@ -0,0 +1,51 @@
1
+ import { RainMan } from "../../../Rainman";
2
+ import { getTexture } from "../../../utils";
3
+ import { BaseButton, ButtonStates } from "../BaseButton";
4
+ const createTextureMap = (speedLevel) => {
5
+ return {
6
+ [ButtonStates.NORMAL]: getTexture(`${speedLevel}.png`),
7
+ [ButtonStates.CLICKED]: getTexture(`${speedLevel}-c.png`)
8
+ };
9
+ };
10
+ const createTextureMapForSpeed = (speedLevel) => {
11
+ const speedLevelMapper = {
12
+ slow: createTextureMap("turtle"),
13
+ normal: createTextureMap("rabbit"),
14
+ fast: createTextureMap("cheetah")
15
+ };
16
+ return speedLevelMapper[speedLevel];
17
+ };
18
+ /**
19
+ * Button for speed control {@link SpeedLevel}
20
+ * It needs following textures:
21
+ * - turtle - `speed-turtle.png`
22
+ * - rabbit - `speed-rabbit.png`
23
+ * - cheetah - `speed-cheetah.png`
24
+ * @class
25
+ */
26
+ export class SpeedControlButtonMobile extends BaseButton {
27
+ static registryName = "speedControlButtonMobile";
28
+ speedLevelMapper;
29
+ constructor() {
30
+ super(SpeedControlButtonMobile.registryName, createTextureMapForSpeed(RainMan.settingsStore.currentSpeed));
31
+ this.speedLevelMapper = {
32
+ slow: createTextureMap("turtle"),
33
+ normal: createTextureMap("rabbit"),
34
+ fast: createTextureMap("cheetah")
35
+ };
36
+ }
37
+ /**
38
+ * Change button's texture according to speed level
39
+ * @public
40
+ * @param {SpeedLevel} speedLevel - new speed level
41
+ * @returns {void}
42
+ */
43
+ adaptToSpeed(speedLevel) {
44
+ if (RainMan.settingsStore.isTemporarySpeed) {
45
+ return;
46
+ }
47
+ const textureMap = this.speedLevelMapper[speedLevel];
48
+ this.setTextureMap(textureMap);
49
+ this.texture = textureMap[ButtonStates.NORMAL];
50
+ }
51
+ }
@@ -9,6 +9,7 @@ export * from "./PlusButton";
9
9
  export * from "./RedButton";
10
10
  export * from "./RefreshButton";
11
11
  export * from "./SpeedControlButton";
12
+ export * from "./SpeedControlButtonMobile";
12
13
  export * from "./SpinConfirmationButton";
13
14
  export * from "./TakeButton";
14
15
  export * from "./VolumeButton";
@@ -9,6 +9,7 @@ export * from "./PlusButton";
9
9
  export * from "./RedButton";
10
10
  export * from "./RefreshButton";
11
11
  export * from "./SpeedControlButton";
12
+ export * from "./SpeedControlButtonMobile";
12
13
  export * from "./SpinConfirmationButton";
13
14
  export * from "./TakeButton";
14
15
  export * from "./VolumeButton";
@@ -68,7 +68,16 @@ export class AnimatedNumber extends Container {
68
68
  * @returns {void}
69
69
  */
70
70
  removeDigits() {
71
- this.digits.forEach((digit) => this.removeChild(digit));
71
+ this.digits.forEach((digit) => {
72
+ if (digit.destroyed) {
73
+ return;
74
+ }
75
+ digit.state.clearListeners();
76
+ if (digit.parent) {
77
+ digit.parent.removeChild(digit);
78
+ }
79
+ digit.destroy({ children: true });
80
+ });
72
81
  this.digits = [];
73
82
  }
74
83
  /**
@@ -91,6 +91,7 @@ export declare abstract class AbstractSymbolBase extends Container implements Sp
91
91
  * @returns {Promise<void>}
92
92
  */
93
93
  handleMultiplierAnimation(multiplier: number): Promise<void>;
94
+ private destroySpine;
94
95
  /**
95
96
  * Function for playing the symbol animation
96
97
  * @public
@@ -98,6 +99,7 @@ export declare abstract class AbstractSymbolBase extends Container implements Sp
98
99
  * @returns {Promise<void>} - A promise that resolves when the animation is complete
99
100
  */
100
101
  play(suffix?: string): Promise<void>;
102
+ isCurrentAnimationPastProgressThreshold(): boolean;
101
103
  /**
102
104
  * Function for stopping the symbol animation.
103
105
  * @public
@@ -31,10 +31,10 @@ export class AbstractSymbolBase extends Container {
31
31
  this._id = symbolId;
32
32
  this.spine = new Spine(this.getResourceForSymbolId(this._id));
33
33
  this.spine.parentLayer = AnimationLayer;
34
- this.symbolImage = new Sprite(getTexture(this.getImage(this._id)));
35
- this.setSymbol(symbolId);
36
34
  this.spine.x = 0;
37
35
  this.spine.y = 0;
36
+ this.symbolImage = new Sprite(getTexture(this.getImage(this._id)));
37
+ this.setSymbol(symbolId);
38
38
  this.eventMode = "static";
39
39
  const symbolRadius = 150;
40
40
  this.hitArea = new Rectangle(-symbolRadius, -symbolRadius, 2 * symbolRadius, 2 * symbolRadius);
@@ -109,6 +109,7 @@ export class AbstractSymbolBase extends Container {
109
109
  return () => {
110
110
  this.swapSymbol(symbolId);
111
111
  return new Promise((resolve) => {
112
+ this.destroySpine(this.spine);
112
113
  this.spine = new Spine(getSpineData(specialResourceName));
113
114
  this.endOfSwapAnimationPromise = resolve;
114
115
  switch (this.animationSpeedMultiplier) {
@@ -143,10 +144,11 @@ export class AbstractSymbolBase extends Container {
143
144
  */
144
145
  switchSymbolBaseDisplayedObject(option, suffix = "") {
145
146
  if (option === "spine") {
146
- this.removeChild(this.spine);
147
- this.spine.x = this.symbolImage.x;
148
- this.spine.y = this.symbolImage.y;
147
+ const { x, y } = this.symbolImage;
148
+ this.destroySpine(this.spine);
149
149
  this.spine = new Spine(this.getResourceForSymbolId(this._id));
150
+ this.spine.x = x;
151
+ this.spine.y = y;
150
152
  const animationName = this.getAnimationNameForSymbol(this._id, suffix);
151
153
  this.spine.parentLayer = AnimationLayer;
152
154
  this.spine.state.setAnimation(0, animationName, false);
@@ -156,14 +158,15 @@ export class AbstractSymbolBase extends Container {
156
158
  this.removeChild(this.symbolImage);
157
159
  return;
158
160
  }
161
+ const { x, y } = this.symbolImage;
159
162
  this.removeChild(this.symbolImage);
160
163
  this.symbolImage.destroy({ children: true });
161
164
  this.symbolImage = new Sprite(getTexture(this.getImage(this._id, suffix)));
162
165
  this.symbolImage.anchor.set(0.5, 0.5);
163
- this.symbolImage.x = this.spine.x;
164
- this.symbolImage.y = this.spine.y;
166
+ this.symbolImage.x = this.spine.destroyed ? x : this.spine.x;
167
+ this.symbolImage.y = this.spine.destroyed ? y : this.spine.y;
165
168
  this.addChild(this.symbolImage);
166
- this.removeChild(this.spine);
169
+ this.destroySpine(this.spine);
167
170
  return;
168
171
  }
169
172
  /**
@@ -182,6 +185,7 @@ export class AbstractSymbolBase extends Container {
182
185
  }
183
186
  else {
184
187
  logError(`$ Multiplier animation 'x${multiplier}' does not exist for symbol ID: ${this._id}`);
188
+ symbolMultiplier.destroy({ children: true });
185
189
  return;
186
190
  }
187
191
  this.addChild(symbolMultiplier);
@@ -194,6 +198,17 @@ export class AbstractSymbolBase extends Container {
194
198
  });
195
199
  });
196
200
  this.removeChild(symbolMultiplier);
201
+ symbolMultiplier.destroy({ children: true });
202
+ }
203
+ destroySpine(spine) {
204
+ if (spine.destroyed) {
205
+ return;
206
+ }
207
+ spine.state.clearListeners();
208
+ if (spine.parent) {
209
+ spine.parent.removeChild(spine);
210
+ }
211
+ spine.destroy({ children: true });
197
212
  }
198
213
  /**
199
214
  * Function for playing the symbol animation
@@ -214,6 +229,18 @@ export class AbstractSymbolBase extends Container {
214
229
  });
215
230
  });
216
231
  }
232
+ isCurrentAnimationPastProgressThreshold() {
233
+ const entry = this.spine.state.tracks[0];
234
+ if (!entry) {
235
+ return false;
236
+ }
237
+ const trackTime = entry.trackTime ?? 0;
238
+ const animationEnd = entry.animationEnd ?? 0;
239
+ if (animationEnd <= 0) {
240
+ return false;
241
+ }
242
+ return trackTime >= animationEnd / 3;
243
+ }
217
244
  /**
218
245
  * Function for stopping the symbol animation.
219
246
  * @public
@@ -57,6 +57,9 @@ export declare abstract class AbstractController<T> {
57
57
  shouldStopMobileSpin: boolean;
58
58
  disableMessageBoxChange: boolean;
59
59
  shouldClearQuickReelsStopAfterSpin: boolean;
60
+ protected firstAnimationTarget: {
61
+ isCurrentAnimationPastProgressThreshold?: () => boolean;
62
+ } | null;
60
63
  freeSpinsWin: Nullable<number>;
61
64
  protected resolveBigWin?: () => void;
62
65
  protected resolveMysteryWin?: () => void;
@@ -1,7 +1,7 @@
1
1
  import i18next from "i18next";
2
2
  import { sample } from "lodash";
3
3
  import { ApiConfig, SoundManager, SoundTracks, SPEED_LEVELS } from "../application";
4
- import { AbstractFrame, AutoplayButton, BUTTONS, COMPONENTS, FreeSpinButton, MessageBox, RefreshButton, SpeedControlButton } from "../components";
4
+ import { AbstractFrame, AutoplayButton, BUTTONS, COMPONENTS, FreeSpinButton, MessageBox, RefreshButton, SpeedControlButton, SpeedControlButtonMobile } from "../components";
5
5
  import { SpinData } from "../connectivity";
6
6
  import { gamePhases, PossibleWins } from "../constants";
7
7
  import { RainMan } from "../Rainman";
@@ -57,6 +57,7 @@ export class AbstractController {
57
57
  shouldStopMobileSpin = false;
58
58
  disableMessageBoxChange = false;
59
59
  shouldClearQuickReelsStopAfterSpin = false;
60
+ firstAnimationTarget = null;
60
61
  freeSpinsWin = null;
61
62
  resolveBigWin;
62
63
  resolveMysteryWin;
@@ -256,7 +257,10 @@ export class AbstractController {
256
257
  * @returns {void}
257
258
  */
258
259
  disableSpeedButton(shouldBeDisabled) {
259
- RainMan.componentRegistry.get(SpeedControlButton.registryName).flipDisabledFlag(shouldBeDisabled);
260
+ RainMan.componentRegistry.getIfExists(SpeedControlButton.registryName)?.flipDisabledFlag(shouldBeDisabled);
261
+ RainMan.componentRegistry
262
+ .getIfExists(SpeedControlButtonMobile.registryName)
263
+ ?.flipDisabledFlag(shouldBeDisabled);
260
264
  }
261
265
  /**
262
266
  * Function for disabling information buttons
@@ -289,14 +293,11 @@ export class AbstractController {
289
293
  */
290
294
  handleDisablingButtons(disabled) {
291
295
  this.disableBetButtons(disabled ?? this.shouldBetButtonsBeDisabled);
292
- if (RainMan.settingsStore.isFreeSpinsPlayEnabled ||
293
- RainMan.globals.isSuperBonusGameEnabled ||
294
- RainMan.settingsStore.isAutoplayEnabled) {
295
- this.disableSpeedButton(false);
296
- }
297
- else {
298
- this.disableSpeedButton(disabled ?? this.shouldButtonsBeDisabled);
299
- }
296
+ const shouldSpeedBeDisabled = !RainMan.settingsStore.isFreeSpinsPlayEnabled &&
297
+ !RainMan.globals.isSuperBonusGameEnabled &&
298
+ !RainMan.settingsStore.isAutoplayEnabled &&
299
+ this.shouldButtonsBeDisabled;
300
+ this.disableSpeedButton(shouldSpeedBeDisabled);
300
301
  }
301
302
  /**
302
303
  * Function for initializing buttons
@@ -339,10 +340,12 @@ export class AbstractController {
339
340
  this.gamePhase === gamePhases.SPINNING ||
340
341
  this.gamePhase === gamePhases.GAME_SUMMARY);
341
342
  }
342
- RainMan.componentRegistry.get(SpeedControlButton.registryName).setOnClick(() => {
343
+ RainMan.componentRegistry.getIfExists(SpeedControlButton.registryName)?.setOnClick(() => {
343
344
  RainMan.settingsStore.closeCurrentlyOpenModal?.();
344
345
  RainMan.componentRegistry.nextSpeedLevel();
345
- this.uiController.messageBox.updateIncentiveText(sample(i18next.t("idleMessages", { returnObjects: true })[RainMan.componentRegistry.getSpeedLevel()]));
346
+ const speedLevel = RainMan.componentRegistry.getSpeedLevel();
347
+ this.uiController.updateShownSpeedLevel(speedLevel);
348
+ this.uiController.messageBox.updateIncentiveText(sample(i18next.t("idleMessages", { returnObjects: true })[speedLevel]));
346
349
  });
347
350
  }
348
351
  /**
@@ -365,6 +368,13 @@ export class AbstractController {
365
368
  }
366
369
  RainMan.settingsStore.handleModalInvocation?.(UI_ITEMS.bet);
367
370
  });
371
+ if (RainMan.componentRegistry.has(SpeedControlButtonMobile.registryName)) {
372
+ RainMan.componentRegistry.get(SpeedControlButtonMobile.registryName).setOnClick(() => {
373
+ RainMan.settingsStore.closeCurrentlyOpenModal?.();
374
+ RainMan.componentRegistry.nextSpeedLevel();
375
+ this.uiController.messageBox.updateIncentiveText(sample(i18next.t("idleMessages", { returnObjects: true })[RainMan.componentRegistry.getSpeedLevel()]));
376
+ });
377
+ }
368
378
  return;
369
379
  }
370
380
  RainMan.componentRegistry.get(BUTTONS.plus).setOnClick(() => {
@@ -760,7 +770,7 @@ export class AbstractController {
760
770
  return;
761
771
  }
762
772
  if (this.gamePhase === gamePhases.HANDLING_ACTIONS) {
763
- this.shouldPlayNextSpin = true;
773
+ this.shouldPlayNextSpin = !RainMan.settingsStore.isFreeSpinsPlayEnabled;
764
774
  }
765
775
  if (this.mainContainer.mysteryWinVisibleFor) {
766
776
  RainMan.componentRegistry.get(COMPONENTS.mysteryWin).stopUpdate();
@@ -907,7 +917,7 @@ export class AbstractController {
907
917
  async useTakeAction() {
908
918
  if (!RainMan.settingsStore.isTakeActionAvailable ||
909
919
  RainMan.settingsStore.roundNumber === 0 ||
910
- this._lastWinAmount === 0) {
920
+ (this._lastWinAmount === 0 && !RainMan.settingsStore.isAfterFreeSpinsSummary)) {
911
921
  logInfo("Take action can't be performed");
912
922
  return;
913
923
  }
@@ -1159,6 +1169,8 @@ export class AbstractController {
1159
1169
  this.uiController.limitWinAmount(spinLogic.winTotalAmount);
1160
1170
  }
1161
1171
  this.composeWinActionsQueue(spinLogic);
1172
+ this.firstAnimationTarget =
1173
+ this.winActionsQueue.find((action) => action.animationTarget)?.animationTarget ?? null;
1162
1174
  if (spinLogic.winTotalAmount === 0 && this.invokeFreeSpinPlateAfterWin) {
1163
1175
  this.uiController.messageBox.hideCurrentWinText();
1164
1176
  }
@@ -1312,6 +1324,8 @@ export class AbstractController {
1312
1324
  * @returns {Promise<void>} - promise that resolves when free spin plate is invoked
1313
1325
  */
1314
1326
  async handleFreeSpinPlateInvocation(numberOfFreeSpins, isAdditionalFreeSpin = false) {
1327
+ RainMan.componentRegistry.getIfExists(SpeedControlButtonMobile.registryName)?.flipDisabledFlag(true);
1328
+ RainMan.componentRegistry.getIfExists(SpeedControlButton.registryName)?.flipDisabledFlag(true);
1315
1329
  await this.buttonsEventManager.disableButtonsForAction(async () => {
1316
1330
  await this.mainContainer.invokeFreeSpinPlate(numberOfFreeSpins ?? RainMan.settingsStore.totalNumberOfFreeSpins, isAdditionalFreeSpin);
1317
1331
  });
@@ -1328,9 +1342,12 @@ export class AbstractController {
1328
1342
  * @returns {Promise<void>} - promise that resolves when free spin summary plate is invoked
1329
1343
  */
1330
1344
  async handleFreeSpinSummaryPlateInvocation() {
1345
+ this.shouldPlayNextSpin = false;
1331
1346
  await this.buttonsEventManager.disableButtonsForAction(async () => {
1332
1347
  this.uiController.messageBox.hideFreeSpinText();
1333
1348
  this.uiController.messageBox.hideCurrentWinText();
1349
+ RainMan.componentRegistry.getIfExists(SpeedControlButtonMobile.registryName)?.flipDisabledFlag(true);
1350
+ RainMan.componentRegistry.getIfExists(SpeedControlButton.registryName)?.flipDisabledFlag(true);
1334
1351
  await this.mainContainer.invokeFreeSpinSummary(this.uiController.totalNumberOfFreeSpins, this.uiController.totalFreeSpinWinAmount);
1335
1352
  this.uiController.totalNumberOfFreeSpins = 0;
1336
1353
  this.uiController.resetTotalFreeSpinWinAmount();
@@ -149,7 +149,6 @@ export declare class UiController {
149
149
  updateShownPossibleWin(): void;
150
150
  /**
151
151
  * Shows game pays in a bottom panel, if exists
152
- * @param amount
153
152
  * @public
154
153
  * @returns {void}
155
154
  */
@@ -1,5 +1,5 @@
1
1
  import { round } from "lodash";
2
- import { COMPONENTS, MessageBox, SpeedControlButton } from "../components";
2
+ import { COMPONENTS, MessageBox, SpeedControlButton, SpeedControlButtonMobile } from "../components";
3
3
  import { RainMan } from "../Rainman";
4
4
  import { ceilToDecimal } from "../utils";
5
5
  /**
@@ -181,8 +181,12 @@ export class UiController {
181
181
  * @returns {void}
182
182
  */
183
183
  updateShownSpeedLevel(currentSpeed) {
184
- RainMan.componentRegistry.get(SpeedControlButton.registryName).adaptToSpeed(currentSpeed);
185
- RainMan.componentRegistry.get(SpeedControlButton.registryName).forceTextureUpdate();
184
+ const speedButton = RainMan.componentRegistry.getIfExists(SpeedControlButton.registryName);
185
+ const mobileSpeedButton = RainMan.componentRegistry.getIfExists(SpeedControlButtonMobile.registryName);
186
+ speedButton?.adaptToSpeed(currentSpeed);
187
+ speedButton?.forceTextureUpdate();
188
+ mobileSpeedButton?.adaptToSpeed(currentSpeed);
189
+ mobileSpeedButton?.forceTextureUpdate();
186
190
  }
187
191
  /**
188
192
  * Function for updating shown balance
@@ -216,7 +220,6 @@ export class UiController {
216
220
  }
217
221
  /**
218
222
  * Shows game pays in a bottom panel, if exists
219
- * @param amount
220
223
  * @public
221
224
  * @returns {void}
222
225
  */
@@ -63,6 +63,7 @@ export declare class SettingsStore {
63
63
  updateVolumeButtonTexture?: () => void;
64
64
  handleModalInvocation?: (layerId: string) => void;
65
65
  closeOtherModals?: (layerId: string) => void;
66
+ closeAllModals?: () => void;
66
67
  closeCurrentlyOpenModal?: () => void;
67
68
  handleInvokeFreeSpinPlate?: (freeSpinAmount: number, isAdditionalFreeSpin: boolean) => Promise<void>;
68
69
  handleBuyFreeSpinPopupActivation?: () => void;
@@ -69,6 +69,7 @@ export class SettingsStore {
69
69
  updateVolumeButtonTexture;
70
70
  handleModalInvocation;
71
71
  closeOtherModals;
72
+ closeAllModals;
72
73
  closeCurrentlyOpenModal;
73
74
  handleInvokeFreeSpinPlate;
74
75
  handleBuyFreeSpinPopupActivation;
@@ -16,16 +16,15 @@ const interruptedAudioResume = () => {
16
16
  window.addEventListener("touchend", resumeAudioOnInterruption, { once: true, passive: true });
17
17
  };
18
18
  /**
19
- * Clears pending foreground recovery when the page goes to the background.
20
- * Pixi handles the actual pause state through its WebAudio auto-pause flow.
19
+ * Pauses audio when the page goes to the background.
21
20
  * @returns {void}
22
21
  */
23
- const prepareAudioForPageHide = () => {
22
+ const pauseAudio = () => {
24
23
  isResumingAudio = false;
25
24
  shouldResumeAudioOnInterruption = false;
26
25
  removeInterruptedAudioResumeListeners();
27
26
  if (RainMan.settingsStore.isSoundEnabled()) {
28
- SoundManager.captureAmbientPlaybackPositionForPageHide();
27
+ SoundManager.stopSoundsForPageHide();
29
28
  }
30
29
  };
31
30
  /**
@@ -72,10 +71,10 @@ function resumeAudioOnInterruption() {
72
71
  */
73
72
  const handleVisibilityChange = () => {
74
73
  if (document.hidden) {
75
- prepareAudioForPageHide();
74
+ pauseAudio();
76
75
  return;
77
76
  }
78
- resumeAudio();
77
+ interruptedAudioResume();
79
78
  };
80
79
  /**
81
80
  * Sets up audio states for tab switches and iOS app interruptions.
@@ -84,7 +83,7 @@ const handleVisibilityChange = () => {
84
83
  export const loadSoundsOnTabChange = () => {
85
84
  sound.disableAutoPause = false;
86
85
  document.addEventListener("visibilitychange", handleVisibilityChange);
87
- window.addEventListener("pagehide", () => prepareAudioForPageHide());
88
- window.addEventListener("pageshow", () => resumeAudio());
89
- window.addEventListener("focus", () => resumeAudio());
86
+ window.addEventListener("pagehide", () => pauseAudio());
87
+ window.addEventListener("pageshow", () => interruptedAudioResume());
88
+ window.addEventListener("focus", () => interruptedAudioResume());
90
89
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixi-rainman-game-engine",
3
- "version": "0.3.43",
3
+ "version": "0.3.44-beta",
4
4
  "description": "This repository contains all of the mechanics that used in rainman games.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",