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

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.
@@ -81,6 +81,7 @@ export interface Globals {
81
81
  disableSpeedPopup?: (value: boolean) => void;
82
82
  handleDisablingButtons?: (value?: boolean) => void;
83
83
  throttledSpin?: () => void;
84
+ prepareBoughtFreeSpins?: () => void;
84
85
  updateFreeSpinShown?: (value: number) => void;
85
86
  backendSymbolMap?: Map<number, never>;
86
87
  betTable?: number[];
@@ -145,9 +145,12 @@ export class AbstractBuyFreeSpinsPlate extends Container {
145
145
  RainMan.settingsStore.setRoundNumber(roundNumber);
146
146
  this.cleanup();
147
147
  this.onContinueCallback();
148
- RainMan.settingsStore.handleModalInvocation?.("freeSpin");
149
- RainMan.settingsStore.handleInvokeFreeSpinPlate?.(option.amount, false);
148
+ await RainMan.settingsStore.handleInvokeFreeSpinPlate?.(option.amount, false);
150
149
  RainMan.settingsStore.disableKeyboardShortcuts = false;
150
+ RainMan.globals.prepareBoughtFreeSpins?.();
151
+ setTimeout(() => {
152
+ RainMan.globals.throttledSpin?.();
153
+ }, 500);
151
154
  }
152
155
  catch (err) {
153
156
  console.error(err);
@@ -2,7 +2,7 @@ import { Container, Sprite, Text, TextStyle, Texture } from "pixi.js";
2
2
  import { IntroLogo } from "../common";
3
3
  import { IntroContinueButton } from "./IntroContinueButton";
4
4
  import { IntroDotButton } from "./IntroDotButton";
5
- import { IntroPageData } from "./IntroPageData";
5
+ import { IntroPageData, IntroPageInnerTextBlock } from "./IntroPageData";
6
6
  /**
7
7
  * Abstract intro screen shown between loading and gameplay.
8
8
  * Each game subclasses this and provides assets + page definitions as properties.
@@ -38,6 +38,7 @@ export declare abstract class AbstractIntroScreen extends Container {
38
38
  protected dotShadow?: Sprite;
39
39
  protected frameImage: Sprite;
40
40
  protected pageImage: Sprite;
41
+ protected pageInnerTextBlocks: Text[];
41
42
  protected pageText: Text;
42
43
  private currentPage;
43
44
  private isChecked;
@@ -54,14 +55,19 @@ export declare abstract class AbstractIntroScreen extends Container {
54
55
  protected abstract positionText(): void;
55
56
  protected abstract positionCheckbox(): void;
56
57
  protected abstract positionLogo(): void;
58
+ protected positionPageInnerText(): void;
57
59
  protected abstract getPageImageScale(): {
58
60
  vertical: number;
59
61
  horizontal: number;
60
62
  };
61
63
  protected abstract positionPageImage(): void;
64
+ protected createPageInnerTextBlocks(blocks: IntroPageInnerTextBlock[]): Text[];
62
65
  protected showPage(index: number): void;
63
66
  private updateFade;
64
67
  private cleanup;
68
+ private refreshPageInnerText;
69
+ private setInnerTextAlpha;
70
+ private setPageContentAlpha;
65
71
  /**
66
72
  * Resizes dot shadow width to match all dots and centers it on them.
67
73
  * Call this in positionDots() after positioning the dots.
@@ -32,6 +32,7 @@ export class AbstractIntroScreen extends Container {
32
32
  dotShadow;
33
33
  frameImage;
34
34
  pageImage;
35
+ pageInnerTextBlocks = [];
35
36
  pageText;
36
37
  currentPage = 0;
37
38
  isChecked = false;
@@ -91,8 +92,7 @@ export class AbstractIntroScreen extends Container {
91
92
  this.addChild(bg);
92
93
  this.frameImage.texture = getTexture(isPortrait ? "intro-frame-mobile.png" : "intro-frame.png");
93
94
  this.showPage(this.currentPage);
94
- this.pageImage.alpha = 1;
95
- this.pageText.alpha = 1;
95
+ this.setPageContentAlpha(1);
96
96
  const pageImageScale = this.getPageImageScale();
97
97
  const dimensions = scaleToParent(this.pageImage.texture.width, this.pageImage.texture.height, RainMan.app.screen.width * pageImageScale.horizontal, RainMan.app.screen.height * pageImageScale.vertical);
98
98
  this.pageImage.width = dimensions.width;
@@ -109,6 +109,7 @@ export class AbstractIntroScreen extends Container {
109
109
  this.addChild(this.frameImage);
110
110
  this.addChild(this.frameImage);
111
111
  this.addChild(this.pageImage);
112
+ this.pageInnerTextBlocks.forEach((block) => this.addChild(block));
112
113
  this.addChild(this.pageText);
113
114
  this.addChild(this.continueButton);
114
115
  this.addChild(this.checkboxSprite);
@@ -123,6 +124,7 @@ export class AbstractIntroScreen extends Container {
123
124
  this.positionCheckbox();
124
125
  this.positionContinueButton();
125
126
  this.positionText();
127
+ this.positionPageInnerText();
126
128
  this.positionDotShadow();
127
129
  this.frameImage.x = this.pageImage.x;
128
130
  this.frameImage.y = this.pageImage.y;
@@ -133,6 +135,10 @@ export class AbstractIntroScreen extends Container {
133
135
  this.frameImage.width = this.pageImage.width;
134
136
  this.frameImage.height = this.pageImage.height;
135
137
  }
138
+ positionPageInnerText() { }
139
+ createPageInnerTextBlocks(blocks) {
140
+ return blocks.map((block) => new Text(block.text));
141
+ }
136
142
  showPage(index) {
137
143
  if (index < 0 || index >= this.pages.length)
138
144
  return;
@@ -141,6 +147,8 @@ export class AbstractIntroScreen extends Container {
141
147
  const isPortrait = getDeviceOrientation() === "mobile-portrait";
142
148
  this.pageImage.texture = Texture.from(isPortrait ? page.imageMobile : page.imageDesktop);
143
149
  this.pageText.text = page.text;
150
+ this.refreshPageInnerText(page);
151
+ this.setInnerTextAlpha(this.pageImage.alpha);
144
152
  for (let i = 0; i < this.dots.length; i++) {
145
153
  this.dots[i].setActive(i === index);
146
154
  }
@@ -153,6 +161,28 @@ export class AbstractIntroScreen extends Container {
153
161
  RainMan.app.renderer.off("resize", this.resizeHandler);
154
162
  this.destroy({ children: true });
155
163
  }
164
+ refreshPageInnerText(page) {
165
+ this.pageInnerTextBlocks.forEach((block) => {
166
+ this.removeChild(block);
167
+ block.destroy();
168
+ });
169
+ this.pageInnerTextBlocks = [];
170
+ if (!page.pageInnerText?.length)
171
+ return;
172
+ this.pageInnerTextBlocks = this.createPageInnerTextBlocks(page.pageInnerText);
173
+ this.pageInnerTextBlocks.forEach((block) => this.addChild(block));
174
+ this.positionPageInnerText();
175
+ }
176
+ setInnerTextAlpha(alpha) {
177
+ this.pageInnerTextBlocks.forEach((block) => {
178
+ block.alpha = alpha;
179
+ });
180
+ }
181
+ setPageContentAlpha(alpha) {
182
+ this.pageImage.alpha = alpha;
183
+ this.pageText.alpha = alpha;
184
+ this.setInnerTextAlpha(alpha);
185
+ }
156
186
  /**
157
187
  * Resizes dot shadow width to match all dots and centers it on them.
158
188
  * Call this in positionDots() after positioning the dots.
@@ -195,16 +225,14 @@ export class AbstractIntroScreen extends Container {
195
225
  this.fadeTween = new TWEEN.Tween({ alpha: this.pageImage.alpha })
196
226
  .to({ alpha: 0 }, this.FADE_DURATION)
197
227
  .onUpdate((obj) => {
198
- this.pageImage.alpha = obj.alpha;
199
- this.pageText.alpha = obj.alpha;
228
+ this.setPageContentAlpha(obj.alpha);
200
229
  })
201
230
  .onComplete(() => {
202
231
  this.showPage(index);
203
232
  this.fadeTween = new TWEEN.Tween({ alpha: 0 })
204
233
  .to({ alpha: 1 }, this.FADE_DURATION)
205
234
  .onUpdate((obj) => {
206
- this.pageImage.alpha = obj.alpha;
207
- this.pageText.alpha = obj.alpha;
235
+ this.setPageContentAlpha(obj.alpha);
208
236
  })
209
237
  .onComplete(() => {
210
238
  this.isAnimating = false;
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Data for a single inner text block rendered inside an intro page image.
3
+ */
4
+ export type IntroPageInnerTextBlock = {
5
+ id?: string;
6
+ text: string;
7
+ };
1
8
  /**
2
9
  * Data for a single page in the intro screen.
3
10
  */
@@ -5,4 +12,5 @@ export type IntroPageData = {
5
12
  imageDesktop: string;
6
13
  imageMobile: string;
7
14
  text: string;
15
+ pageInnerText?: IntroPageInnerTextBlock[];
8
16
  };
@@ -549,7 +549,7 @@ export class AbstractMainContainer extends Container {
549
549
  async invokeGambleGame() {
550
550
  if (!this.gambleGameSpineName)
551
551
  return;
552
- const newBalance = await new Promise((resolve) => {
552
+ await new Promise((resolve) => {
553
553
  this.gambleGame = new GambleGame(this.gambleGameSpineName, resolve, this._controller.lastWinAmount, this._controller.balance);
554
554
  RainMan.settingsStore.isGambleGameActive = true;
555
555
  this.showOverlay();
@@ -561,7 +561,7 @@ export class AbstractMainContainer extends Container {
561
561
  this._controller.updateGambleValues(RainMan.settingsStore.gambleGameWin);
562
562
  this.gambleGame = null;
563
563
  }
564
- this._controller.balance = newBalance;
564
+ this._controller?.updateBalance();
565
565
  this.hideOverlay();
566
566
  }
567
567
  /**
@@ -126,6 +126,7 @@ export class GambleGame extends Container {
126
126
  this.card.state.addAnimation(0, "wait", true, 0);
127
127
  const gambleData = await window.connectionWrapper.fetchGambleData(RainMan.settingsStore.roundNumber, this.lastWinAmount, choice);
128
128
  this.balanceValue = gambleData.balance + gambleData.win_amount;
129
+ this.balanceValue = gambleData.balanceAfterSpin;
129
130
  await new Promise((resolve) => {
130
131
  this.card.state.clearTracks();
131
132
  this.card.state.clearListeners();
@@ -18,6 +18,9 @@ export declare abstract class AbstractColumnsContainer extends Container impleme
18
18
  protected allColumns: AbstractSymbolsColumn[];
19
19
  private velocityMultiplier;
20
20
  private skipScatterDelay;
21
+ private mysteryDelayTimeout;
22
+ private mysteryFxGeneration;
23
+ private skipRemainingMysteryFxs;
21
24
  currentColumn: Nullable<AbstractSymbolsColumn>;
22
25
  private playedSounds;
23
26
  quickReelsStop: boolean;
@@ -92,6 +95,28 @@ export declare abstract class AbstractColumnsContainer extends Container impleme
92
95
  * @returns {Promise<void>}
93
96
  */
94
97
  configBlindSpin(finalSymbolReels: SymbolReels, afterSpinResolver: (value: void | PromiseLike<void>) => void, indexesOfReelsToExtendSpinningTime: number[]): Promise<void>;
98
+ /**
99
+ * This cancels the timeout, removes the stored delayed action and invalidates
100
+ * any older timeout callback that might still try to execute later.
101
+ * @private
102
+ * @returns {void}
103
+ */
104
+ private clearPendingMysteryDelay;
105
+ /**
106
+ * Runs the pending mysteryFx immediately and clears its timeout.
107
+ * @private
108
+ * @returns {void}
109
+ */
110
+ private executePendingMysteryFx;
111
+ /**
112
+ * The callback is bound to the current generation so an old timeout from
113
+ * a previous spin cannot execute after a newer mystery delay was scheduled.
114
+ * @private
115
+ * @param {PlayableAction} action delayed reel configuration action
116
+ * @param {number} delay time in milliseconds before the action should run
117
+ * @returns {void}
118
+ */
119
+ private scheduleMysteryDelay;
95
120
  /**
96
121
  * Function for playing all streaks
97
122
  * @public
@@ -20,6 +20,9 @@ export class AbstractColumnsContainer extends Container {
20
20
  allColumns = [];
21
21
  velocityMultiplier = defaultSpeedConfig.symbolsColumnsSpeed;
22
22
  skipScatterDelay = null;
23
+ mysteryDelayTimeout = null;
24
+ mysteryFxGeneration = 0;
25
+ skipRemainingMysteryFxs = false;
23
26
  currentColumn = null;
24
27
  playedSounds = [];
25
28
  quickReelsStop = false;
@@ -93,12 +96,13 @@ export class AbstractColumnsContainer extends Container {
93
96
  */
94
97
  enableQuickReelsStop() {
95
98
  this.quickReelsStop = true;
99
+ this.skipRemainingMysteryFxs = true;
96
100
  RainMan.settingsStore.isQuickSpinsActive = true;
97
101
  RainMan.settingsStore.setIsTemporarySpeed(true);
98
102
  RainMan.componentRegistry.setTemporarySpeedLevel(SPEED_LEVELS.fast);
99
- this.skipScatterDelay?.();
103
+ this.executePendingMysteryFx();
104
+ this.currentColumn?.releaseNextColumnTrigger();
100
105
  this.resolveMysterySpeedUp();
101
- this.skipScatterDelay = null;
102
106
  if (this.currentColumn) {
103
107
  this.currentColumn.skipHidingMysteryFx = true;
104
108
  }
@@ -133,10 +137,15 @@ export class AbstractColumnsContainer extends Container {
133
137
  // spin
134
138
  return new Promise(async (resolve) => {
135
139
  let index = 0;
140
+ this.clearPendingMysteryDelay();
141
+ this.mysteryFxGeneration = 0;
142
+ this.skipRemainingMysteryFxs = false;
136
143
  const configuredBlindSpinsActions = this.prepareConfiguringReelsEnd(finalSymbolReels);
137
144
  const frame = RainMan.componentRegistry.get(AbstractFrame.registryName);
138
145
  while (configuredBlindSpinsActions.length !== 0) {
139
- if (this.quickReelsStop || RainMan.settingsStore.currentSpeed === SPEED_LEVELS.fast) {
146
+ if (this.quickReelsStop ||
147
+ this.skipRemainingMysteryFxs ||
148
+ RainMan.settingsStore.currentSpeed === SPEED_LEVELS.fast) {
140
149
  await Promise.all(configuredBlindSpinsActions.map(async (action) => await action()));
141
150
  configuredBlindSpinsActions.length = 0;
142
151
  SoundManager.play(SoundTracks.allRollStops);
@@ -145,21 +154,22 @@ export class AbstractColumnsContainer extends Container {
145
154
  const configureSpinAction = configuredBlindSpinsActions.shift();
146
155
  if (configureSpinAction !== undefined) {
147
156
  if (indexesOfReelsToExtendSpinningTime.includes(index)) {
148
- this.currentColumn = this.allColumns[index];
149
- this.currentColumn.adaptToSpeed(RainMan.settingsStore.currentSpeed);
150
- frame.invokeMysteryFx(this.currentColumn.x, this.currentColumn.y);
157
+ const mysteryColumn = this.allColumns[index];
158
+ this.currentColumn = mysteryColumn;
159
+ mysteryColumn.adaptToSpeed(RainMan.settingsStore.currentSpeed);
160
+ frame.invokeMysteryFx(mysteryColumn.x, mysteryColumn.y);
151
161
  this.skipScatterDelay = configureSpinAction;
152
- await this.currentColumn.speedUpForMysterySpin(100, 1500);
153
- if (!this.quickReelsStop) {
162
+ await mysteryColumn.speedUpForMysterySpin(100, 1500);
163
+ if (this.quickReelsStop || this.skipRemainingMysteryFxs) {
164
+ await mysteryColumn.changeVelocity(Speed.STOP, 0, frame);
165
+ }
166
+ else {
154
167
  if (!RainMan.settingsStore.isAutoplayEnabled ||
155
168
  (RainMan.settingsStore.isAutoplayEnabled && !this.quickReelsStop)) {
156
- setTimeout(() => {
157
- this.skipScatterDelay?.();
158
- this.skipScatterDelay = null;
159
- }, RainMan.config.durationOfActions.scatterDelayerTime);
169
+ this.scheduleMysteryDelay(configureSpinAction, RainMan.config.durationOfActions.scatterDelayerTime);
160
170
  }
171
+ await mysteryColumn.changeVelocity(Speed.STOP, RainMan.config.durationOfActions.scatterSpinningTime, frame);
161
172
  }
162
- await this.currentColumn.changeVelocity(Speed.STOP, RainMan.config.durationOfActions.scatterSpinningTime, frame);
163
173
  frame.destroyMysteryFx();
164
174
  }
165
175
  if (!indexesOfReelsToExtendSpinningTime.includes(index)) {
@@ -170,10 +180,60 @@ export class AbstractColumnsContainer extends Container {
170
180
  this.currentColumn = null;
171
181
  }
172
182
  await Promise.all(this.allColumns.map((column) => column.getStopPromise()));
183
+ this.clearPendingMysteryDelay();
184
+ this.mysteryFxGeneration = 0;
185
+ this.skipRemainingMysteryFxs = false;
173
186
  afterSpinResolver();
174
187
  resolve();
175
188
  });
176
189
  }
190
+ /**
191
+ * This cancels the timeout, removes the stored delayed action and invalidates
192
+ * any older timeout callback that might still try to execute later.
193
+ * @private
194
+ * @returns {void}
195
+ */
196
+ clearPendingMysteryDelay() {
197
+ this.mysteryFxGeneration += 1;
198
+ if (this.mysteryDelayTimeout !== null) {
199
+ clearTimeout(this.mysteryDelayTimeout);
200
+ this.mysteryDelayTimeout = null;
201
+ }
202
+ this.skipScatterDelay = null;
203
+ }
204
+ /**
205
+ * Runs the pending mysteryFx immediately and clears its timeout.
206
+ * @private
207
+ * @returns {void}
208
+ */
209
+ executePendingMysteryFx() {
210
+ const pendingMysteryDelay = this.skipScatterDelay;
211
+ this.clearPendingMysteryDelay();
212
+ pendingMysteryDelay?.();
213
+ }
214
+ /**
215
+ * The callback is bound to the current generation so an old timeout from
216
+ * a previous spin cannot execute after a newer mystery delay was scheduled.
217
+ * @private
218
+ * @param {PlayableAction} action delayed reel configuration action
219
+ * @param {number} delay time in milliseconds before the action should run
220
+ * @returns {void}
221
+ */
222
+ scheduleMysteryDelay(action, delay) {
223
+ this.clearPendingMysteryDelay();
224
+ // Store new mysteryFx action to be available in executePendingMysteryFx
225
+ this.skipScatterDelay = action;
226
+ const mysteryFxGeneration = this.mysteryFxGeneration;
227
+ this.mysteryDelayTimeout = setTimeout(() => {
228
+ if (mysteryFxGeneration !== this.mysteryFxGeneration) {
229
+ return;
230
+ }
231
+ this.mysteryDelayTimeout = null;
232
+ this.skipScatterDelay = null;
233
+ this.mysteryFxGeneration += 1;
234
+ action();
235
+ }, delay);
236
+ }
177
237
  /**
178
238
  * Function for playing all streaks
179
239
  * @public
@@ -373,8 +433,10 @@ export class AbstractColumnsContainer extends Container {
373
433
  */
374
434
  prepareConfiguringReelsEnd(finalSymbolReels) {
375
435
  return this.allColumns.map((column, index) => async () => {
436
+ this.currentColumn = column;
376
437
  column.configBlindSpin(finalSymbolReels[index]);
377
438
  await column.getTriggersPromise();
439
+ this.currentColumn = null;
378
440
  });
379
441
  }
380
442
  }
@@ -167,6 +167,12 @@ export declare abstract class AbstractSymbolsColumn extends Container implements
167
167
  * @returns {Promise<void>} stop promise
168
168
  */
169
169
  getStopPromise(): Promise<void>;
170
+ /**
171
+ * Function for releasing trigger to next column
172
+ * @public
173
+ * @returns {void}
174
+ */
175
+ releaseNextColumnTrigger(): void;
170
176
  /**
171
177
  * Function for stopping all playing animations of symbols
172
178
  * @public
@@ -298,6 +298,17 @@ export class AbstractSymbolsColumn extends Container {
298
298
  }
299
299
  throw new Error("No promise to return");
300
300
  }
301
+ /**
302
+ * Function for releasing trigger to next column
303
+ * @public
304
+ * @returns {void}
305
+ */
306
+ releaseNextColumnTrigger() {
307
+ if (this.triggerNextColumn !== null) {
308
+ this.triggerNextColumn();
309
+ this.triggerNextColumn = null;
310
+ }
311
+ }
301
312
  /**
302
313
  * Function for stopping all playing animations of symbols
303
314
  * @public
@@ -618,12 +629,9 @@ export class AbstractSymbolsColumn extends Container {
618
629
  if (element) {
619
630
  // swap symbol with chosen element
620
631
  symbol.swapSymbol(element);
621
- // symbol.setSpineMode(MOVE3);
622
632
  }
623
633
  if (this.ending.length === 1) {
624
- if (this.triggerNextColumn) {
625
- this.triggerNextColumn();
626
- }
634
+ this.releaseNextColumnTrigger();
627
635
  }
628
636
  if (this.ending.length === 0) {
629
637
  this.shouldMoveToTop = false;
@@ -64,6 +64,7 @@ export declare abstract class AbstractController<T> {
64
64
  protected resolveSuperBonusWin?: () => void;
65
65
  clearMobileSpinTimeout: (() => void) | undefined;
66
66
  protected initData: InitDataInterface;
67
+ protected shouldPlayNextSpin: boolean;
67
68
  protected _lastWinAmount: number;
68
69
  protected constructor(buttonsEventManager: ButtonsEventManager, mainContainer: AbstractMainContainer, connection: SubscribableConnectionWrapper);
69
70
  /**
@@ -262,6 +263,7 @@ export declare abstract class AbstractController<T> {
262
263
  * @returns {void}
263
264
  */
264
265
  protected throttledSpin(): void;
266
+ protected prepareBoughtFreeSpins(): void;
265
267
  /**
266
268
  * This function is responsible for getting the number of free spins bought
267
269
  * @private
@@ -366,7 +368,7 @@ export declare abstract class AbstractController<T> {
366
368
  * @param {number} winAmount amount from gamble game
367
369
  * @returns {void}
368
370
  */
369
- protected gambleGameEnabler(winAmount: number): void;
371
+ gambleGameEnabler(winAmount: number): void;
370
372
  /**
371
373
  * Function for spin logic of the game
372
374
  * This function is responsible for:
@@ -64,6 +64,7 @@ export class AbstractController {
64
64
  resolveSuperBonusWin;
65
65
  clearMobileSpinTimeout;
66
66
  initData;
67
+ shouldPlayNextSpin = false;
67
68
  _lastWinAmount = 0;
68
69
  constructor(buttonsEventManager, mainContainer, connection) {
69
70
  this.buttonsEventManager = buttonsEventManager;
@@ -102,6 +103,7 @@ export class AbstractController {
102
103
  RainMan.settingsStore.setRoundNumber(this.roundNumber);
103
104
  RainMan.settingsStore.initPaytableMap(initData);
104
105
  RainMan.globals.throttledSpin = this.throttledSpin.bind(this);
106
+ RainMan.globals.prepareBoughtFreeSpins = this.prepareBoughtFreeSpins.bind(this);
105
107
  RainMan.settingsStore.useTakeAction = this.useTakeAction.bind(this);
106
108
  this.connection.subscribeForConfigChanges(this.reinitializeUserBalanceData.bind(this));
107
109
  this.uiController.updateBet("first");
@@ -480,30 +482,47 @@ export class AbstractController {
480
482
  this.clearMobileSpinTimeout();
481
483
  });
482
484
  refreshButton.on("touchstart", () => {
485
+ if (this.mobileSpinTimeout) {
486
+ clearInterval(this.mobileSpinTimeout);
487
+ this.mobileSpinTimeout = null;
488
+ }
489
+ if (this.mobileSpinDelay) {
490
+ clearTimeout(this.mobileSpinDelay);
491
+ this.mobileSpinDelay = null;
492
+ }
483
493
  if (this.buttonHeldSince === 0) {
484
494
  this.buttonHeldSince = Date.now();
485
495
  }
486
- this.mobileSpinTimeout = setInterval(() => {
487
- const buttonHold = Date.now() - this.buttonHeldSince;
488
- if (buttonHold >= 500) {
489
- this.columnsContainer.enableQuickReelsStop();
490
- }
491
- this.handleDisablingButtons(true);
492
- this.quickStopController.setQuickStopCounterLock(true);
493
- this.summaryStop = this.skipFreeSpinSummary ? false : this.invokeFreeSpinSummaryPlateAfterWin;
494
- if (this.invokeFreeSpinPlateAfterWin ||
495
- this.summaryStop ||
496
- this.shouldStopMobileSpin ||
497
- this.resolveBigWin !== undefined ||
498
- this.resolveSuperBonusWin !== undefined ||
499
- this.resolveMysteryWin !== undefined) {
500
- if (this.clearMobileSpinTimeout) {
501
- this.clearMobileSpinTimeout();
502
- }
496
+ this.mobileSpinDelay = setTimeout(() => {
497
+ this.mobileSpinDelay = null;
498
+ if (this.buttonHeldSince === 0) {
503
499
  return;
504
500
  }
505
- this.handleSpinLogic();
506
- }, RainMan.config.durationOfActions.mobileSpinTimeout);
501
+ this.mobileSpinTimeout = setInterval(() => {
502
+ if (this.buttonHeldSince === 0) {
503
+ if (this.clearMobileSpinTimeout) {
504
+ this.clearMobileSpinTimeout();
505
+ }
506
+ return;
507
+ }
508
+ this.columnsContainer.enableQuickReelsStop();
509
+ this.handleDisablingButtons(true);
510
+ this.quickStopController.setQuickStopCounterLock(true);
511
+ this.summaryStop = this.skipFreeSpinSummary ? false : this.invokeFreeSpinSummaryPlateAfterWin;
512
+ if (this.invokeFreeSpinPlateAfterWin ||
513
+ this.summaryStop ||
514
+ this.shouldStopMobileSpin ||
515
+ this.resolveBigWin !== undefined ||
516
+ this.resolveSuperBonusWin !== undefined ||
517
+ this.resolveMysteryWin !== undefined) {
518
+ if (this.clearMobileSpinTimeout) {
519
+ this.clearMobileSpinTimeout();
520
+ }
521
+ return;
522
+ }
523
+ this.handleSpinLogic();
524
+ }, RainMan.config.durationOfActions.mobileSpinTimeout);
525
+ }, 250);
507
526
  });
508
527
  }
509
528
  /**
@@ -727,6 +746,9 @@ export class AbstractController {
727
746
  this.mainContainer.freeSpinSummary.emit("pointerdown", new PIXI.FederatedPointerEvent("pointerdown"));
728
747
  return;
729
748
  }
749
+ if (this.gamePhase === gamePhases.HANDLING_ACTIONS) {
750
+ this.shouldPlayNextSpin = true;
751
+ }
730
752
  if (this.mainContainer.mysteryWinVisibleFor) {
731
753
  RainMan.componentRegistry.get(COMPONENTS.mysteryWin).stopUpdate();
732
754
  }
@@ -786,12 +808,7 @@ export class AbstractController {
786
808
  this.quickStopController.handleInvokingSpeedPopup();
787
809
  return;
788
810
  }
789
- if (RainMan.settingsStore.isFreeSpinsBought) {
790
- this.uiController.totalNumberOfFreeSpins += this.getBoughtNumberOfFreeSpins();
791
- RainMan.componentRegistry.getIfExists(FreeSpinButton.registryName)?.setInteractivity(false);
792
- RainMan.settingsStore.setIsFreeSpinBought(false);
793
- return;
794
- }
811
+ this.prepareBoughtFreeSpins();
795
812
  if (RainMan.settingsStore.isAutoplayEnabled && !this.spinning) {
796
813
  this.changeGamePhase(gamePhases.IDLE);
797
814
  this.spin();
@@ -806,6 +823,14 @@ export class AbstractController {
806
823
  }
807
824
  this.spinThrottlerTimeout = setTimeout(() => (this.spinThrottlerTimeout = null), 100);
808
825
  }
826
+ prepareBoughtFreeSpins() {
827
+ if (!RainMan.settingsStore.isFreeSpinsBought) {
828
+ return;
829
+ }
830
+ this.uiController.totalNumberOfFreeSpins += this.getBoughtNumberOfFreeSpins();
831
+ RainMan.componentRegistry.getIfExists(FreeSpinButton.registryName)?.setInteractivity(false);
832
+ RainMan.settingsStore.setIsFreeSpinBought(false);
833
+ }
809
834
  /**
810
835
  * This function is responsible for getting the number of free spins bought
811
836
  * @private
@@ -866,6 +891,12 @@ export class AbstractController {
866
891
  logInfo("Take action can't be performed");
867
892
  return;
868
893
  }
894
+ if (this.currentlyPlayedAction !== undefined) {
895
+ this.changeGamePhase(gamePhases.STOPPING_HANDLING_ACTIONS);
896
+ this.stopPlayingSymbolsStreak();
897
+ await this.currentlyPlayedAction;
898
+ }
899
+ await Promise.all(this.uiController.promisesToSetBalances);
869
900
  const takeData = await window.connectionWrapper.sendTakeAction(RainMan.settingsStore.roundNumber);
870
901
  if (takeData.status.code === 200) {
871
902
  RainMan.settingsStore.isTakeActionAvailable = false;
@@ -981,14 +1012,10 @@ export class AbstractController {
981
1012
  */
982
1013
  gambleGameEnabler(winAmount) {
983
1014
  if (RainMan.componentRegistry.has(BUTTONS.gamble) && RainMan.settingsStore.isGambleGameAvailable) {
984
- RainMan.componentRegistry
985
- .get(BUTTONS.gamble)
986
- .flipDisabledFlag(RainMan.settingsStore.isFreeSpinsPlayEnabled || winAmount === 0);
1015
+ RainMan.componentRegistry.get(BUTTONS.gamble).flipDisabledFlag(winAmount === 0);
987
1016
  }
988
1017
  if (RainMan.componentRegistry.has(BUTTONS.take) && RainMan.settingsStore.isTakeActionAvailable) {
989
- RainMan.componentRegistry
990
- .get(BUTTONS.take)
991
- .flipDisabledFlag(RainMan.settingsStore.isFreeSpinsPlayEnabled || winAmount === 0);
1018
+ RainMan.componentRegistry.get(BUTTONS.take).flipDisabledFlag(winAmount === 0);
992
1019
  }
993
1020
  }
994
1021
  /**
@@ -1067,7 +1094,7 @@ export class AbstractController {
1067
1094
  const availableFreeSpins = spinLogic.availableFreeSpins;
1068
1095
  const availableFreeSpinsIncreased = availableFreeSpins > RainMan.settingsStore.howManyFreeSpinsLeft;
1069
1096
  this.hideFreeSpinsMessageBox = availableFreeSpinsIncreased;
1070
- if (this._lastWinAmount !== 0) {
1097
+ if (this._lastWinAmount !== 0 && !(availableFreeSpins > 0)) {
1071
1098
  RainMan.settingsStore.isTakeActionAvailable = true;
1072
1099
  RainMan.settingsStore.isGambleGameAvailable = true;
1073
1100
  }
@@ -1206,6 +1233,24 @@ export class AbstractController {
1206
1233
  }
1207
1234
  RainMan.settingsStore.setIsPlayingAnyAction(false);
1208
1235
  this.columnsContainer.setInteractivityForSymbols(true);
1236
+ const shouldStartNextSpin = this.shouldPlayNextSpin &&
1237
+ !RainMan.settingsStore.isAutoplayEnabled &&
1238
+ !RainMan.settingsStore.isFreeSpinsPlayEnabled &&
1239
+ !this.invokeFreeSpinPlateAfterWin &&
1240
+ !this.invokeFreeSpinSummaryPlateAfterWin &&
1241
+ !RainMan.globals.isSuperBonusGameEnabled;
1242
+ this.shouldPlayNextSpin = false;
1243
+ if (shouldStartNextSpin) {
1244
+ setTimeout(() => {
1245
+ if (!this.spinning &&
1246
+ this.gamePhase !== gamePhases.SPINNING &&
1247
+ this.gamePhase !== gamePhases.DATA_FETCH &&
1248
+ this.gamePhase !== gamePhases.CONFIGURING_SPIN &&
1249
+ !RainMan.settingsStore.isPlayingAnyAction) {
1250
+ this.throttledSpin();
1251
+ }
1252
+ }, 0);
1253
+ }
1209
1254
  }
1210
1255
  /**
1211
1256
  * Function for invoking free spin plate, invoked in mainContainer
@@ -1234,7 +1279,6 @@ export class AbstractController {
1234
1279
  */
1235
1280
  async handleFreeSpinSummaryPlateInvocation() {
1236
1281
  await this.buttonsEventManager.disableButtonsForAction(async () => {
1237
- this.uiController.updateDisplayedBalance(this.uiController.totalFreeSpinWinAmount);
1238
1282
  this.uiController.messageBox.hideFreeSpinText();
1239
1283
  this.uiController.messageBox.hideCurrentWinText();
1240
1284
  await this.mainContainer.invokeFreeSpinSummary(this.uiController.totalNumberOfFreeSpins, this.uiController.totalFreeSpinWinAmount);
@@ -1242,7 +1286,6 @@ export class AbstractController {
1242
1286
  this.uiController.resetTotalFreeSpinWinAmount();
1243
1287
  this.changeGamePhase(gamePhases.IDLE);
1244
1288
  });
1245
- this.uiController.currentBalance = RainMan.settingsStore.balanceAfterSpin;
1246
1289
  RainMan.settingsStore.resetTotalNumberOfFreeSpins();
1247
1290
  RainMan.settingsStore.resetFreeSpinNumberBought();
1248
1291
  this.changeGamePhase(gamePhases.IDLE);
@@ -1314,6 +1357,8 @@ export class AbstractController {
1314
1357
  stopPlayingSymbolsStreak() {
1315
1358
  this.columnsContainer.stopPlayingSymbolsInColumns();
1316
1359
  this.frame.stopPlayingWinLineAnimations();
1360
+ this.frame.hideMysteryFx();
1361
+ this.frame.destroyMysteryFx();
1317
1362
  }
1318
1363
  /**
1319
1364
  * Function for stopping playing symbols transformation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixi-rainman-game-engine",
3
- "version": "0.3.29",
3
+ "version": "0.3.31b",
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",