pixi-rainman-game-engine 0.3.28 → 0.3.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/Rainman/types.d.ts +1 -0
  2. package/dist/application/ButtonsEventManager/ButtonsEventManager.js +1 -1
  3. package/dist/components/AbstractBuyFreeSpins/AbstractBuyFreeSpinsPlate.d.ts +44 -0
  4. package/dist/components/AbstractBuyFreeSpins/AbstractBuyFreeSpinsPlate.js +184 -0
  5. package/dist/components/AbstractBuyFreeSpins/AcceptDeclineButton.d.ts +14 -0
  6. package/dist/components/AbstractBuyFreeSpins/AcceptDeclineButton.js +28 -0
  7. package/dist/components/AbstractBuyFreeSpins/BuyFreeSpinButton.d.ts +14 -0
  8. package/dist/components/AbstractBuyFreeSpins/BuyFreeSpinButton.js +22 -0
  9. package/dist/components/AbstractBuyFreeSpins/index.d.ts +3 -0
  10. package/dist/components/AbstractBuyFreeSpins/index.js +3 -0
  11. package/dist/components/AbstractIntroScreen/AbstractIntroScreen.d.ts +7 -1
  12. package/dist/components/AbstractIntroScreen/AbstractIntroScreen.js +34 -6
  13. package/dist/components/AbstractIntroScreen/IntroPageData.d.ts +8 -0
  14. package/dist/components/AbstractMainContainer/AbstractMainContainer.d.ts +5 -0
  15. package/dist/components/AbstractMainContainer/AbstractMainContainer.js +33 -2
  16. package/dist/components/GambleGame/GambleGame.js +1 -0
  17. package/dist/components/dictionary/locales/en.json +3 -0
  18. package/dist/components/frame/AbstractColumnsContainer.d.ts +25 -0
  19. package/dist/components/frame/AbstractColumnsContainer.js +67 -9
  20. package/dist/components/index.d.ts +1 -0
  21. package/dist/components/index.js +1 -0
  22. package/dist/controllers/AbstractController.d.ts +3 -1
  23. package/dist/controllers/AbstractController.js +50 -17
  24. package/dist/stores/SettingsStore.d.ts +8 -0
  25. package/dist/stores/SettingsStore.js +10 -0
  26. package/package.json +1 -1
