pixi-rainman-game-engine 0.3.40 → 0.3.42-beta.1

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.
@@ -7,6 +7,7 @@ import { Color } from "pixi.js";
7
7
  export const defaultAppConfig = {
8
8
  gameWidth: 1920,
9
9
  gameHeight: 1080,
10
+ enableInfinityAutoplay: false,
10
11
  reinitMode: false,
11
12
  reinitSymbolIndexFix: 0,
12
13
  idleTimeout: 10000,
@@ -13,6 +13,7 @@ export type RequiredAppConfig = {
13
13
  export type OptionalAppConfig = {
14
14
  gameWidth: number;
15
15
  gameHeight: number;
16
+ enableInfinityAutoplay: boolean;
16
17
  reinitMode: boolean;
17
18
  reinitSymbolIndexFix: number;
18
19
  idleTimeout: number;
@@ -4,6 +4,7 @@ import i18next from "i18next";
4
4
  import { observer } from "mobx-react-lite";
5
5
  import React from "react";
6
6
  import { COMPONENTS } from "../../../components";
7
+ import { RainMan } from "../../../Rainman";
7
8
  import { UI_ITEMS } from "../../../utils";
8
9
  import { useStores } from "../../hooks";
9
10
  import { CloseModalButton } from "../CloseModalButton";
@@ -11,11 +12,18 @@ import { RichLimitingSlider } from "../RichLimitingSlider";
11
12
  import { SpeedButtonWithHeader, SwitchWithHeader } from "../SwitchWithHeader";
12
13
  const initialAutoSpinCount = 10;
13
14
  const availableAutoSpinLimits = [10, 20, 50, 70, 100];
15
+ const infiniteAutoSpinCount = 1000000;
14
16
  export const AutoplaySettings = observer(() => {
15
17
  const { settingStore } = useStores();
18
+ const infinityAutoplayEnabled = RainMan.config.enableInfinityAutoplay;
16
19
  const updateLimitsBounds = (newLimit) => {
20
+ settingStore.setInfinitySpinsEnabled(false);
17
21
  setAutoSpinsCount(newLimit);
18
22
  };
23
+ const enableInfinityAutoplay = () => {
24
+ settingStore.setInfinitySpinsEnabled(true);
25
+ setAutoSpinsCount(infiniteAutoSpinCount);
26
+ };
19
27
  const { singleWinExceeds, balancedIncreased, balancedDecreased, howManyAutoSpinsLeft, infinitySpinsEnabled, setSingleWinExceeds, setBalancedIncreased, setBalancedDecreased, setAutoSpinsCount, resetAutoplaySettings } = settingStore;
20
28
  return (<div className="autoplay-settings">
21
29
  <CloseModalButton layerId={UI_ITEMS.autoplay}/>
@@ -23,9 +31,14 @@ export const AutoplaySettings = observer(() => {
23
31
  <h2>{i18next.t("autoPlay")}</h2>
24
32
  </div>
25
33
  <div className="autoplay-settings__stop-limits">
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))}>
34
+ {availableAutoSpinLimits.map((limiter, index) => (<button key={`${limiter}${index}`} value={limiter} className={`autoplay-settings__limit ${!infinitySpinsEnabled && limiter === howManyAutoSpinsLeft
35
+ ? "autoplay-settings__limit--active"
36
+ : ""} middle`} onClick={(event) => updateLimitsBounds(Number(event.currentTarget.value))}>
27
37
  <p>{limiter}</p>
28
38
  </button>))}
39
+ {infinityAutoplayEnabled && (<button value="infinity" className={`autoplay-settings__limit ${infinitySpinsEnabled ? "autoplay-settings__limit--active" : ""} right`} onClick={enableInfinityAutoplay}>
40
+ <p>∞</p>
41
+ </button>)}
29
42
  </div>
30
43
  <div className="separator"/>
31
44
  <div className="autoplay-settings__title">
@@ -5,13 +5,13 @@ export const SpeedButtonSwitch = observer(() => {
5
5
  const speedLevelMapper = {
6
6
  slow: "turtle",
7
7
  normal: "rabbit",
8
- fast: "cheetah",
8
+ fast: "cheetah"
9
9
  };
10
10
  const { settingStore } = useStores();
11
11
  const { currentSpeed } = settingStore;
12
12
  return (<label className="switch turtle-switch speed-level-id">
13
13
  <span className="turtle-switch-round-box"/>
14
- <img className="turtle-switch__icon" src={`assets/settings/speed-${speedLevelMapper[currentSpeed]}.png`}/>
14
+ <img className="turtle-switch__icon" src={`assets/settings/speed-${speedLevelMapper[currentSpeed]}.webp`}/>
15
15
  <div className="dot-container">
16
16
  {Array.from({ length: 3 }).map((_, i) => (<div key={i} className={`white-dot ${i < settingStore.indexOfSpeedLevel ? "white-dot--active" : ""}`}/>))}
17
17
  </div>
@@ -36,7 +36,7 @@
36
36
  bottom: 5px;
37
37
  width: 30px;
38
38
  height: 30px;
39
- background-image: url("/assets/settings/sound-icon.png");
39
+ background-image: url("/assets/settings/sound-icon.webp");
40
40
  background-position: center;
41
41
  background-size: contain;
42
42
  background-repeat: no-repeat;
@@ -16,7 +16,11 @@ 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
+ private loadedSoundAssets;
21
+ private audioContextRecoveryPromise;
19
22
  constructor();
23
+ private loadSound;
20
24
  /**
21
25
  * Function for loading sounds into pixi
22
26
  * @public
@@ -37,7 +41,7 @@ export declare class SoundManagerInstance implements SpeedAdapterInterface<Sound
37
41
  * @returns {void}
38
42
  */
39
43
  stopPlayingFxSounds(): void;
40
- stopSoundsForPageHide(): void;
44
+ pauseSoundsForPageHide(): void;
41
45
  /**
42
46
  * Function for resuming and pausing sound in game
43
47
  * @public
@@ -61,7 +65,19 @@ export declare class SoundManagerInstance implements SpeedAdapterInterface<Sound
61
65
  getVolumeLevel(soundName: SoundTrack): number;
62
66
  private isAmbientTrack;
63
67
  private getOppositeAmbientTrack;
68
+ private getCurrentAmbientTrack;
64
69
  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
+ private isIOSWebAudioRuntime;
79
+ private recreatePixiSoundContext;
80
+ private recreatePixiSoundContextOnce;
65
81
  stopAmbientMusic(): void;
66
82
  /**
67
83
  * Function for resuming all sounds in game
@@ -76,6 +92,19 @@ export declare class SoundManagerInstance implements SpeedAdapterInterface<Sound
76
92
  * @returns {void}
77
93
  */
78
94
  restoreAmbientPlayback(): void;
95
+ /**
96
+ * Recreates the active ambient WebAudio source after iOS app interruptions.
97
+ * Existing Pixi instances can report isPlaying=true even when the native audio pipeline is silent.
98
+ * @private
99
+ * @returns {void}
100
+ */
101
+ private restartAmbientPlaybackAfterInterruption;
102
+ /**
103
+ * Restores ambient playback after page visibility change or iOS app interruption.
104
+ * @public
105
+ * @returns {boolean} True when audio context is ready after recovery.
106
+ */
107
+ restorePlaybackAfterPageHide(): Promise<boolean>;
79
108
  /**
80
109
  * Function for pausing all sounds in game
81
110
  * If sound is not playing, it will be skipped
@@ -121,13 +150,13 @@ export declare class SoundManagerInstance implements SpeedAdapterInterface<Sound
121
150
  /**
122
151
  * Function that resumes sounds and music when game is back in focus.
123
152
  * @public
124
- * @returns {void}
153
+ * @returns {Promise<boolean>} True when audio context is running.
125
154
  */
126
155
  resumeAudioContext(): Promise<boolean>;
127
156
  /**
128
157
  * Function that resumes sounds and music when game is back in focus.
129
158
  * @public
130
- * @returns {void}
159
+ * @returns {Promise<boolean>} True when audio context and ambient recovery are ready.
131
160
  */
132
161
  resumeOnFocus(): Promise<boolean>;
133
162
  /**
@@ -1,7 +1,12 @@
1
1
  import { Sound, sound } from "@pixi/sound";
2
2
  import { RainMan } from "../../Rainman";
3
3
  import { SoundTracks } from "./types";
4
- const soundsToLoop = [SoundTracks.mysteryFxLoop, SoundTracks.music, SoundTracks.freeSpinsMusic];
4
+ const soundsToLoop = [
5
+ SoundTracks.mysteryFxLoop,
6
+ SoundTracks.music,
7
+ SoundTracks.freeSpinsMusic,
8
+ SoundTracks.cardDrawing
9
+ ];
5
10
  export const pixiSoundContext = sound.context;
6
11
  /**
7
12
  * Class for managing sound in game, uses the pixi sound library
@@ -26,9 +31,25 @@ export class SoundManagerInstance {
26
31
  };
27
32
  musicCatalogue = new Map();
28
33
  currentAmbientTrack = null;
34
+ currentAmbientOffset = 0;
35
+ loadedSoundAssets = {};
36
+ audioContextRecoveryPromise = null;
29
37
  constructor() {
30
38
  this.overrides = this.speedLevelMapper.slow;
31
39
  }
40
+ loadSound(soundName, url) {
41
+ return new Promise((resolve) => {
42
+ const sound = Sound.from({
43
+ url,
44
+ autoPlay: false,
45
+ preload: true,
46
+ volume: this.getVolumeLevel(soundName),
47
+ loaded: () => resolve()
48
+ });
49
+ sound.name = soundName;
50
+ this.musicCatalogue.set(soundName, sound);
51
+ });
52
+ }
32
53
  /**
33
54
  * Function for loading sounds into pixi
34
55
  * @public
@@ -36,22 +57,13 @@ export class SoundManagerInstance {
36
57
  * @returns {Promise<void>} - promise that resolves when all sounds are loaded
37
58
  */
38
59
  async loadSounds(assets) {
60
+ this.loadedSoundAssets = { ...assets };
39
61
  const loadingSoundsPromises = [];
40
62
  for (const [soundName, url] of Object.entries(assets)) {
41
63
  if (!url) {
42
64
  continue;
43
65
  }
44
- loadingSoundsPromises.push(new Promise((resolve) => {
45
- const sound = Sound.from({
46
- url,
47
- autoPlay: false,
48
- preload: true,
49
- volume: this.getVolumeLevel(soundName),
50
- loaded: () => resolve()
51
- });
52
- sound.name = soundName;
53
- this.musicCatalogue.set(soundName, sound);
54
- }));
66
+ loadingSoundsPromises.push(this.loadSound(soundName, url));
55
67
  }
56
68
  await Promise.allSettled(loadingSoundsPromises);
57
69
  }
@@ -89,19 +101,16 @@ export class SoundManagerInstance {
89
101
  sound.stop();
90
102
  }
91
103
  }
92
- stopSoundsForPageHide() {
93
- const music = this.getSound(SoundTracks.music);
94
- const freeSpinsMusic = this.getSound(SoundTracks.freeSpinsMusic);
95
- const isMusicPlaying = Boolean(music?.isPlaying && !music.paused);
96
- const isFreeSpinsMusicPlaying = Boolean(freeSpinsMusic?.isPlaying && !freeSpinsMusic.paused);
97
- if (isFreeSpinsMusicPlaying) {
98
- this.currentAmbientTrack = SoundTracks.freeSpinsMusic;
99
- }
100
- else if (isMusicPlaying) {
101
- this.currentAmbientTrack = SoundTracks.music;
102
- }
103
- this.stopAmbientMusic();
104
- this.stopPlayingFxSounds();
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();
105
114
  }
106
115
  /**
107
116
  * Function for resuming and pausing sound in game
@@ -145,11 +154,15 @@ export class SoundManagerInstance {
145
154
  return;
146
155
  }
147
156
  }
148
- sound.play({
157
+ const instance = sound.play({
149
158
  singleInstance: true,
150
159
  loop: soundsToLoop.includes(soundName),
151
- volume: this.getVolumeLevel(soundName)
160
+ volume: this.getVolumeLevel(soundName),
161
+ speed: soundName === "cardDrawing" ? 0.8 : 1
152
162
  });
163
+ if (this.isAmbientTrack(soundName) && !(instance instanceof Promise)) {
164
+ this.trackAmbientPlaybackProgress(soundName, sound, instance);
165
+ }
153
166
  }
154
167
  /**
155
168
  * Returns the volume level for a given sound track.
@@ -172,10 +185,86 @@ export class SoundManagerInstance {
172
185
  getOppositeAmbientTrack(soundName) {
173
186
  return soundName === SoundTracks.freeSpinsMusic ? SoundTracks.music : SoundTracks.freeSpinsMusic;
174
187
  }
188
+ getCurrentAmbientTrack() {
189
+ return (this.currentAmbientTrack ??
190
+ (RainMan.settingsStore.isFreeSpinsStarted ? SoundTracks.freeSpinsMusic : SoundTracks.music));
191
+ }
175
192
  getSound(soundName) {
176
193
  const overrideSoundName = this.overrides[soundName];
177
194
  return this.musicCatalogue.get(overrideSoundName ?? soundName);
178
195
  }
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
+ isIOSWebAudioRuntime() {
232
+ const userAgent = navigator.userAgent;
233
+ const platform = navigator.platform;
234
+ return /iPad|iPhone|iPod/.test(userAgent) || (platform === "MacIntel" && navigator.maxTouchPoints > 1);
235
+ }
236
+ async recreatePixiSoundContext() {
237
+ if (this.audioContextRecoveryPromise) {
238
+ return this.audioContextRecoveryPromise;
239
+ }
240
+ this.audioContextRecoveryPromise = this.recreatePixiSoundContextOnce().finally(() => {
241
+ this.audioContextRecoveryPromise = null;
242
+ });
243
+ return this.audioContextRecoveryPromise;
244
+ }
245
+ async recreatePixiSoundContextOnce() {
246
+ if (!Object.keys(this.loadedSoundAssets).length) {
247
+ return;
248
+ }
249
+ this.musicCatalogue.forEach((sound) => sound.destroy());
250
+ this.musicCatalogue.clear();
251
+ try {
252
+ sound.context.destroy();
253
+ }
254
+ catch (error) {
255
+ console.error("Could not destroy Pixi sound context", error);
256
+ }
257
+ sound.init();
258
+ sound.disableAutoPause = false;
259
+ const loadingSoundsPromises = [];
260
+ for (const [soundName, url] of Object.entries(this.loadedSoundAssets)) {
261
+ if (!url) {
262
+ continue;
263
+ }
264
+ loadingSoundsPromises.push(this.loadSound(soundName, url));
265
+ }
266
+ await Promise.allSettled(loadingSoundsPromises);
267
+ }
179
268
  stopAmbientMusic() {
180
269
  this.stop(SoundTracks.music);
181
270
  this.stop(SoundTracks.freeSpinsMusic);
@@ -190,23 +279,8 @@ export class SoundManagerInstance {
190
279
  if (!RainMan.settingsStore.isSoundStarted) {
191
280
  return;
192
281
  }
193
- this.musicCatalogue.forEach((sound, soundName) => {
194
- if (sound.paused && sound.isPlaying && sound) {
195
- sound.resume();
196
- }
197
- if (RainMan.settingsStore.shouldPlayAmbientMusic &&
198
- !RainMan.settingsStore.isFreeSpinsPlayEnabled &&
199
- soundName === SoundTracks.music &&
200
- sound.paused) {
201
- sound.resume();
202
- }
203
- if (RainMan.settingsStore.shouldPlayAmbientMusic &&
204
- RainMan.settingsStore.isFreeSpinsPlayEnabled &&
205
- soundName === SoundTracks.freeSpinsMusic &&
206
- sound.paused) {
207
- sound.resume();
208
- }
209
- });
282
+ sound.resumeAll();
283
+ this.restoreAmbientPlayback();
210
284
  }
211
285
  /**
212
286
  * Restores currently active ambient loop if browser dropped it while page was inactive.
@@ -221,10 +295,62 @@ export class SoundManagerInstance {
221
295
  this.stopAmbientMusic();
222
296
  return;
223
297
  }
224
- const ambientTrack = this.currentAmbientTrack ??
225
- (RainMan.settingsStore.isFreeSpinsStarted ? SoundTracks.freeSpinsMusic : SoundTracks.music);
298
+ const ambientTrack = this.getCurrentAmbientTrack();
226
299
  this.play(ambientTrack);
227
300
  }
301
+ /**
302
+ * Recreates the active ambient WebAudio source after iOS app interruptions.
303
+ * Existing Pixi instances can report isPlaying=true even when the native audio pipeline is silent.
304
+ * @private
305
+ * @returns {void}
306
+ */
307
+ restartAmbientPlaybackAfterInterruption() {
308
+ if (!RainMan.settingsStore.isSoundStarted) {
309
+ return;
310
+ }
311
+ if (!RainMan.settingsStore.shouldPlayAmbientMusic) {
312
+ this.stopAmbientMusic();
313
+ return;
314
+ }
315
+ const ambientTrack = this.getCurrentAmbientTrack();
316
+ const ambientSound = this.getSound(ambientTrack);
317
+ if (!ambientSound) {
318
+ return;
319
+ }
320
+ this.stop(this.getOppositeAmbientTrack(ambientTrack));
321
+ ambientSound.stop();
322
+ this.currentAmbientTrack = ambientTrack;
323
+ const instance = ambientSound.play({
324
+ singleInstance: true,
325
+ loop: soundsToLoop.includes(ambientTrack),
326
+ start: this.getAmbientPlaybackOffset(ambientSound),
327
+ volume: this.getVolumeLevel(ambientTrack),
328
+ speed: 1
329
+ });
330
+ if (!(instance instanceof Promise)) {
331
+ this.trackAmbientPlaybackProgress(ambientTrack, ambientSound, instance);
332
+ }
333
+ }
334
+ /**
335
+ * Restores ambient playback after page visibility change or iOS app interruption.
336
+ * @public
337
+ * @returns {boolean} True when audio context is ready after recovery.
338
+ */
339
+ async restorePlaybackAfterPageHide() {
340
+ if (!RainMan.settingsStore.isSoundStarted) {
341
+ return false;
342
+ }
343
+ sound.resumeAll();
344
+ if (this.isIOSWebAudioRuntime()) {
345
+ await this.recreatePixiSoundContext();
346
+ }
347
+ const isAudioContextRunning = await this.resumeAudioContext();
348
+ if (!isAudioContextRunning) {
349
+ return false;
350
+ }
351
+ this.restartAmbientPlaybackAfterInterruption();
352
+ return true;
353
+ }
228
354
  /**
229
355
  * Function for pausing all sounds in game
230
356
  * If sound is not playing, it will be skipped
@@ -232,7 +358,7 @@ export class SoundManagerInstance {
232
358
  * @returns {void}
233
359
  */
234
360
  pauseAll() {
235
- this.musicCatalogue.forEach((sound) => sound.pause());
361
+ sound.pauseAll();
236
362
  }
237
363
  /**
238
364
  * Function for stopping all sounds in game
@@ -282,7 +408,7 @@ export class SoundManagerInstance {
282
408
  /**
283
409
  * Function that resumes sounds and music when game is back in focus.
284
410
  * @public
285
- * @returns {void}
411
+ * @returns {Promise<boolean>} True when audio context is running.
286
412
  */
287
413
  async resumeAudioContext() {
288
414
  const pixiSoundContext = sound.context;
@@ -301,7 +427,7 @@ export class SoundManagerInstance {
301
427
  /**
302
428
  * Function that resumes sounds and music when game is back in focus.
303
429
  * @public
304
- * @returns {void}
430
+ * @returns {Promise<boolean>} True when audio context and ambient recovery are ready.
305
431
  */
306
432
  async resumeOnFocus() {
307
433
  if (!RainMan.settingsStore.isSoundStarted) {
@@ -311,8 +437,7 @@ export class SoundManagerInstance {
311
437
  if (!isAudioContextRunning) {
312
438
  return false;
313
439
  }
314
- this.restoreAmbientPlayback();
315
- return true;
440
+ return this.restorePlaybackAfterPageHide();
316
441
  }
317
442
  /**
318
443
  * Returns information if audio context is ready to play sounds.
@@ -64,6 +64,10 @@ export interface SoundTracks {
64
64
  rouletteStop: "rouletteStop";
65
65
  mysterySymbol: "mysterySymbol";
66
66
  jackpotWin: "jackpotWin";
67
+ cardWin: "cardWin";
68
+ cardLose: "cardLose";
69
+ cardDrawing: "cardDrawing";
70
+ cardCollect: "cardCollect";
67
71
  }
68
72
  export type SoundTrack = SoundTracks[keyof SoundTracks];
69
73
  /**
@@ -61,5 +61,9 @@ export const SoundTracks = {
61
61
  rouletteStart: "rouletteStart",
62
62
  rouletteLoop: "rouletteLoop",
63
63
  rouletteStop: "rouletteStop",
64
- jackpotWin: "jackpotWin"
64
+ jackpotWin: "jackpotWin",
65
+ cardWin: "cardWin",
66
+ cardLose: "cardLose",
67
+ cardDrawing: "cardDrawing",
68
+ cardCollect: "cardCollect"
65
69
  };
@@ -563,6 +563,10 @@ export class AbstractMainContainer extends Container {
563
563
  this.messageBox.visible = true;
564
564
  RainMan.settingsStore.isGambleGameActive = false;
565
565
  this._controller.updateGambleValues(RainMan.settingsStore.gambleGameWin);
566
+ if (RainMan.settingsStore.gambleGameWin > 0) {
567
+ RainMan.settingsStore.useTakeAction?.();
568
+ }
569
+ RainMan.settingsStore.disableSpecialButtons();
566
570
  this.gambleGame = null;
567
571
  }
568
572
  this.hideOverlay();
@@ -1,6 +1,7 @@
1
1
  import i18next from "i18next";
2
2
  import { Container, Sprite, Text, Texture } from "pixi.js";
3
3
  import { Spine } from "pixi-spine";
4
+ import { SoundManager } from "../../application";
4
5
  import { COLOR_BUTTONS_CENTER_OFFSET, COLOR_BUTTONS_SCALE_X, gamblesTextStyle } from "../../constants";
5
6
  import { RainMan } from "../../Rainman";
6
7
  import { getDeviceOrientation, getSpineData, globalMinRatio } from "../../utils";
@@ -60,6 +61,7 @@ export class GambleGame extends Container {
60
61
  this.addChildrenOnce();
61
62
  this.bindButtonsOnce();
62
63
  this.resize();
64
+ SoundManager.play("cardDrawing");
63
65
  }
64
66
  configureButtons() {
65
67
  this.collectButton.eventMode = "static";
@@ -86,8 +88,11 @@ export class GambleGame extends Container {
86
88
  this.redButton.setOnClick(() => this.handleGamblePlay("red"));
87
89
  this.blackButton.setOnClick(() => this.handleGamblePlay("black"));
88
90
  this.collectButton.setOnClick(() => {
91
+ SoundManager.stop("cardDrawing");
92
+ SoundManager.play("music");
89
93
  RainMan.settingsStore.gambleGameWin = this.lastWinAmount;
90
94
  RainMan.componentRegistry.get(BUTTONS.gamble).flipDisabledFlag(true);
95
+ SoundManager.play("cardCollect");
91
96
  this.resolver(this.balanceValue);
92
97
  });
93
98
  }
@@ -170,6 +175,8 @@ export class GambleGame extends Container {
170
175
  if (this.lastWinAmount === 0) {
171
176
  return;
172
177
  }
178
+ SoundManager.stop("cardDrawing");
179
+ this.collectButton.flipDisabledFlag(true);
173
180
  this.redButton.flipDisabledFlag(true);
174
181
  this.blackButton.flipDisabledFlag(true);
175
182
  this.card.state.addAnimation(0, "wait", true, 0);
@@ -178,6 +185,12 @@ export class GambleGame extends Container {
178
185
  this.card.state.clearTracks();
179
186
  this.card.state.clearListeners();
180
187
  this.card.state.addAnimation(0, gambleData.winning_card, false, 0);
188
+ if (gambleData.win_amount > 0) {
189
+ SoundManager.play("cardWin");
190
+ }
191
+ else {
192
+ SoundManager.play("cardLose");
193
+ }
181
194
  this.card.state.addListener({ complete: () => resolve() });
182
195
  });
183
196
  if (gambleData.win_amount > 0) {
@@ -186,6 +199,8 @@ export class GambleGame extends Container {
186
199
  this.blackButton.flipDisabledFlag(false);
187
200
  this.redButton.eventMode = "static";
188
201
  this.blackButton.eventMode = "static";
202
+ SoundManager.play("cardDrawing");
203
+ this.collectButton.flipDisabledFlag(false);
189
204
  }
190
205
  this.card.state.addAnimation(0, "wait2", true, 0);
191
206
  this.lastWinAmount = gambleData.win_amount;
@@ -197,8 +212,10 @@ export class GambleGame extends Container {
197
212
  this.card.state.addAnimation(0, "idle", true, 0);
198
213
  await this.loseDoubleAndWinFields();
199
214
  window.setTimeout(() => {
215
+ SoundManager.stop("cardDrawing");
216
+ SoundManager.play("music");
200
217
  this.closeGame();
201
- }, 2000);
218
+ }, 500);
202
219
  }
203
220
  }
204
221
  /**
@@ -95,6 +95,16 @@ export declare abstract class AbstractColumnsContainer extends Container impleme
95
95
  * @returns {Promise<void>}
96
96
  */
97
97
  configBlindSpin(finalSymbolReels: SymbolReels, afterSpinResolver: (value: void | PromiseLike<void>) => void, indexesOfReelsToExtendSpinningTime: number[]): Promise<void>;
98
+ /**
99
+ * This function is responsible for setting symbols in single column
100
+ * @public
101
+ * @param {SymbolReels} finalSymbolReels final symbols in columns
102
+ * @param {(value: void | PromiseLike<void>) => void} afterSpinResolver resolver for after spin
103
+ * @param {number} columnIndex index of column to configure
104
+ * @param {boolean} shouldExtendSpinningTime determines whether to extend spinning time for the column
105
+ * @returns {Promise<void>}
106
+ */
107
+ configBlindSpinForColumn(finalSymbolReels: SymbolReels, afterSpinResolver: (value: void | PromiseLike<void>) => void, columnIndex: number, shouldExtendSpinningTime?: boolean): Promise<void>;
98
108
  /**
99
109
  * This cancels the timeout, removes the stored delayed action and invalidates
100
110
  * any older timeout callback that might still try to execute later.
@@ -227,4 +237,12 @@ export declare abstract class AbstractColumnsContainer extends Container impleme
227
237
  * @returns {PlayableAction[]} - array of playable actions
228
238
  */
229
239
  protected prepareConfiguringReelsEnd(finalSymbolReels: SymbolReels): PlayableAction[];
240
+ /**
241
+ * Function for preparing configuring for single reel
242
+ * @protected
243
+ * @param {SymbolReels} finalSymbolReels - final symbol reels
244
+ * @param {number} columnIndex - column index
245
+ * @returns {PlayableAction} - playable action
246
+ */
247
+ protected prepareConfiguringReelsEndForIndex(finalSymbolReels: SymbolReels, columnIndex: number): PlayableAction;
230
248
  }
@@ -187,6 +187,63 @@ export class AbstractColumnsContainer extends Container {
187
187
  resolve();
188
188
  });
189
189
  }
190
+ /**
191
+ * This function is responsible for setting symbols in single column
192
+ * @public
193
+ * @param {SymbolReels} finalSymbolReels final symbols in columns
194
+ * @param {(value: void | PromiseLike<void>) => void} afterSpinResolver resolver for after spin
195
+ * @param {number} columnIndex index of column to configure
196
+ * @param {boolean} shouldExtendSpinningTime determines whether to extend spinning time for the column
197
+ * @returns {Promise<void>}
198
+ */
199
+ async configBlindSpinForColumn(finalSymbolReels, afterSpinResolver, columnIndex, shouldExtendSpinningTime = false) {
200
+ return new Promise(async (resolve) => {
201
+ this.clearPendingMysteryDelay();
202
+ this.mysteryFxGeneration = 0;
203
+ this.skipRemainingMysteryFxs = false;
204
+ const frame = RainMan.componentRegistry.get(AbstractFrame.registryName);
205
+ const column = this.allColumns[columnIndex];
206
+ if (column === undefined) {
207
+ afterSpinResolver();
208
+ resolve();
209
+ return;
210
+ }
211
+ const configureSpinAction = this.prepareConfiguringReelsEndForIndex(finalSymbolReels, columnIndex);
212
+ if (this.quickReelsStop ||
213
+ this.skipRemainingMysteryFxs ||
214
+ RainMan.settingsStore.currentSpeed === SPEED_LEVELS.fast) {
215
+ await configureSpinAction();
216
+ SoundManager.play(SoundTracks.allRollStops);
217
+ }
218
+ else if (shouldExtendSpinningTime) {
219
+ this.currentColumn = column;
220
+ column.adaptToSpeed(RainMan.settingsStore.currentSpeed);
221
+ frame.invokeMysteryFx(column.x, column.y);
222
+ this.skipScatterDelay = configureSpinAction;
223
+ await column.speedUpForMysterySpin(100, 1500);
224
+ if (this.quickReelsStop || this.skipRemainingMysteryFxs) {
225
+ await column.changeVelocity(Speed.STOP, 0, frame);
226
+ }
227
+ else {
228
+ if (!RainMan.settingsStore.isAutoplayEnabled ||
229
+ (RainMan.settingsStore.isAutoplayEnabled && !this.quickReelsStop)) {
230
+ this.scheduleMysteryDelay(configureSpinAction, RainMan.config.durationOfActions.scatterDelayerTime);
231
+ }
232
+ await column.changeVelocity(Speed.STOP, RainMan.config.durationOfActions.scatterSpinningTime, frame);
233
+ }
234
+ frame.destroyMysteryFx();
235
+ }
236
+ else {
237
+ await configureSpinAction();
238
+ }
239
+ await column.getStopPromise();
240
+ this.clearPendingMysteryDelay();
241
+ this.mysteryFxGeneration = 0;
242
+ this.skipRemainingMysteryFxs = false;
243
+ afterSpinResolver();
244
+ resolve();
245
+ });
246
+ }
190
247
  /**
191
248
  * This cancels the timeout, removes the stored delayed action and invalidates
192
249
  * any older timeout callback that might still try to execute later.
@@ -441,4 +498,20 @@ export class AbstractColumnsContainer extends Container {
441
498
  this.currentColumn = null;
442
499
  });
443
500
  }
501
+ /**
502
+ * Function for preparing configuring for single reel
503
+ * @protected
504
+ * @param {SymbolReels} finalSymbolReels - final symbol reels
505
+ * @param {number} columnIndex - column index
506
+ * @returns {PlayableAction} - playable action
507
+ */
508
+ prepareConfiguringReelsEndForIndex(finalSymbolReels, columnIndex) {
509
+ const column = this.allColumns[columnIndex];
510
+ return async () => {
511
+ this.currentColumn = column;
512
+ column.configBlindSpin(finalSymbolReels[columnIndex]);
513
+ await column.getTriggersPromise();
514
+ this.currentColumn = null;
515
+ };
516
+ }
444
517
  }
@@ -34,6 +34,8 @@ export declare class MessageBox extends Container {
34
34
  private possibleWinShown;
35
35
  private incentiveMessageShown;
36
36
  private winLineIndicatorShown;
37
+ private winLineIndicatorXOffset;
38
+ private winLineIndicatorYOffset;
37
39
  constructor();
38
40
  /**
39
41
  * Updates the incentive text displayed in the message box.
@@ -84,6 +86,15 @@ export declare class MessageBox extends Container {
84
86
  * @returns {void}
85
87
  */
86
88
  hideWinLineIndicator(): void;
89
+ /**
90
+ * Sets the offset of the win line indicator along the x and y axes. Each time winLineIndicator is shown,
91
+ * the indicator is positioned at the current offset.
92
+ * @public
93
+ * @param {number} xOffset - The amount to move the win line indicator along the x-axis.
94
+ * @param {number} yOffset - The amount to move the win line indicator along the y-axis.
95
+ * @returns {void}
96
+ */
97
+ setWinWinLineIndicatorOffset(xOffset?: number, yOffset?: number): void;
87
98
  /**
88
99
  * Function for resizing message box
89
100
  * It sets the scale of the message box to the global minimum ratio.
@@ -39,6 +39,8 @@ export class MessageBox extends Container {
39
39
  possibleWinShown = false;
40
40
  incentiveMessageShown = false;
41
41
  winLineIndicatorShown = false;
42
+ winLineIndicatorXOffset = 0;
43
+ winLineIndicatorYOffset = 0;
42
44
  constructor() {
43
45
  super();
44
46
  this.name = MessageBox.registryName;
@@ -165,6 +167,18 @@ export class MessageBox extends Container {
165
167
  this.winLineIndicator = undefined;
166
168
  }
167
169
  }
170
+ /**
171
+ * Sets the offset of the win line indicator along the x and y axes. Each time winLineIndicator is shown,
172
+ * the indicator is positioned at the current offset.
173
+ * @public
174
+ * @param {number} xOffset - The amount to move the win line indicator along the x-axis.
175
+ * @param {number} yOffset - The amount to move the win line indicator along the y-axis.
176
+ * @returns {void}
177
+ */
178
+ setWinWinLineIndicatorOffset(xOffset, yOffset) {
179
+ this.winLineIndicatorXOffset += xOffset ?? 0;
180
+ this.winLineIndicatorYOffset += yOffset ?? 0;
181
+ }
168
182
  /**
169
183
  * Function for resizing message box
170
184
  * It sets the scale of the message box to the global minimum ratio.
@@ -334,6 +348,12 @@ export class MessageBox extends Container {
334
348
  };
335
349
  this.winLineIndicator = new WinLineIndicator(winLineTextStyle, getImageForSymbolId, message, symbolIds);
336
350
  this.winLineIndicator.y = 50;
351
+ if (this.winLineIndicatorXOffset) {
352
+ this.winLineIndicator.x = this.winLineIndicatorXOffset;
353
+ }
354
+ if (this.winLineIndicatorYOffset) {
355
+ this.winLineIndicator.y = this.winLineIndicatorYOffset;
356
+ }
337
357
  this.winLineIndicatorShown = true;
338
358
  this.centerTextOnX(this.winLineIndicator);
339
359
  this.addChild(this.winLineIndicator);
@@ -74,6 +74,7 @@ export interface MiniGameInterface {
74
74
  };
75
75
  extra_data: ExtraData;
76
76
  win_amount: number;
77
+ round_win_amount: number;
77
78
  wins: Record<string, RawWin>;
78
79
  matrix: {
79
80
  height: number;
@@ -311,6 +311,7 @@ export class AbstractController {
311
311
  if (RainMan.componentRegistry.has(BUTTONS.gamble)) {
312
312
  this.buttonsEventManager.initGambleButton(() => {
313
313
  RainMan.settingsStore.isGambleGameAvailable = false;
314
+ SoundManager.stopAll();
314
315
  this.mainContainer.invokeGambleGame();
315
316
  }, !RainMan.settingsStore.isGambleGameAvailable ||
316
317
  RainMan.settingsStore.isPlayingAnyAction ||
@@ -322,7 +323,6 @@ export class AbstractController {
322
323
  if (RainMan.componentRegistry.has(BUTTONS.take)) {
323
324
  this.buttonsEventManager.initTakeButton(() => {
324
325
  RainMan.settingsStore.useTakeAction?.();
325
- this.uiController.currentBalance = RainMan.settingsStore.balanceAfterSpin;
326
326
  RainMan.settingsStore.isTakeActionAvailable = false;
327
327
  RainMan.settingsStore.isGambleGameAvailable = false;
328
328
  if (RainMan.componentRegistry.has(BUTTONS.gamble)) {
@@ -842,6 +842,13 @@ export class AbstractController {
842
842
  this.uiController.totalNumberOfFreeSpins += this.getBoughtNumberOfFreeSpins();
843
843
  RainMan.componentRegistry.getIfExists(FreeSpinButton.registryName)?.setInteractivity(false);
844
844
  RainMan.settingsStore.setIsFreeSpinBought(false);
845
+ if (this.speedLevelBeforeFreeSpins === null) {
846
+ this.speedLevelBeforeFreeSpins = RainMan.settingsStore.currentSpeed;
847
+ }
848
+ RainMan.settingsStore.setIsTemporarySpeed(false);
849
+ RainMan.componentRegistry.setSpeedLevel(SPEED_LEVELS.slow);
850
+ this.uiController.updateShownSpeedLevel(SPEED_LEVELS.slow);
851
+ this.disableSpeedButton(false);
845
852
  }
846
853
  /**
847
854
  * This function is responsible for getting the number of free spins bought
@@ -904,6 +911,7 @@ export class AbstractController {
904
911
  logInfo("Take action can't be performed");
905
912
  return;
906
913
  }
914
+ this.shouldPlayNextSpin = false;
907
915
  if (this.currentlyPlayedAction !== undefined) {
908
916
  this.changeGamePhase(gamePhases.STOPPING_HANDLING_ACTIONS);
909
917
  this.stopPlayingSymbolsStreak();
@@ -914,6 +922,7 @@ export class AbstractController {
914
922
  if (takeData.status.code === 200) {
915
923
  RainMan.settingsStore.isTakeActionAvailable = false;
916
924
  logInfo(`💰 Round ${takeData.round_number} ended with balance:`, takeData.balance);
925
+ await this.uiController.animateCurrentBalance(takeData.balance);
917
926
  }
918
927
  else {
919
928
  RainMan.settingsStore.isTakeActionAvailable = true;
@@ -1219,7 +1228,9 @@ export class AbstractController {
1219
1228
  }
1220
1229
  await this.performActionsAfterSpin();
1221
1230
  this.freeSpinsWin = gambleWinAmount > 0 ? gambleWinAmount : null;
1222
- this.gambleGameEnabler(gambleWinAmount);
1231
+ if (!RainMan.globals.isSuperBonusGameEnabled) {
1232
+ this.gambleGameEnabler(gambleWinAmount);
1233
+ }
1223
1234
  if (RainMan.settingsStore.isAutoplayEnabled ||
1224
1235
  (RainMan.settingsStore.howManyFreeSpinsLeft !== 0 && this.shouldAutoplayFreeSpins)) {
1225
1236
  this.handleAutoplayLogic();
@@ -1237,6 +1248,7 @@ export class AbstractController {
1237
1248
  if (this.speedLevelBeforeFreeSpins !== null &&
1238
1249
  !RainMan.settingsStore.isFreeSpinsPlayEnabled &&
1239
1250
  !RainMan.globals.isSuperBonusGameEnabled) {
1251
+ RainMan.settingsStore.setIsTemporarySpeed(false);
1240
1252
  RainMan.componentRegistry.setSpeedLevel(this.speedLevelBeforeFreeSpins);
1241
1253
  this.uiController.updateShownSpeedLevel(this.speedLevelBeforeFreeSpins);
1242
1254
  this.speedLevelBeforeFreeSpins = null;
@@ -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
  */
@@ -189,6 +188,13 @@ export declare class UiController {
189
188
  * @returns {Promise<void>}
190
189
  */
191
190
  updateDisplayedBalance(amount: number): Promise<void>;
191
+ /**
192
+ * Function for animating current balance to a final value.
193
+ * @public
194
+ * @param {number} finalBalance - final balance to display
195
+ * @returns {Promise<void>}
196
+ */
197
+ animateCurrentBalance(finalBalance: number): Promise<void>;
192
198
  /**
193
199
  * Function for resetting win amount
194
200
  * @public
@@ -216,7 +216,6 @@ export class UiController {
216
216
  }
217
217
  /**
218
218
  * Shows game pays in a bottom panel, if exists
219
- * @param amount
220
219
  * @public
221
220
  * @returns {void}
222
221
  */
@@ -282,6 +281,23 @@ export class UiController {
282
281
  async updateDisplayedBalance(amount) {
283
282
  this.promisesToSetBalances.push(RainMan.componentRegistry.get(COMPONENTS.creditText).update(amount));
284
283
  }
284
+ /**
285
+ * Function for animating current balance to a final value.
286
+ * @public
287
+ * @param {number} finalBalance - final balance to display
288
+ * @returns {Promise<void>}
289
+ */
290
+ async animateCurrentBalance(finalBalance) {
291
+ const balanceDiff = ceilToDecimal(finalBalance - this._currentBalance);
292
+ this._currentBalance = finalBalance;
293
+ if (balanceDiff === 0) {
294
+ this.setDisplayedBalance();
295
+ return;
296
+ }
297
+ const animationPromise = RainMan.componentRegistry.get(COMPONENTS.creditText).update(balanceDiff);
298
+ this.promisesToSetBalances.push(animationPromise);
299
+ await animationPromise;
300
+ }
285
301
  /**
286
302
  * Function for resetting win amount
287
303
  * @public
@@ -24,7 +24,7 @@ export class AbstractLoadingContainer extends Container {
24
24
  this.name = "loadingContainer";
25
25
  this.x = 0;
26
26
  this.y = 0;
27
- this.loaderContainer = new SpriteLoadingContainer("./assets/images/materials/texture.png");
27
+ this.loaderContainer = new SpriteLoadingContainer("./assets/images/materials/texture.webp");
28
28
  this.presentationFinishedPromise = new Promise((resolve) => {
29
29
  this.resolvePresentationFinished = resolve;
30
30
  });
@@ -113,6 +113,12 @@ export declare class SettingsStore {
113
113
  * @returns {void}
114
114
  */
115
115
  disableSpecialButtons(): void;
116
+ /**
117
+ * Enables special buttons like gamble and take.
118
+ * @public
119
+ * @returns {void}
120
+ */
121
+ enableSpecialButtons(): void;
116
122
  /**
117
123
  * Swaps the bet change disabled flag.
118
124
  * @public
@@ -338,6 +344,13 @@ export declare class SettingsStore {
338
344
  * @returns {void}
339
345
  */
340
346
  setAutoSpinsCount(count: number): void;
347
+ /**
348
+ * Setter for the infinite autoplay flag
349
+ * @public
350
+ * @param {boolean} newState new state for infinite autoplay
351
+ * @returns {void}
352
+ */
353
+ setInfinitySpinsEnabled(newState: boolean): void;
341
354
  /**
342
355
  * Setter for the autoplay enabled flag
343
356
  * @public
@@ -79,6 +79,7 @@ export class SettingsStore {
79
79
  initialAutoplaySettingsState = {
80
80
  onAnyWin: false,
81
81
  howManyAutoSpinsLeft: INITIAL_AUTO_SPIN_COUNT,
82
+ infinitySpinsEnabled: false,
82
83
  singleWinExceeds: 0,
83
84
  balancedIncreased: 0,
84
85
  balancedDecreased: 0,
@@ -185,6 +186,21 @@ export class SettingsStore {
185
186
  RainMan.componentRegistry.get(BUTTONS.take).flipDisabledFlag(true);
186
187
  }
187
188
  }
189
+ /**
190
+ * Enables special buttons like gamble and take.
191
+ * @public
192
+ * @returns {void}
193
+ */
194
+ enableSpecialButtons() {
195
+ if (RainMan.componentRegistry.has(BUTTONS.gamble)) {
196
+ RainMan.settingsStore.isGambleGameAvailable = true;
197
+ RainMan.componentRegistry.get(BUTTONS.gamble).flipDisabledFlag(false);
198
+ }
199
+ if (RainMan.componentRegistry.has(BUTTONS.take)) {
200
+ RainMan.settingsStore.isTakeActionAvailable = true;
201
+ RainMan.componentRegistry.get(BUTTONS.take).flipDisabledFlag(false);
202
+ }
203
+ }
188
204
  /**
189
205
  * Swaps the bet change disabled flag.
190
206
  * @public
@@ -576,6 +592,15 @@ export class SettingsStore {
576
592
  setAutoSpinsCount(count) {
577
593
  this.howManyAutoSpinsLeft = count;
578
594
  }
595
+ /**
596
+ * Setter for the infinite autoplay flag
597
+ * @public
598
+ * @param {boolean} newState new state for infinite autoplay
599
+ * @returns {void}
600
+ */
601
+ setInfinitySpinsEnabled(newState) {
602
+ this.infinitySpinsEnabled = newState;
603
+ }
579
604
  /**
580
605
  * Setter for the autoplay enabled flag
581
606
  * @public
@@ -713,6 +738,7 @@ export class SettingsStore {
713
738
  resetAutoplaySettings() {
714
739
  this.onAnyWin = this.initialAutoplaySettingsState.onAnyWin;
715
740
  this.howManyAutoSpinsLeft = this.initialAutoplaySettingsState.howManyAutoSpinsLeft;
741
+ this.infinitySpinsEnabled = this.initialAutoplaySettingsState.infinitySpinsEnabled;
716
742
  this.singleWinExceeds = this.initialAutoplaySettingsState.singleWinExceeds;
717
743
  this.balancedIncreased = this.initialAutoplaySettingsState.balancedIncreased;
718
744
  this.balancedDecreased = this.initialAutoplaySettingsState.balancedDecreased;
@@ -16,15 +16,16 @@ const interruptedAudioResume = () => {
16
16
  window.addEventListener("touchend", resumeAudioOnInterruption, { once: true, passive: true });
17
17
  };
18
18
  /**
19
- * Pauses audio when the page goes to the background.
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.
20
21
  * @returns {void}
21
22
  */
22
- const pauseAudio = () => {
23
+ const prepareAudioForPageHide = () => {
23
24
  isResumingAudio = false;
24
25
  shouldResumeAudioOnInterruption = false;
25
26
  removeInterruptedAudioResumeListeners();
26
27
  if (RainMan.settingsStore.isSoundEnabled()) {
27
- SoundManager.stopSoundsForPageHide();
28
+ SoundManager.captureAmbientPlaybackPositionForPageHide();
28
29
  }
29
30
  };
30
31
  /**
@@ -71,19 +72,19 @@ function resumeAudioOnInterruption() {
71
72
  */
72
73
  const handleVisibilityChange = () => {
73
74
  if (document.hidden) {
74
- pauseAudio();
75
+ prepareAudioForPageHide();
75
76
  return;
76
77
  }
77
- interruptedAudioResume();
78
+ resumeAudio();
78
79
  };
79
80
  /**
80
81
  * Sets up audio states for tab switches and iOS app interruptions.
81
82
  * @returns {void}
82
83
  */
83
84
  export const loadSoundsOnTabChange = () => {
84
- sound.disableAutoPause = true;
85
+ sound.disableAutoPause = false;
85
86
  document.addEventListener("visibilitychange", handleVisibilityChange);
86
- window.addEventListener("pagehide", () => pauseAudio());
87
- window.addEventListener("pageshow", () => interruptedAudioResume());
88
- window.addEventListener("focus", () => interruptedAudioResume());
87
+ window.addEventListener("pagehide", () => prepareAudioForPageHide());
88
+ window.addEventListener("pageshow", () => resumeAudio());
89
+ window.addEventListener("focus", () => resumeAudio());
89
90
  };
@@ -25,6 +25,8 @@ export declare class TexturedText extends UpdatableValueComponent {
25
25
  * @returns {void}
26
26
  */
27
27
  randomizeSet(value: number, _possibleValues: number[]): void;
28
+ adjustTextureSpriteLength(): void;
29
+ set(newValue: number): void;
28
30
  /**
29
31
  * Plays the animation for the text element
30
32
  * @public
@@ -18,6 +18,7 @@ export class TexturedText extends UpdatableValueComponent {
18
18
  this.registryName = registryName;
19
19
  this.valueTextComponent.style = { ...this.valueTextComponent.style, fontSize: 120, padding: 50 };
20
20
  this.valueTextComponent.anchor.set(0.5, 0.5);
21
+ this.updateFontSize();
21
22
  if (textureName) {
22
23
  this.textureSprite = new TilingSprite(getTexture(textureName), this.valueTextComponent.width, this.valueTextComponent.height);
23
24
  this.textureSprite.scale.set(1.1);
@@ -48,6 +49,17 @@ export class TexturedText extends UpdatableValueComponent {
48
49
  this.valueTextComponent.style.fontSize = -10 * new MoneyText(value).toString().length + this.baseFontSize;
49
50
  super.update(value, 1);
50
51
  }
52
+ adjustTextureSpriteLength() {
53
+ if (!this.textureSprite) {
54
+ return;
55
+ }
56
+ this.textureSprite.width = this.valueTextComponent.width;
57
+ this.textureSprite.height = this.valueTextComponent.height;
58
+ }
59
+ set(newValue) {
60
+ super.set(newValue);
61
+ this.adjustTextureSpriteLength();
62
+ }
51
63
  /**
52
64
  * Plays the animation for the text element
53
65
  * @public
@@ -89,5 +101,6 @@ export class TexturedText extends UpdatableValueComponent {
89
101
  */
90
102
  updateFontSize() {
91
103
  this.valueTextComponent.style.fontSize = -10 * super.value.length + this.baseFontSize;
104
+ this.adjustTextureSpriteLength();
92
105
  }
93
106
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixi-rainman-game-engine",
3
- "version": "0.3.40",
3
+ "version": "0.3.42-beta.1",
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",