pixi-rainman-game-engine 0.3.31-b → 0.3.32

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.
@@ -2,87 +2,30 @@ import "react-custom-scroll/dist/customScroll.css";
2
2
  import "./autoplaySettings.css";
3
3
  import i18next from "i18next";
4
4
  import { observer } from "mobx-react-lite";
5
- import React, { useEffect, useState } from "react";
5
+ import React from "react";
6
6
  import { COMPONENTS } from "../../../components";
7
7
  import { UI_ITEMS } from "../../../utils";
8
8
  import { useStores } from "../../hooks";
9
9
  import { CloseModalButton } from "../CloseModalButton";
10
10
  import { RichLimitingSlider } from "../RichLimitingSlider";
11
11
  import { SpeedButtonWithHeader, SwitchWithHeader } from "../SwitchWithHeader";
12
+ const initialAutoSpinCount = 10;
13
+ const availableAutoSpinLimits = [10, 20, 50, 70, 100];
12
14
  export const AutoplaySettings = observer(() => {
13
- const [limitNumbers, setLimitNumbers] = useState([0]);
14
- const initialAutoSpinCount = 50;
15
- const availableAutoSpinLimits = [10, 20, 50, 100, 150, 200, 250, 300, 350, 400, 500, 600, 700, 800, 900, 1000];
16
15
  const { settingStore } = useStores();
17
- const calculateLimitBounds = (indexOfSelectedLimit) => {
18
- let bottomLimitIndex = indexOfSelectedLimit - 2;
19
- let topLimitIndex = indexOfSelectedLimit + 1;
20
- if (bottomLimitIndex < 0) {
21
- bottomLimitIndex = availableAutoSpinLimits.length - -bottomLimitIndex;
22
- }
23
- if (topLimitIndex === availableAutoSpinLimits.length) {
24
- topLimitIndex = 0;
25
- }
26
- return {
27
- bottomLimitIndex,
28
- topLimitIndex
29
- };
30
- };
31
16
  const updateLimitsBounds = (newLimit) => {
32
17
  setAutoSpinsCount(newLimit);
33
- const indexOfSelectedSpinsLimit = availableAutoSpinLimits.findIndex((limitCount) => limitCount === newLimit);
34
- if (indexOfSelectedSpinsLimit === -1) {
35
- }
36
- const { bottomLimitIndex, topLimitIndex } = calculateLimitBounds(indexOfSelectedSpinsLimit);
37
- const limitNumbersIndexes = [];
38
- for (let index = bottomLimitIndex; index !== topLimitIndex + 1; index++) {
39
- if (index === availableAutoSpinLimits.length) {
40
- index = 0;
41
- }
42
- limitNumbersIndexes.push(availableAutoSpinLimits[index]);
43
- if (limitNumbersIndexes.length === 4) {
44
- break;
45
- }
46
- }
47
- setLimitNumbers(limitNumbersIndexes);
48
18
  };
49
- useEffect(() => {
50
- updateLimitsBounds(initialAutoSpinCount);
51
- }, []);
52
- const { singleWinExceeds, balancedIncreased, balancedDecreased, howManyAutoSpinsLeft, infinitySpinsEnabled, setSingleWinExceeds, setBalancedIncreased, setBalancedDecreased, setAutoSpinsCount, flipInfinitySpinsFlag, resetAutoplaySettings } = settingStore;
19
+ const { singleWinExceeds, balancedIncreased, balancedDecreased, howManyAutoSpinsLeft, infinitySpinsEnabled, setSingleWinExceeds, setBalancedIncreased, setBalancedDecreased, setAutoSpinsCount, resetAutoplaySettings } = settingStore;
53
20
  return (<div className="autoplay-settings">
54
21
  <CloseModalButton layerId={UI_ITEMS.autoplay}/>
55
22
  <div className="autoplay-settings__title">
56
23
  <h2>{i18next.t("autoPlay")}</h2>
57
24
  </div>
58
25
  <div className="autoplay-settings__stop-limits">
59
- <button className={`autoplay-settings__limit left ${infinitySpinsEnabled ? "autoplay-settings__limit--infinity" : ""}`} onClick={() => {
60
- let indexOfSelectedSpinsLimit = availableAutoSpinLimits.findIndex((limitCount) => limitCount === howManyAutoSpinsLeft);
61
- indexOfSelectedSpinsLimit -= 1;
62
- if (indexOfSelectedSpinsLimit === -1) {
63
- indexOfSelectedSpinsLimit = availableAutoSpinLimits.length - 1;
64
- }
65
- updateLimitsBounds(availableAutoSpinLimits[indexOfSelectedSpinsLimit]);
66
- }}>
67
- <p>-</p>
68
- </button>
69
- {limitNumbers.map((limiter, index) => (<button key={`${limiter}${index}`} value={limiter} className={`autoplay-settings__limit ${limiter === howManyAutoSpinsLeft ? "autoplay-settings__limit--active" : ""} middle ${infinitySpinsEnabled ? "autoplay-settings__limit--infinity" : ""}`} disabled={settingStore.infinitySpinsEnabled} onClick={(event) => updateLimitsBounds(Number(event.currentTarget.value))}>
26
+ {availableAutoSpinLimits.map((limiter, index) => (<button key={`${limiter}${index}`} value={limiter} className={`autoplay-settings__limit ${limiter === howManyAutoSpinsLeft ? "autoplay-settings__limit--active" : ""} middle ${infinitySpinsEnabled ? "autoplay-settings__limit--infinity" : ""}`} disabled={settingStore.infinitySpinsEnabled} onClick={(event) => updateLimitsBounds(Number(event.currentTarget.value))}>
70
27
  <p>{limiter}</p>
71
28
  </button>))}
72
- <button className={`autoplay-settings__limit right ${infinitySpinsEnabled ? "autoplay-settings__limit--infinity" : ""}`} onClick={() => {
73
- let indexOfSelectedSpinsLimit = availableAutoSpinLimits.findIndex((limitCount) => limitCount === howManyAutoSpinsLeft);
74
- indexOfSelectedSpinsLimit += 1;
75
- if (indexOfSelectedSpinsLimit === availableAutoSpinLimits.length) {
76
- indexOfSelectedSpinsLimit = 0;
77
- }
78
- const newAutoSpinsLimitNumber = availableAutoSpinLimits[indexOfSelectedSpinsLimit];
79
- updateLimitsBounds(newAutoSpinsLimitNumber);
80
- }}>
81
- <p>+</p>
82
- </button>
83
- <button className={`autoplay-settings__limit circle ${infinitySpinsEnabled ? "autoplay-settings__limit--active" : ""}`} onClick={() => flipInfinitySpinsFlag()}>
84
- <p>∞</p>
85
- </button>
86
29
  </div>
87
30
  <div className="separator"/>
88
31
  <div className="autoplay-settings__title">
@@ -64,6 +64,12 @@ export declare class SoundManagerInstance implements SpeedAdapterInterface<Sound
64
64
  * @returns {void}
65
65
  */
66
66
  resumeAll(): void;
67
+ /**
68
+ * Restores currently active ambient loop if browser dropped it while page was inactive.
69
+ * @public
70
+ * @returns {void}
71
+ */
72
+ restoreAmbientPlayback(): void;
67
73
  /**
68
74
  * Function for pausing all sounds in game
69
75
  * If sound is not playing, it will be skipped
@@ -112,5 +118,11 @@ export declare class SoundManagerInstance implements SpeedAdapterInterface<Sound
112
118
  * @returns {void}
113
119
  */
114
120
  resumeOnFocus(): Promise<void>;
121
+ /**
122
+ * Returns information if audio context is ready to play sounds.
123
+ * @public
124
+ * @returns {boolean} True when audio context is in running state.
125
+ */
126
+ isAudioContextRunning(): boolean;
115
127
  }
116
128
  export declare const SoundManager: SoundManagerInstance;
@@ -170,6 +170,28 @@ export class SoundManagerInstance {
170
170
  }
171
171
  });
172
172
  }
173
+ /**
174
+ * Restores currently active ambient loop if browser dropped it while page was inactive.
175
+ * @public
176
+ * @returns {void}
177
+ */
178
+ restoreAmbientPlayback() {
179
+ if (!RainMan.settingsStore.shouldPlayAmbientMusic) {
180
+ return;
181
+ }
182
+ const ambientTrack = RainMan.settingsStore.isFreeSpinsPlayEnabled
183
+ ? SoundTracks.freeSpinsMusic
184
+ : SoundTracks.music;
185
+ const ambientMusic = this.musicCatalogue.get(ambientTrack);
186
+ if (!ambientMusic || ambientMusic.isPlaying) {
187
+ return;
188
+ }
189
+ if (ambientMusic.paused) {
190
+ ambientMusic.resume();
191
+ return;
192
+ }
193
+ this.play(ambientTrack);
194
+ }
173
195
  /**
174
196
  * Function for pausing all sounds in game
175
197
  * If sound is not playing, it will be skipped
@@ -240,13 +262,16 @@ export class SoundManagerInstance {
240
262
  catch (error) {
241
263
  console.error("Could not resume audio");
242
264
  }
243
- const ambientMusic = this.musicCatalogue.get(RainMan.settingsStore.isFreeSpinsPlayEnabled ? "freeSpinsMusic" : "music");
244
- if (ambientMusic !== undefined &&
245
- ambientMusic.isPlaying !== true &&
246
- RainMan.settingsStore.shouldPlayAmbientMusic) {
247
- ambientMusic.resume();
248
- }
249
265
  }
266
+ this.restoreAmbientPlayback();
267
+ }
268
+ /**
269
+ * Returns information if audio context is ready to play sounds.
270
+ * @public
271
+ * @returns {boolean} True when audio context is in running state.
272
+ */
273
+ isAudioContextRunning() {
274
+ return sound.context.audioContext.state === "running";
250
275
  }
251
276
  }
252
277
  export const SoundManager = SoundManagerInstance.getInstance();
@@ -58,6 +58,7 @@ export declare abstract class AbstractController<T> {
58
58
  protected buttonHeldSince: number;
59
59
  shouldStopMobileSpin: boolean;
60
60
  disableMessageBoxChange: boolean;
61
+ shouldClearQuickReelsStopAfterSpin: boolean;
61
62
  protected resolveBigWin?: () => void;
62
63
  protected resolveMysteryWin?: () => void;
63
64
  protected resolveBonusWin?: () => void;
@@ -384,6 +385,7 @@ export declare abstract class AbstractController<T> {
384
385
  * @returns {Promise<void>}
385
386
  */
386
387
  private spin;
388
+ private cleanupDeferredQuickStop;
387
389
  /**
388
390
  * Function for invoking free spin plate, invoked in mainContainer
389
391
  * ```ts
@@ -58,6 +58,7 @@ export class AbstractController {
58
58
  buttonHeldSince = 0;
59
59
  shouldStopMobileSpin = false;
60
60
  disableMessageBoxChange = false;
61
+ shouldClearQuickReelsStopAfterSpin = false;
61
62
  resolveBigWin;
62
63
  resolveMysteryWin;
63
64
  resolveBonusWin;
@@ -191,6 +192,10 @@ export class AbstractController {
191
192
  keyboardEvent.preventDefault();
192
193
  this.buttonHeldSince = 0;
193
194
  this.columnsContainer.clearQuickReelsStop();
195
+ if (this.gamePhase === gamePhases.HANDLING_ACTIONS ||
196
+ this.gamePhase === gamePhases.STOPPING_HANDLING_ACTIONS) {
197
+ this.shouldPlayNextSpin = false;
198
+ }
194
199
  }
195
200
  });
196
201
  window.addEventListener("blur", () => {
@@ -430,25 +435,26 @@ export class AbstractController {
430
435
  initSpinButton() {
431
436
  const refreshButton = RainMan.componentRegistry.get(RefreshButton.registryName);
432
437
  this.clearMobileSpinTimeout = () => {
433
- this.buttonHeldSince = 0;
434
438
  const isSpinning = this.gamePhase === gamePhases.SPINNING ||
435
439
  this.gamePhase === gamePhases.CONFIGURING_SPIN ||
436
440
  this.gamePhase === gamePhases.DATA_FETCH;
437
- if (!isSpinning) {
438
- this.columnsContainer.clearQuickReelsStop();
439
- }
441
+ this.buttonHeldSince = 0;
440
442
  if (this.mobileSpinTimeout) {
441
443
  clearInterval(this.mobileSpinTimeout);
442
- this.quickStopController.setQuickStopCounterLock(false);
443
444
  this.mobileSpinTimeout = null;
444
- if (!isSpinning) {
445
- this.columnsContainer.clearQuickReelsStop();
446
- }
447
445
  }
448
446
  if (this.mobileSpinDelay) {
449
447
  clearTimeout(this.mobileSpinDelay);
450
448
  this.mobileSpinDelay = null;
451
449
  }
450
+ if (isSpinning) {
451
+ this.shouldClearQuickReelsStopAfterSpin = true;
452
+ }
453
+ else {
454
+ this.columnsContainer.clearQuickReelsStop();
455
+ this.quickStopController.setQuickStopCounterLock(false);
456
+ this.shouldClearQuickReelsStopAfterSpin = false;
457
+ }
452
458
  };
453
459
  refreshButton.setOnClick(() => {
454
460
  this.handleSpinLogic();
@@ -726,10 +732,10 @@ export class AbstractController {
726
732
  this.handleDisablingButtons(true);
727
733
  RainMan.globals.shouldShowModals = false;
728
734
  RainMan.settingsStore.closeCurrentlyOpenModal?.();
729
- const realTime = Date.now() - this.buttonHeldSince;
730
- if (realTime <= 500) {
731
- this.columnsContainer.clearQuickReelsStop();
732
- }
735
+ // const realTime = Date.now() - this.buttonHeldSince;
736
+ // if (realTime <= 500) {
737
+ // this.columnsContainer.clearQuickReelsStop();
738
+ // }
733
739
  if (this.mainContainer.freeSpinPlate) {
734
740
  if (this.mainContainer.freeSpinPlate.shownAt === null ||
735
741
  performance.now() - this.mainContainer.freeSpinPlate.shownAt <
@@ -810,6 +816,7 @@ export class AbstractController {
810
816
  }
811
817
  this.prepareBoughtFreeSpins();
812
818
  if (RainMan.settingsStore.isAutoplayEnabled && !this.spinning) {
819
+ this.cleanupDeferredQuickStop();
813
820
  this.changeGamePhase(gamePhases.IDLE);
814
821
  this.spin();
815
822
  return;
@@ -819,6 +826,7 @@ export class AbstractController {
819
826
  this.changeGamePhase(gamePhases.IDLE);
820
827
  }
821
828
  if (this.gamePhase === gamePhases.IDLE || this.gamePhase === gamePhases.HANDLING_ACTIONS) {
829
+ this.cleanupDeferredQuickStop();
822
830
  this.spin();
823
831
  }
824
832
  this.spinThrottlerTimeout = setTimeout(() => (this.spinThrottlerTimeout = null), 100);
@@ -1252,6 +1260,14 @@ export class AbstractController {
1252
1260
  }, 0);
1253
1261
  }
1254
1262
  }
1263
+ cleanupDeferredQuickStop() {
1264
+ if (!this.shouldClearQuickReelsStopAfterSpin) {
1265
+ return;
1266
+ }
1267
+ this.columnsContainer.clearQuickReelsStop();
1268
+ this.quickStopController.setQuickStopCounterLock(false);
1269
+ this.shouldClearQuickReelsStopAfterSpin = false;
1270
+ }
1255
1271
  /**
1256
1272
  * Function for invoking free spin plate, invoked in mainContainer
1257
1273
  * ```ts
@@ -327,12 +327,6 @@ export declare class SettingsStore {
327
327
  * @returns {void}
328
328
  */
329
329
  setAutoSpinsCount(count: number): void;
330
- /**
331
- * Flip the infinity spins flag
332
- * @public
333
- * @returns {void}
334
- */
335
- flipInfinitySpinsFlag(): void;
336
330
  /**
337
331
  * Setter for the autoplay enabled flag
338
332
  * @public
@@ -72,7 +72,6 @@ export class SettingsStore {
72
72
  enableButtons;
73
73
  useTakeAction;
74
74
  initialAutoplaySettingsState = {
75
- infinitySpinsEnabled: false,
76
75
  onAnyWin: false,
77
76
  howManyAutoSpinsLeft: INITIAL_AUTO_SPIN_COUNT,
78
77
  singleWinExceeds: 0,
@@ -108,7 +107,6 @@ export class SettingsStore {
108
107
  resetDataForSymbolPaytablePopup: action.bound,
109
108
  decreaseAutoSpinsCount: action.bound,
110
109
  setAutoSpinsCount: action.bound,
111
- flipInfinitySpinsFlag: action.bound,
112
110
  resetAutoplaySettings: action.bound,
113
111
  setAutoPlayEnabledFlag: action.bound,
114
112
  setNumberOfFreeSpin: action.bound,
@@ -541,7 +539,7 @@ export class SettingsStore {
541
539
  * @returns {void}
542
540
  */
543
541
  decreaseAutoSpinsCount() {
544
- if (this.infinitySpinsEnabled || this.isFreeSpinsPlayEnabled) {
542
+ if (this.isFreeSpinsPlayEnabled) {
545
543
  return;
546
544
  }
547
545
  this.howManyAutoSpinsLeft -= 1;
@@ -553,20 +551,8 @@ export class SettingsStore {
553
551
  * @returns {void}
554
552
  */
555
553
  setAutoSpinsCount(count) {
556
- if (this.infinitySpinsEnabled) {
557
- this.setAutoSpinsCount(-1);
558
- this.flipInfinitySpinsFlag();
559
- }
560
554
  this.howManyAutoSpinsLeft = count;
561
555
  }
562
- /**
563
- * Flip the infinity spins flag
564
- * @public
565
- * @returns {void}
566
- */
567
- flipInfinitySpinsFlag() {
568
- this.infinitySpinsEnabled = !this.infinitySpinsEnabled;
569
- }
570
556
  /**
571
557
  * Setter for the autoplay enabled flag
572
558
  * @public
@@ -702,7 +688,6 @@ export class SettingsStore {
702
688
  * @returns {void}
703
689
  */
704
690
  resetAutoplaySettings() {
705
- this.infinitySpinsEnabled = this.initialAutoplaySettingsState.infinitySpinsEnabled;
706
691
  this.onAnyWin = this.initialAutoplaySettingsState.onAnyWin;
707
692
  this.howManyAutoSpinsLeft = this.initialAutoplaySettingsState.howManyAutoSpinsLeft;
708
693
  this.singleWinExceeds = this.initialAutoplaySettingsState.singleWinExceeds;
@@ -1,6 +1,5 @@
1
1
  /**
2
- * This function is required for setting up the sound while changing the tab
3
- * This is using the visibilitychange event
2
+ * Sets up audio states for tab switches and iOS app interruptions.
4
3
  * @returns {void}
5
4
  */
6
5
  export declare const loadSoundsOnTabChange: () => void;
@@ -1,26 +1,76 @@
1
1
  import { SoundManager } from "../../application";
2
2
  import { RainMan } from "../../Rainman";
3
+ let shouldResumeAudioOnInterruption = false;
4
+ const removeInterruptedAudioResumeListeners = () => {
5
+ window.removeEventListener("pointerdown", resumeAudioOnInterruption);
6
+ window.removeEventListener("touchend", resumeAudioOnInterruption);
7
+ };
8
+ const interruptedAudioResume = () => {
9
+ if (shouldResumeAudioOnInterruption) {
10
+ return;
11
+ }
12
+ shouldResumeAudioOnInterruption = true;
13
+ window.addEventListener("pointerdown", resumeAudioOnInterruption, { once: true });
14
+ window.addEventListener("touchend", resumeAudioOnInterruption, { once: true, passive: true });
15
+ };
3
16
  /**
4
- * This function using the visibilitychange event to resume sounds
5
- * If any time the sounds was enable than this will be resumed
6
- * If the sounds was disabled than this will be not triggered
17
+ * Pauses audio when the page goes to the background.
7
18
  * @returns {void}
8
19
  */
9
- const resumeAudioContext = () => {
10
- if (RainMan.settingsStore.isSoundEnabled() && document.hidden) {
11
- SoundManager.stopAll();
20
+ const pauseAudio = () => {
21
+ shouldResumeAudioOnInterruption = false;
22
+ removeInterruptedAudioResumeListeners();
23
+ if (RainMan.settingsStore.isSoundEnabled()) {
24
+ SoundManager.pauseAll();
25
+ }
26
+ };
27
+ /**
28
+ * Restores audio when the page becomes active again.
29
+ * @returns {Promise<void>}
30
+ */
31
+ const resumeAudio = async () => {
32
+ if (document.hidden || !RainMan.settingsStore.isSoundEnabled()) {
12
33
  return;
13
34
  }
14
- if (RainMan.settingsStore.isSoundEnabled() && !document.hidden) {
35
+ if (RainMan.settingsStore.isSoundEnabled()) {
36
+ await SoundManager.resumeOnFocus();
15
37
  SoundManager.resumeAll();
38
+ }
39
+ if (SoundManager.isAudioContextRunning()) {
40
+ shouldResumeAudioOnInterruption = false;
41
+ removeInterruptedAudioResumeListeners();
42
+ return;
43
+ }
44
+ interruptedAudioResume();
45
+ };
46
+ /**
47
+ * Restore audio on the next user action if browser blocked it earlier.
48
+ * @returns {void}
49
+ */
50
+ function resumeAudioOnInterruption() {
51
+ shouldResumeAudioOnInterruption = false;
52
+ removeInterruptedAudioResumeListeners();
53
+ resumeAudio();
54
+ }
55
+ /**
56
+ * Check if visibility is changed to pause or restore audio.
57
+ * @returns {void}
58
+ */
59
+ const handleVisibilityChange = () => {
60
+ if (document.hidden) {
61
+ pauseAudio();
16
62
  return;
17
63
  }
64
+ resumeAudio();
18
65
  };
19
66
  /**
20
- * This function is required for setting up the sound while changing the tab
21
- * This is using the visibilitychange event
67
+ * Sets up audio states for tab switches and iOS app interruptions.
22
68
  * @returns {void}
23
69
  */
24
70
  export const loadSoundsOnTabChange = () => {
25
- document.addEventListener("visibilitychange", resumeAudioContext);
71
+ document.addEventListener("visibilitychange", handleVisibilityChange);
72
+ window.addEventListener("pagehide", pauseAudio);
73
+ window.addEventListener("blur", pauseAudio);
74
+ window.addEventListener("pageshow", () => resumeAudio());
75
+ window.addEventListener("focus", () => resumeAudio());
26
76
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixi-rainman-game-engine",
3
- "version": "0.3.31b",
3
+ "version": "0.3.32",
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",