@@ -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[];
@@ -209,7 +209,7 @@ export class ButtonsEventManager {
209
209
  initFreeSpinModal() {
210
210
  RainMan.componentRegistry
211
211
  .getIfExists(FreeSpinButton.registryName)
212
- ?.setOnClick(() => this.handleModalInvocation(UI_ITEMS.freeSpin));
212
+ ?.setOnClick(() => RainMan.settingsStore.handleBuyFreeSpinPopupActivation?.());
213
213
  }
214
214
  /**
215
215
  * Method for initializing volume button
@@ -0,0 +1,44 @@
1
+ import { Container, Sprite } from "pixi.js";
2
+ import { BuyFreeSpinOption } from "SettingsUI/hooks";
3
+ import { AcceptDeclineButton } from "./AcceptDeclineButton";
4
+ export type BuyFreeSpinConfig = {
5
+ options: BuyFreeSpinOption[];
6
+ };
7
+ export type BuyFreeSpinState = "selection" | "confirmation";
8
+ export declare abstract class AbstractBuyFreeSpinsPlate extends Container {
9
+ private state;
10
+ protected selectedOption?: BuyFreeSpinOption;
11
+ protected readonly config: BuyFreeSpinConfig;
12
+ protected selectionPlate?: Sprite;
13
+ protected closeButton?: AcceptDeclineButton;
14
+ protected confirmationPlate?: Sprite;
15
+ protected acceptButton?: AcceptDeclineButton;
16
+ protected declineButton?: AcceptDeclineButton;
17
+ protected selectionPlateTexture: string;
18
+ protected confirmationPlateTexture: string;
19
+ private readonly onContinueCallback;
20
+ private readonly resizeHandler;
21
+ private handleClose;
22
+ protected constructor(onContinue: () => void, config: BuyFreeSpinConfig);
23
+ init(): void;
24
+ private createElements;
25
+ resize(): void;
26
+ private showSelectionScreen;
27
+ private showConfirmationScreen;
28
+ protected abstract getPlateScale(): {
29
+ vertical: number;
30
+ horizontal: number;
31
+ };
32
+ protected abstract createSelectionContent(): void;
33
+ protected abstract createConfirmationContent(): void;
34
+ protected abstract showSelectionContent(): void;
35
+ protected abstract showConfirmationContent(option: BuyFreeSpinOption): void;
36
+ protected abstract positionSelectionScreen(): void;
37
+ protected abstract positionConfirmationScreen(): void;
38
+ protected onCleanup(): void;
39
+ protected onBuyButtonClick(option: BuyFreeSpinOption): void;
40
+ private onAcceptClick;
41
+ private renderState;
42
+ private onDeclineClick;
43
+ private cleanup;
44
+ }
@@ -0,0 +1,184 @@
1
+ import { Container, Sprite, Texture } from "pixi.js";
2
+ import { UXLayer } from "../../layers";
3
+ import { RainMan } from "../../Rainman";
4
+ import { scaleToParent } from "../../utils";
5
+ import { AcceptDeclineButton } from "./AcceptDeclineButton";
6
+ export class AbstractBuyFreeSpinsPlate extends Container {
7
+ state = "selection";
8
+ selectedOption;
9
+ config;
10
+ selectionPlate;
11
+ closeButton;
12
+ confirmationPlate;
13
+ acceptButton;
14
+ declineButton;
15
+ selectionPlateTexture = "selection-freespin-frame";
16
+ confirmationPlateTexture = "confirmation-freespin-frame";
17
+ onContinueCallback;
18
+ resizeHandler = () => this.resize();
19
+ handleClose = () => {
20
+ this.cleanup();
21
+ this.onContinueCallback();
22
+ RainMan.settingsStore.disableKeyboardShortcuts = false;
23
+ };
24
+ constructor(onContinue, config) {
25
+ super();
26
+ RainMan.settingsStore.disableKeyboardShortcuts = true;
27
+ this.onContinueCallback = onContinue;
28
+ this.config = config;
29
+ this.parentLayer = UXLayer;
30
+ this.eventMode = "static";
31
+ if (config.options.length === 1) {
32
+ this.state = "confirmation";
33
+ this.selectedOption = config.options[0];
34
+ }
35
+ }
36
+ init() {
37
+ this.createElements();
38
+ this.renderState();
39
+ RainMan.app.renderer.on("resize", this.resizeHandler);
40
+ this.resize();
41
+ }
42
+ createElements() {
43
+ const numOptions = this.config.options.length;
44
+ this.selectionPlate = new Sprite(Texture.from(this.selectionPlateTexture));
45
+ this.selectionPlate.anchor.set(0.5);
46
+ if (numOptions > 1) {
47
+ this.closeButton = new AcceptDeclineButton(false);
48
+ this.closeButton.setOnClick(() => this.handleClose());
49
+ this.closeButton.anchor.set(0.5);
50
+ }
51
+ this.confirmationPlate = new Sprite(Texture.from(this.confirmationPlateTexture));
52
+ this.confirmationPlate.anchor.set(0.5);
53
+ this.acceptButton = new AcceptDeclineButton(true);
54
+ this.acceptButton.setOnClick(() => this.onAcceptClick(this.selectedOption));
55
+ this.acceptButton.anchor.set(0.5);
56
+ this.declineButton = new AcceptDeclineButton(false);
57
+ this.declineButton.setOnClick(() => this.onDeclineClick());
58
+ this.declineButton.anchor.set(0.5);
59
+ this.createSelectionContent();
60
+ this.createConfirmationContent();
61
+ }
62
+ resize() {
63
+ const { width, height } = RainMan.app.screen;
64
+ if (this.state === "selection") {
65
+ if (!this.selectionPlate)
66
+ return;
67
+ const plateScale = this.getPlateScale();
68
+ const texW = this.selectionPlate.texture.width;
69
+ const texH = this.selectionPlate.texture.height;
70
+ const targetW = width * plateScale.horizontal;
71
+ const targetH = height * plateScale.vertical;
72
+ const dims = scaleToParent(texW, texH, targetW, targetH);
73
+ const scaleX = dims.width / texW;
74
+ const scaleY = dims.height / texH;
75
+ const scale = Math.min(scaleX, scaleY);
76
+ this.scale.set(scale);
77
+ this.position.set(width / 2, height / 2);
78
+ this.positionSelectionScreen();
79
+ return;
80
+ }
81
+ if (this.state === "confirmation") {
82
+ if (!this.confirmationPlate)
83
+ return;
84
+ const plateScale = this.getPlateScale();
85
+ const texW = this.confirmationPlate.texture.width;
86
+ const texH = this.confirmationPlate.texture.height;
87
+ const targetW = RainMan.app.screen.width * plateScale.horizontal;
88
+ const targetH = RainMan.app.screen.height * plateScale.vertical;
89
+ const dims = scaleToParent(texW, texH, targetW, targetH);
90
+ const scaleX = dims.width / texW;
91
+ const scaleY = dims.height / texH;
92
+ const scale = Math.min(scaleX, scaleY);
93
+ this.scale.set(scale);
94
+ this.position.set(width / 2, height / 2);
95
+ this.positionConfirmationScreen();
96
+ }
97
+ this.parentLayer = UXLayer;
98
+ }
99
+ showSelectionScreen() {
100
+ if (!this.selectionPlate)
101
+ return;
102
+ this.selectionPlate.visible = true;
103
+ this.selectionPlate.alpha = 1;
104
+ this.addChild(this.selectionPlate);
105
+ this.showSelectionContent();
106
+ if (this.closeButton) {
107
+ this.addChild(this.closeButton);
108
+ }
109
+ this.positionSelectionScreen();
110
+ }
111
+ showConfirmationScreen() {
112
+ if (!this.confirmationPlate)
113
+ return;
114
+ if (!this.selectedOption) {
115
+ this.selectedOption = this.config.options[0];
116
+ }
117
+ this.addChild(this.confirmationPlate);
118
+ this.showConfirmationContent(this.selectedOption);
119
+ if (this.acceptButton) {
120
+ this.addChild(this.acceptButton);
121
+ }
122
+ if (this.declineButton) {
123
+ this.addChild(this.declineButton);
124
+ }
125
+ this.positionConfirmationScreen();
126
+ }
127
+ onCleanup() { }
128
+ onBuyButtonClick(option) {
129
+ this.selectedOption = option;
130
+ this.state = "confirmation";
131
+ this.renderState();
132
+ this.resize();
133
+ }
134
+ async onAcceptClick(option) {
135
+ const roundNumber = RainMan.settingsStore.roundNumber + 1;
136
+ try {
137
+ const data = await window.connectionWrapper.fetchFreeSpinData(roundNumber, option.amount, option.price);
138
+ const code = data.getRawData()?.status?.code;
139
+ if (code === 400 || code === 406) {
140
+ return;
141
+ }
142
+ RainMan.settingsStore.setIsFreeSpinBought(true, data);
143
+ RainMan.settingsStore.setFreeSpinNumberBought(option.amount);
144
+ RainMan.globals.updateFreeSpinShown?.(option.amount);
145
+ RainMan.settingsStore.setRoundNumber(roundNumber);
146
+ this.cleanup();
147
+ this.onContinueCallback();
148
+ await RainMan.settingsStore.handleInvokeFreeSpinPlate?.(option.amount, false);
149
+ RainMan.settingsStore.disableKeyboardShortcuts = false;
150
+ RainMan.globals.prepareBoughtFreeSpins?.();
151
+ setTimeout(() => {
152
+ RainMan.globals.throttledSpin?.();
153
+ }, 500);
154
+ }
155
+ catch (err) {
156
+ console.error(err);
157
+ RainMan.settingsStore.setIsFreeSpinBought(false);
158
+ RainMan.settingsStore.disableKeyboardShortcuts = false;
159
+ }
160
+ }
161
+ renderState() {
162
+ this.removeChildren();
163
+ if (this.state === "selection") {
164
+ this.showSelectionScreen();
165
+ return;
166
+ }
167
+ this.showConfirmationScreen();
168
+ }
169
+ onDeclineClick() {
170
+ if (this.config.options.length === 1) {
171
+ this.handleClose();
172
+ return;
173
+ }
174
+ this.state = "selection";
175
+ this.selectedOption = undefined;
176
+ this.renderState();
177
+ this.resize();
178
+ }
179
+ cleanup() {
180
+ RainMan.app.renderer.off("resize", this.resizeHandler);
181
+ this.onCleanup();
182
+ this.destroy({ children: true });
183
+ }
184
+ }
@@ -0,0 +1,14 @@
1
+ import { BaseButton } from "../buttons/BaseButton";
2
+ /**
3
+ * Button for accepting or declining number of free spins to buy.
4
+ * It needs following textures:
5
+ * - normal - `v-n.png` for accept, `x-n.png` for decline
6
+ * - hover - `v-h.png` for accept, `x-h.png` for decline
7
+ * - clicked - `v-c.png` for accept, `x-c.png` for decline
8
+ * @param {boolean} isAcceptButton desides which texture to use
9
+ * @augments {BaseButton}
10
+ */
11
+ export declare class AcceptDeclineButton extends BaseButton {
12
+ static registryName: string;
13
+ constructor(isAcceptButton?: boolean);
14
+ }
@@ -0,0 +1,28 @@
1
+ import { getTexture } from "../../utils";
2
+ import { BaseButton, ButtonStates } from "../buttons/BaseButton";
3
+ /**
4
+ * Button for accepting or declining number of free spins to buy.
5
+ * It needs following textures:
6
+ * - normal - `v-n.png` for accept, `x-n.png` for decline
7
+ * - hover - `v-h.png` for accept, `x-h.png` for decline
8
+ * - clicked - `v-c.png` for accept, `x-c.png` for decline
9
+ * @param {boolean} isAcceptButton desides which texture to use
10
+ * @augments {BaseButton}
11
+ */
12
+ export class AcceptDeclineButton extends BaseButton {
13
+ static registryName = "acceptDeclineButton";
14
+ constructor(isAcceptButton) {
15
+ const textureMap = isAcceptButton
16
+ ? {
17
+ [ButtonStates.NORMAL]: getTexture("v-n.png"),
18
+ [ButtonStates.OVER]: getTexture("v-h.png"),
19
+ [ButtonStates.CLICKED]: getTexture("v-c.png")
20
+ }
21
+ : {
22
+ [ButtonStates.NORMAL]: getTexture("x-n.png"),
23
+ [ButtonStates.OVER]: getTexture("x-h.png"),
24
+ [ButtonStates.CLICKED]: getTexture("x-c.png")
25
+ };
26
+ super(AcceptDeclineButton.registryName, textureMap);
27
+ }
28
+ }
@@ -0,0 +1,14 @@
1
+ import { BaseButton } from "../buttons/BaseButton";
2
+ /**
3
+ * Button for selecting number of freespins to buy
4
+ * It needs following textures:
5
+ * - normal - `buttonTextureName-n.png`
6
+ * - hover - `buttonTextureName-h.png`
7
+ * - clicked - `buttonTextureName-c.png`
8
+ * @param {string} buttonTextureName name of a texture to use
9
+ * @augments {BaseButton}
10
+ */
11
+ export declare class BuyFreeSpinButton extends BaseButton {
12
+ static registryName: string;
13
+ constructor(buttonTextureName: string);
14
+ }
@@ -0,0 +1,22 @@
1
+ import { getTexture } from "../../utils";
2
+ import { BaseButton, ButtonStates } from "../buttons/BaseButton";
3
+ /**
4
+ * Button for selecting number of freespins to buy
5
+ * It needs following textures:
6
+ * - normal - `buttonTextureName-n.png`
7
+ * - hover - `buttonTextureName-h.png`
8
+ * - clicked - `buttonTextureName-c.png`
9
+ * @param {string} buttonTextureName name of a texture to use
10
+ * @augments {BaseButton}
11
+ */
12
+ export class BuyFreeSpinButton extends BaseButton {
13
+ static registryName = "buyFreeSpinButton";
14
+ constructor(buttonTextureName) {
15
+ const textureMap = {
16
+ [ButtonStates.NORMAL]: getTexture(`${buttonTextureName}-n.png`),
17
+ [ButtonStates.OVER]: getTexture(`${buttonTextureName}-h.png`),
18
+ [ButtonStates.CLICKED]: getTexture(`${buttonTextureName}-c.png`)
19
+ };
20
+ super(BuyFreeSpinButton.registryName, textureMap);
21
+ }
22
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./AbstractBuyFreeSpinsPlate";
2
+ export * from "./AcceptDeclineButton";
3
+ export * from "./BuyFreeSpinButton";
@@ -0,0 +1,3 @@
1
+ export * from "./AbstractBuyFreeSpinsPlate";
2
+ export * from "./AcceptDeclineButton";
3
+ export * from "./BuyFreeSpinButton";
@@ -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
  };
@@ -1,3 +1,4 @@
1
+ import { AbstractBuyFreeSpinsPlate } from "components/AbstractBuyFreeSpins/AbstractBuyFreeSpinsPlate";
1
2
  import { Container, Graphics, Sprite } from "pixi.js";
2
3
  import { ButtonsEventManager, WinTypeId } from "../../application";
3
4
  import { AbstractController } from "../../controllers";
@@ -54,6 +55,7 @@ export declare abstract class AbstractMainContainer extends Container {
54
55
  protected mysteryBonusContainer: Nullable<UpdatableSpineContainer>;
55
56
  protected resolveMysteryBonusContainer?: PromiseResolverType;
56
57
  protected _freeSpinPlate: Nullable<AbstractFreeSpinContainer>;
58
+ protected _buyFreeSpinsPlate: Nullable<AbstractBuyFreeSpinsPlate>;
57
59
  protected _freeSpinSummary: Nullable<AbstractFreeSpinContainer>;
58
60
  private _bigWinInvokedAt;
59
61
  private _mysteryWinInvokedAt;
@@ -173,6 +175,9 @@ export declare abstract class AbstractMainContainer extends Container {
173
175
  * @returns {Promise<void>}
174
176
  */
175
177
  invokeFreeSpinPlate(freeSpinCount: number, isAdditionalFreeSpin?: boolean, isMoneyReward?: boolean): Promise<void>;
178
+ invokeBuyFreeSpinPlate(): void;
179
+ protected closeBuyFreeSpinPlate(): void;
180
+ protected createBuyFreeSpinPlate(): AbstractBuyFreeSpinsPlate | null;
176
181
  /**
177
182
  * Function for creating free spin summary
178
183
  * @protected
@@ -47,6 +47,7 @@ export class AbstractMainContainer extends Container {
47
47
  mysteryBonusContainer = null;
48
48
  resolveMysteryBonusContainer = undefined;
49
49
  _freeSpinPlate = null;
50
+ _buyFreeSpinsPlate = null;
50
51
  _freeSpinSummary = null;
51
52
  _bigWinInvokedAt = null;
52
53
  _mysteryWinInvokedAt = null;
@@ -60,6 +61,7 @@ export class AbstractMainContainer extends Container {
60
61
  this.messageBox = new MessageBox();
61
62
  SoundManager.setPlayStatus(SoundTracks.music, RainMan.settingsStore.shouldPlayAmbientMusic);
62
63
  RainMan.settingsStore.setHandleInvokeFreeSpinPlate(this.invokeFreeSpinPlate.bind(this));
64
+ RainMan.settingsStore.setHandleBuyFreeSpinPopupActivatio(this.invokeBuyFreeSpinPlate.bind(this));
63
65
  RainMan.app.renderer.on("resize", () => this.resize());
64
66
  }
65
67
  /**
@@ -236,6 +238,30 @@ export class AbstractMainContainer extends Container {
236
238
  this.hideOverlay();
237
239
  refreshButton.flipDisabledFlag(false);
238
240
  }
241
+ invokeBuyFreeSpinPlate() {
242
+ const refreshButton = RainMan.componentRegistry.get(RefreshButton.registryName);
243
+ refreshButton.flipDisabledFlag(true);
244
+ this._buyFreeSpinsPlate = this.createBuyFreeSpinPlate();
245
+ if (!this._buyFreeSpinsPlate) {
246
+ refreshButton.flipDisabledFlag(false);
247
+ return;
248
+ }
249
+ this._buyFreeSpinsPlate.init();
250
+ this._buyFreeSpinsPlate.resize();
251
+ this.showOverlay();
252
+ this.addChild(this._buyFreeSpinsPlate);
253
+ }
254
+ closeBuyFreeSpinPlate() {
255
+ this.removeChild(this._buyFreeSpinsPlate);
256
+ this._buyFreeSpinsPlate.destroy({ children: true });
257
+ this._buyFreeSpinsPlate = null;
258
+ this.hideOverlay();
259
+ const refreshButton = RainMan.componentRegistry.get(RefreshButton.registryName);
260
+ refreshButton.flipDisabledFlag(false);
261
+ }
262
+ createBuyFreeSpinPlate() {
263
+ return null;
264
+ }
239
265
  /**
240
266
  * Function for creating free spin summary
241
267
  * @protected
@@ -523,7 +549,7 @@ export class AbstractMainContainer extends Container {
523
549
  async invokeGambleGame() {
524
550
  if (!this.gambleGameSpineName)
525
551
  return;
526
- const newBalance = await new Promise((resolve) => {
552
+ await new Promise((resolve) => {
527
553
  this.gambleGame = new GambleGame(this.gambleGameSpineName, resolve, this._controller.lastWinAmount, this._controller.balance);
528
554
  RainMan.settingsStore.isGambleGameActive = true;
529
555
  this.showOverlay();
@@ -535,7 +561,7 @@ export class AbstractMainContainer extends Container {
535
561
  this._controller.updateGambleValues(RainMan.settingsStore.gambleGameWin);
536
562
  this.gambleGame = null;
537
563
  }
538
- this._controller.balance = newBalance;
564
+ this._controller?.updateBalance();
539
565
  this.hideOverlay();
540
566
  }
541
567
  /**
@@ -884,6 +910,11 @@ export class AbstractMainContainer extends Container {
884
910
  this.addChild(this.freeSpinSummary);
885
911
  this.freeSpinSummary.resize();
886
912
  }
913
+ if (this._buyFreeSpinsPlate) {
914
+ this.removeChild(this._buyFreeSpinsPlate);
915
+ this.addChild(this._buyFreeSpinsPlate);
916
+ this._buyFreeSpinsPlate.resize();
917
+ }
887
918
  RainMan.settingsStore.popupPaytableRepositionCallback?.();
888
919
  }
889
920
  /**
@@ -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();
@@ -60,6 +60,9 @@
60
60
  "doubleTo": "DOUBLE TO",
61
61
  "collect": "COLLECT",
62
62
  "totalBet": "Bet",
63
+ "buyFreeSpinTitle": "BUY",
64
+ "freeSpinsAmount": "{{amount}}",
65
+ "freeSpinText": "FREE SPINS",
63
66
  "buyFreeSpins": "Buy Free Spins",
64
67
  "possibleWin": "POSSIBLE WIN: ",
65
68
  "gamePaysText": "GAME PAYS: "
@@ -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,12 @@ 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();
100
104
  this.resolveMysterySpeedUp();
101
- this.skipScatterDelay = null;
102
105
  if (this.currentColumn) {
103
106
  this.currentColumn.skipHidingMysteryFx = true;
104
107
  }
@@ -133,10 +136,15 @@ export class AbstractColumnsContainer extends Container {
133
136
  // spin
134
137
  return new Promise(async (resolve) => {
135
138
  let index = 0;
139
+ this.clearPendingMysteryDelay();
140
+ this.mysteryFxGeneration = 0;
141
+ this.skipRemainingMysteryFxs = false;
136
142
  const configuredBlindSpinsActions = this.prepareConfiguringReelsEnd(finalSymbolReels);
137
143
  const frame = RainMan.componentRegistry.get(AbstractFrame.registryName);
138
144
  while (configuredBlindSpinsActions.length !== 0) {
139
- if (this.quickReelsStop || RainMan.settingsStore.currentSpeed === SPEED_LEVELS.fast) {
145
+ if (this.quickReelsStop ||
146
+ this.skipRemainingMysteryFxs ||
147
+ RainMan.settingsStore.currentSpeed === SPEED_LEVELS.fast) {
140
148
  await Promise.all(configuredBlindSpinsActions.map(async (action) => await action()));
141
149
  configuredBlindSpinsActions.length = 0;
142
150
  SoundManager.play(SoundTracks.allRollStops);
@@ -150,16 +158,16 @@ export class AbstractColumnsContainer extends Container {
150
158
  frame.invokeMysteryFx(this.currentColumn.x, this.currentColumn.y);
151
159
  this.skipScatterDelay = configureSpinAction;
152
160
  await this.currentColumn.speedUpForMysterySpin(100, 1500);
153
- if (!this.quickReelsStop) {
161
+ if (this.quickReelsStop || this.skipRemainingMysteryFxs) {
162
+ await this.currentColumn.changeVelocity(Speed.STOP, 0, frame);
163
+ }
164
+ else {
154
165
  if (!RainMan.settingsStore.isAutoplayEnabled ||
155
166
  (RainMan.settingsStore.isAutoplayEnabled && !this.quickReelsStop)) {
156
- setTimeout(() => {
157
- this.skipScatterDelay?.();
158
- this.skipScatterDelay = null;
159
- }, RainMan.config.durationOfActions.scatterDelayerTime);
167
+ this.scheduleMysteryDelay(configureSpinAction, RainMan.config.durationOfActions.scatterDelayerTime);
160
168
  }
169
+ await this.currentColumn.changeVelocity(Speed.STOP, RainMan.config.durationOfActions.scatterSpinningTime, frame);
161
170
  }
162
- await this.currentColumn.changeVelocity(Speed.STOP, RainMan.config.durationOfActions.scatterSpinningTime, frame);
163
171
  frame.destroyMysteryFx();
164
172
  }
165
173
  if (!indexesOfReelsToExtendSpinningTime.includes(index)) {
@@ -170,10 +178,60 @@ export class AbstractColumnsContainer extends Container {
170
178
  this.currentColumn = null;
171
179
  }
172
180
  await Promise.all(this.allColumns.map((column) => column.getStopPromise()));
181
+ this.clearPendingMysteryDelay();
182
+ this.mysteryFxGeneration = 0;
183
+ this.skipRemainingMysteryFxs = false;
173
184
  afterSpinResolver();
174
185
  resolve();
175
186
  });
176
187
  }
188
+ /**
189
+ * This cancels the timeout, removes the stored delayed action and invalidates
190
+ * any older timeout callback that might still try to execute later.
191
+ * @private
192
+ * @returns {void}
193
+ */
194
+ clearPendingMysteryDelay() {
195
+ this.mysteryFxGeneration += 1;
196
+ if (this.mysteryDelayTimeout !== null) {
197
+ clearTimeout(this.mysteryDelayTimeout);
198
+ this.mysteryDelayTimeout = null;
199
+ }
200
+ this.skipScatterDelay = null;
201
+ }
202
+ /**
203
+ * Runs the pending mysteryFx immediately and clears its timeout.
204
+ * @private
205
+ * @returns {void}
206
+ */
207
+ executePendingMysteryFx() {
208
+ const pendingMysteryDelay = this.skipScatterDelay;
209
+ this.clearPendingMysteryDelay();
210
+ pendingMysteryDelay?.();
211
+ }
212
+ /**
213
+ * The callback is bound to the current generation so an old timeout from
214
+ * a previous spin cannot execute after a newer mystery delay was scheduled.
215
+ * @private
216
+ * @param {PlayableAction} action delayed reel configuration action
217
+ * @param {number} delay time in milliseconds before the action should run
218
+ * @returns {void}
219
+ */
220
+ scheduleMysteryDelay(action, delay) {
221
+ this.clearPendingMysteryDelay();
222
+ // Store new mysteryFx action to be available in executePendingMysteryFx
223
+ this.skipScatterDelay = action;
224
+ const mysteryFxGeneration = this.mysteryFxGeneration;
225
+ this.mysteryDelayTimeout = setTimeout(() => {
226
+ if (mysteryFxGeneration !== this.mysteryFxGeneration) {
227
+ return;
228
+ }
229
+ this.mysteryDelayTimeout = null;
230
+ this.skipScatterDelay = null;
231
+ this.mysteryFxGeneration += 1;
232
+ action();
233
+ }, delay);
234
+ }
177
235
  /**
178
236
  * Function for playing all streaks
179
237
  * @public
@@ -1,3 +1,4 @@
1
+ export * from "./AbstractBuyFreeSpins";
1
2
  export * from "./AbstractFreeSpinContainer";
2
3
  export * from "./AbstractIntroScreen";
3
4
  export * from "./AbstractMainContainer";
@@ -1,3 +1,4 @@
1
+ export * from "./AbstractBuyFreeSpins";
1
2
  export * from "./AbstractFreeSpinContainer";
2
3
  export * from "./AbstractIntroScreen";
3
4
  export * from "./AbstractMainContainer";
@@ -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");
@@ -429,12 +431,19 @@ export class AbstractController {
429
431
  const refreshButton = RainMan.componentRegistry.get(RefreshButton.registryName);
430
432
  this.clearMobileSpinTimeout = () => {
431
433
  this.buttonHeldSince = 0;
432
- this.columnsContainer.clearQuickReelsStop();
434
+ const isSpinning = this.gamePhase === gamePhases.SPINNING ||
435
+ this.gamePhase === gamePhases.CONFIGURING_SPIN ||
436
+ this.gamePhase === gamePhases.DATA_FETCH;
437
+ if (!isSpinning) {
438
+ this.columnsContainer.clearQuickReelsStop();
439
+ }
433
440
  if (this.mobileSpinTimeout) {
434
441
  clearInterval(this.mobileSpinTimeout);
435
442
  this.quickStopController.setQuickStopCounterLock(false);
436
443
  this.mobileSpinTimeout = null;
437
- this.columnsContainer.clearQuickReelsStop();
444
+ if (!isSpinning) {
445
+ this.columnsContainer.clearQuickReelsStop();
446
+ }
438
447
  }
439
448
  if (this.mobileSpinDelay) {
440
449
  clearTimeout(this.mobileSpinDelay);
@@ -720,6 +729,9 @@ export class AbstractController {
720
729
  this.mainContainer.freeSpinSummary.emit("pointerdown", new PIXI.FederatedPointerEvent("pointerdown"));
721
730
  return;
722
731
  }
732
+ if (this.gamePhase === gamePhases.HANDLING_ACTIONS) {
733
+ this.shouldPlayNextSpin = true;
734
+ }
723
735
  if (this.mainContainer.mysteryWinVisibleFor) {
724
736
  RainMan.componentRegistry.get(COMPONENTS.mysteryWin).stopUpdate();
725
737
  }
@@ -779,12 +791,7 @@ export class AbstractController {
779
791
  this.quickStopController.handleInvokingSpeedPopup();
780
792
  return;
781
793
  }
782
- if (RainMan.settingsStore.isFreeSpinsBought) {
783
- this.uiController.totalNumberOfFreeSpins += this.getBoughtNumberOfFreeSpins();
784
- RainMan.componentRegistry.getIfExists(FreeSpinButton.registryName)?.setInteractivity(false);
785
- RainMan.settingsStore.setIsFreeSpinBought(false);
786
- return;
787
- }
794
+ this.prepareBoughtFreeSpins();
788
795
  if (RainMan.settingsStore.isAutoplayEnabled && !this.spinning) {
789
796
  this.changeGamePhase(gamePhases.IDLE);
790
797
  this.spin();
@@ -799,6 +806,14 @@ export class AbstractController {
799
806
  }
800
807
  this.spinThrottlerTimeout = setTimeout(() => (this.spinThrottlerTimeout = null), 100);
801
808
  }
809
+ prepareBoughtFreeSpins() {
810
+ if (!RainMan.settingsStore.isFreeSpinsBought) {
811
+ return;
812
+ }
813
+ this.uiController.totalNumberOfFreeSpins += this.getBoughtNumberOfFreeSpins();
814
+ RainMan.componentRegistry.getIfExists(FreeSpinButton.registryName)?.setInteractivity(false);
815
+ RainMan.settingsStore.setIsFreeSpinBought(false);
816
+ }
802
817
  /**
803
818
  * This function is responsible for getting the number of free spins bought
804
819
  * @private
@@ -859,6 +874,12 @@ export class AbstractController {
859
874
  logInfo("Take action can't be performed");
860
875
  return;
861
876
  }
877
+ if (this.currentlyPlayedAction !== undefined) {
878
+ this.changeGamePhase(gamePhases.STOPPING_HANDLING_ACTIONS);
879
+ this.stopPlayingSymbolsStreak();
880
+ await this.currentlyPlayedAction;
881
+ }
882
+ await Promise.all(this.uiController.promisesToSetBalances);
862
883
  const takeData = await window.connectionWrapper.sendTakeAction(RainMan.settingsStore.roundNumber);
863
884
  if (takeData.status.code === 200) {
864
885
  RainMan.settingsStore.isTakeActionAvailable = false;
@@ -974,14 +995,10 @@ export class AbstractController {
974
995
  */
975
996
  gambleGameEnabler(winAmount) {
976
997
  if (RainMan.componentRegistry.has(BUTTONS.gamble) && RainMan.settingsStore.isGambleGameAvailable) {
977
- RainMan.componentRegistry
978
- .get(BUTTONS.gamble)
979
- .flipDisabledFlag(RainMan.settingsStore.isFreeSpinsPlayEnabled || winAmount === 0);
998
+ RainMan.componentRegistry.get(BUTTONS.gamble).flipDisabledFlag(winAmount === 0);
980
999
  }
981
1000
  if (RainMan.componentRegistry.has(BUTTONS.take) && RainMan.settingsStore.isTakeActionAvailable) {
982
- RainMan.componentRegistry
983
- .get(BUTTONS.take)
984
- .flipDisabledFlag(RainMan.settingsStore.isFreeSpinsPlayEnabled || winAmount === 0);
1001
+ RainMan.componentRegistry.get(BUTTONS.take).flipDisabledFlag(winAmount === 0);
985
1002
  }
986
1003
  }
987
1004
  /**
@@ -1060,7 +1077,7 @@ export class AbstractController {
1060
1077
  const availableFreeSpins = spinLogic.availableFreeSpins;
1061
1078
  const availableFreeSpinsIncreased = availableFreeSpins > RainMan.settingsStore.howManyFreeSpinsLeft;
1062
1079
  this.hideFreeSpinsMessageBox = availableFreeSpinsIncreased;
1063
- if (this._lastWinAmount !== 0) {
1080
+ if (this._lastWinAmount !== 0 && !(availableFreeSpins > 0)) {
1064
1081
  RainMan.settingsStore.isTakeActionAvailable = true;
1065
1082
  RainMan.settingsStore.isGambleGameAvailable = true;
1066
1083
  }
@@ -1199,6 +1216,24 @@ export class AbstractController {
1199
1216
  }
1200
1217
  RainMan.settingsStore.setIsPlayingAnyAction(false);
1201
1218
  this.columnsContainer.setInteractivityForSymbols(true);
1219
+ const shouldStartNextSpin = this.shouldPlayNextSpin &&
1220
+ !RainMan.settingsStore.isAutoplayEnabled &&
1221
+ !RainMan.settingsStore.isFreeSpinsPlayEnabled &&
1222
+ !this.invokeFreeSpinPlateAfterWin &&
1223
+ !this.invokeFreeSpinSummaryPlateAfterWin &&
1224
+ !RainMan.globals.isSuperBonusGameEnabled;
1225
+ this.shouldPlayNextSpin = false;
1226
+ if (shouldStartNextSpin) {
1227
+ setTimeout(() => {
1228
+ if (!this.spinning &&
1229
+ this.gamePhase !== gamePhases.SPINNING &&
1230
+ this.gamePhase !== gamePhases.DATA_FETCH &&
1231
+ this.gamePhase !== gamePhases.CONFIGURING_SPIN &&
1232
+ !RainMan.settingsStore.isPlayingAnyAction) {
1233
+ this.throttledSpin();
1234
+ }
1235
+ }, 0);
1236
+ }
1202
1237
  }
1203
1238
  /**
1204
1239
  * Function for invoking free spin plate, invoked in mainContainer
@@ -1227,7 +1262,6 @@ export class AbstractController {
1227
1262
  */
1228
1263
  async handleFreeSpinSummaryPlateInvocation() {
1229
1264
  await this.buttonsEventManager.disableButtonsForAction(async () => {
1230
- this.uiController.updateDisplayedBalance(this.uiController.totalFreeSpinWinAmount);
1231
1265
  this.uiController.messageBox.hideFreeSpinText();
1232
1266
  this.uiController.messageBox.hideCurrentWinText();
1233
1267
  await this.mainContainer.invokeFreeSpinSummary(this.uiController.totalNumberOfFreeSpins, this.uiController.totalFreeSpinWinAmount);
@@ -1235,7 +1269,6 @@ export class AbstractController {
1235
1269
  this.uiController.resetTotalFreeSpinWinAmount();
1236
1270
  this.changeGamePhase(gamePhases.IDLE);
1237
1271
  });
1238
- this.uiController.currentBalance = RainMan.settingsStore.balanceAfterSpin;
1239
1272
  RainMan.settingsStore.resetTotalNumberOfFreeSpins();
1240
1273
  RainMan.settingsStore.resetFreeSpinNumberBought();
1241
1274
  this.changeGamePhase(gamePhases.IDLE);
@@ -62,6 +62,7 @@ export declare class SettingsStore {
62
62
  closeOtherModals?: (layerId: string) => void;
63
63
  closeCurrentlyOpenModal?: () => void;
64
64
  handleInvokeFreeSpinPlate?: (freeSpinAmount: number, isAdditionalFreeSpin: boolean) => Promise<void>;
65
+ handleBuyFreeSpinPopupActivation?: () => void;
65
66
  increaseBet?: () => void;
66
67
  decreaseBet?: () => void;
67
68
  enableButtons?: () => void;
@@ -387,6 +388,13 @@ export declare class SettingsStore {
387
388
  * @returns {void}
388
389
  */
389
390
  setHandleInvokeFreeSpinPlate(handleInvokeFreeSpinPlate: (freeSpinAmount: number, isAdditionalFreeSpin: boolean) => Promise<void>): void;
391
+ /**
392
+ * Set the callback for invoking the free spin plate
393
+ * @param handleBuyFreeSpinPopupActivation
394
+ * @public
395
+ * @returns {void}
396
+ */
397
+ setHandleBuyFreeSpinPopupActivatio(handleBuyFreeSpinPopupActivation: () => void): void;
390
398
  /**
391
399
  * Reset the number of free spins bought
392
400
  * @public
@@ -66,6 +66,7 @@ export class SettingsStore {
66
66
  closeOtherModals;
67
67
  closeCurrentlyOpenModal;
68
68
  handleInvokeFreeSpinPlate;
69
+ handleBuyFreeSpinPopupActivation;
69
70
  increaseBet;
70
71
  decreaseBet;
71
72
  enableButtons;
@@ -641,6 +642,15 @@ export class SettingsStore {
641
642
  setHandleInvokeFreeSpinPlate(handleInvokeFreeSpinPlate) {
642
643
  this.handleInvokeFreeSpinPlate = handleInvokeFreeSpinPlate;
643
644
  }
645
+ /**
646
+ * Set the callback for invoking the free spin plate
647
+ * @param handleBuyFreeSpinPopupActivation
648
+ * @public
649
+ * @returns {void}
650
+ */
651
+ setHandleBuyFreeSpinPopupActivatio(handleBuyFreeSpinPopupActivation) {
652
+ this.handleBuyFreeSpinPopupActivation = handleBuyFreeSpinPopupActivation;
653
+ }
644
654
  /**
645
655
  * Reset the number of free spins bought
646
656
  * @public
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixi-rainman-game-engine",
3
- "version": "0.3.28",
3
+ "version": "0.3.30",
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",