pixi-rainman-game-engine 0.3.26 → 0.3.28

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/components/AbstractIntroScreen/AbstractIntroScreen.d.ts +75 -0
  2. package/dist/components/AbstractIntroScreen/AbstractIntroScreen.js +227 -0
  3. package/dist/components/AbstractIntroScreen/IntroContinueButton.d.ts +13 -0
  4. package/dist/components/AbstractIntroScreen/IntroContinueButton.js +22 -0
  5. package/dist/components/AbstractIntroScreen/IntroDotButton.d.ts +15 -0
  6. package/dist/components/AbstractIntroScreen/IntroDotButton.js +30 -0
  7. package/dist/components/AbstractIntroScreen/IntroPageData.d.ts +8 -0
  8. package/dist/components/AbstractIntroScreen/IntroPageData.js +1 -0
  9. package/dist/components/AbstractIntroScreen/index.d.ts +4 -0
  10. package/dist/components/AbstractIntroScreen/index.js +4 -0
  11. package/dist/components/common/IntroLogo.d.ts +16 -0
  12. package/dist/components/common/IntroLogo.js +35 -0
  13. package/dist/components/common/index.d.ts +1 -0
  14. package/dist/components/common/index.js +1 -0
  15. package/dist/components/frame/AbstractColumnsContainer.js +11 -7
  16. package/dist/components/frame/AbstractFrame.js +6 -2
  17. package/dist/components/index.d.ts +1 -0
  18. package/dist/components/index.js +1 -0
  19. package/dist/components/symbols/AbstractSymbolsColumn.js +6 -2
  20. package/dist/controllers/AbstractController.d.ts +3 -0
  21. package/dist/controllers/AbstractController.js +52 -25
  22. package/dist/localStorage/localStorage.d.ts +1 -0
  23. package/dist/localStorage/localStorage.js +2 -1
  24. package/dist/stores/SettingsStore.d.ts +1 -0
  25. package/dist/stores/SettingsStore.js +1 -0
  26. package/package.json +1 -1
@@ -0,0 +1,75 @@
1
+ import { Container, Sprite, Text, TextStyle, Texture } from "pixi.js";
2
+ import { IntroLogo } from "../common";
3
+ import { IntroContinueButton } from "./IntroContinueButton";
4
+ import { IntroDotButton } from "./IntroDotButton";
5
+ import { IntroPageData } from "./IntroPageData";
6
+ /**
7
+ * Abstract intro screen shown between loading and gameplay.
8
+ * Each game subclasses this and provides assets + page definitions as properties.
9
+ * Properly named assets:
10
+ * - bg - `bg.png` and `bg-mobile.png`
11
+ * - introFrame - `intro-frame.png` and `intro-frame-mobile.png`
12
+ * - interactable - 'intro-continue-n.png`, `intro-continue-h.png`, `intro-continue-c.png' for continue button, "dot-off" states, "dot-on", "check-box", "checkbox-tick" and `dot-shadow.png` for dot shadow (optional)
13
+ * - logo - `intro-logo.png` and `intro-logo-mobile.png`
14
+ * - screens-mobile and screens-desktop - defined in pages property of the class extending AbstractIntroScreen
15
+ * @abstract
16
+ * @class AbstractIntroScreen
17
+ * @typedef {AbstractIntroScreen}
18
+ * @augments {Container}
19
+ */
20
+ export declare abstract class AbstractIntroScreen extends Container {
21
+ static shouldSkip(): boolean;
22
+ protected abstract background: Sprite;
23
+ protected abstract backgroundPortrait: Sprite;
24
+ protected abstract checkboxLabelText: string;
25
+ protected abstract checkboxTextStyle: TextStyle;
26
+ protected abstract checkboxTextures: {
27
+ frame: Texture;
28
+ tick: Texture;
29
+ };
30
+ protected abstract logo: IntroLogo;
31
+ protected abstract pages: IntroPageData[];
32
+ protected abstract pageTextStyle: TextStyle;
33
+ protected checkboxLabel: Text;
34
+ protected checkboxSprite: Sprite;
35
+ protected checkboxTick: Sprite;
36
+ protected continueButton: IntroContinueButton;
37
+ protected dots: IntroDotButton[];
38
+ protected dotShadow?: Sprite;
39
+ protected frameImage: Sprite;
40
+ protected pageImage: Sprite;
41
+ protected pageText: Text;
42
+ private currentPage;
43
+ private isChecked;
44
+ private isAnimating;
45
+ private fadeTween?;
46
+ private readonly FADE_DURATION;
47
+ private readonly onContinueCallback;
48
+ private readonly resizeHandler;
49
+ protected constructor(onContinue: () => void);
50
+ init(): void;
51
+ resize(): void;
52
+ protected abstract positionContinueButton(): void;
53
+ protected abstract positionDots(): void;
54
+ protected abstract positionText(): void;
55
+ protected abstract positionCheckbox(): void;
56
+ protected abstract positionLogo(): void;
57
+ protected abstract getPageImageScale(): {
58
+ vertical: number;
59
+ horizontal: number;
60
+ };
61
+ protected abstract positionPageImage(): void;
62
+ protected showPage(index: number): void;
63
+ private updateFade;
64
+ private cleanup;
65
+ /**
66
+ * Resizes dot shadow width to match all dots and centers it on them.
67
+ * Call this in positionDots() after positioning the dots.
68
+ */
69
+ protected positionDotShadow(): void;
70
+ private createDotShadow;
71
+ private createDots;
72
+ private navigatePage;
73
+ private handleContinue;
74
+ private toggleCheckbox;
75
+ }
@@ -0,0 +1,227 @@
1
+ import * as TWEEN from "@tweenjs/tween.js";
2
+ import { Container, Sprite, Text, Texture, Ticker } from "pixi.js";
3
+ import { UXLayer } from "../../layers";
4
+ import { getFromLocalStorage, LOCAL_STORAGE, setToLocalStorage } from "../../localStorage";
5
+ import { RainMan } from "../../Rainman";
6
+ import { getDeviceOrientation, getTexture, getTextureSafely, scaleToParent } from "../../utils";
7
+ import { IntroContinueButton } from "./IntroContinueButton";
8
+ import { IntroDotButton } from "./IntroDotButton";
9
+ /**
10
+ * Abstract intro screen shown between loading and gameplay.
11
+ * Each game subclasses this and provides assets + page definitions as properties.
12
+ * Properly named assets:
13
+ * - bg - `bg.png` and `bg-mobile.png`
14
+ * - introFrame - `intro-frame.png` and `intro-frame-mobile.png`
15
+ * - interactable - 'intro-continue-n.png`, `intro-continue-h.png`, `intro-continue-c.png' for continue button, "dot-off" states, "dot-on", "check-box", "checkbox-tick" and `dot-shadow.png` for dot shadow (optional)
16
+ * - logo - `intro-logo.png` and `intro-logo-mobile.png`
17
+ * - screens-mobile and screens-desktop - defined in pages property of the class extending AbstractIntroScreen
18
+ * @abstract
19
+ * @class AbstractIntroScreen
20
+ * @typedef {AbstractIntroScreen}
21
+ * @augments {Container}
22
+ */
23
+ export class AbstractIntroScreen extends Container {
24
+ static shouldSkip() {
25
+ return getFromLocalStorage(LOCAL_STORAGE.introScreenHidden, false);
26
+ }
27
+ checkboxLabel;
28
+ checkboxSprite;
29
+ checkboxTick;
30
+ continueButton;
31
+ dots = [];
32
+ dotShadow;
33
+ frameImage;
34
+ pageImage;
35
+ pageText;
36
+ currentPage = 0;
37
+ isChecked = false;
38
+ isAnimating = false;
39
+ fadeTween;
40
+ FADE_DURATION = 200;
41
+ onContinueCallback;
42
+ resizeHandler = () => this.resize();
43
+ constructor(onContinue) {
44
+ super();
45
+ this.onContinueCallback = onContinue;
46
+ this.parentLayer = UXLayer;
47
+ this.eventMode = "static";
48
+ }
49
+ init() {
50
+ this.frameImage = new Sprite();
51
+ this.pageImage = new Sprite();
52
+ this.pageText = new Text("", this.pageTextStyle);
53
+ this.pageText.anchor.set(0.5);
54
+ this.continueButton = new IntroContinueButton();
55
+ this.continueButton.setOnClick(() => {
56
+ this.handleContinue();
57
+ });
58
+ this.createDots();
59
+ this.createDotShadow();
60
+ this.checkboxSprite = new Sprite(this.checkboxTextures.frame);
61
+ this.checkboxSprite.eventMode = "static";
62
+ this.checkboxSprite.cursor = "pointer";
63
+ this.checkboxSprite.anchor.set(0.5);
64
+ this.checkboxSprite.on("pointerdown", () => this.toggleCheckbox());
65
+ this.checkboxTick = new Sprite(this.checkboxTextures.tick);
66
+ this.checkboxTick.visible = false;
67
+ this.checkboxTick.eventMode = "static";
68
+ this.checkboxTick.cursor = "pointer";
69
+ this.checkboxTick.anchor.set(0.5);
70
+ this.checkboxTick.on("pointerdown", () => this.toggleCheckbox());
71
+ this.checkboxLabel = new Text(this.checkboxLabelText, this.checkboxTextStyle);
72
+ this.checkboxLabel.eventMode = "static";
73
+ this.checkboxLabel.cursor = "pointer";
74
+ this.checkboxLabel.anchor.set(0.5);
75
+ this.checkboxLabel.on("pointerdown", () => this.toggleCheckbox());
76
+ Ticker.shared.add(this.updateFade, this);
77
+ this.showPage(0);
78
+ RainMan.app.renderer.on("resize", this.resizeHandler);
79
+ this.resize();
80
+ }
81
+ resize() {
82
+ this.fadeTween?.stop();
83
+ this.fadeTween = undefined;
84
+ this.isAnimating = false;
85
+ const orientation = getDeviceOrientation();
86
+ const isPortrait = orientation === "mobile-portrait";
87
+ this.removeChildren();
88
+ const bg = isPortrait ? this.backgroundPortrait : this.background;
89
+ bg.width = RainMan.app.screen.width;
90
+ bg.height = RainMan.app.screen.height;
91
+ this.addChild(bg);
92
+ this.frameImage.texture = getTexture(isPortrait ? "intro-frame-mobile.png" : "intro-frame.png");
93
+ this.showPage(this.currentPage);
94
+ this.pageImage.alpha = 1;
95
+ this.pageText.alpha = 1;
96
+ const pageImageScale = this.getPageImageScale();
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
+ this.pageImage.width = dimensions.width;
99
+ this.pageImage.height = dimensions.height;
100
+ const scale = this.pageImage.scale.x;
101
+ this.continueButton.scale.set(scale);
102
+ this.pageText.scale.set(scale);
103
+ this.checkboxSprite.scale.set(scale);
104
+ this.checkboxTick.scale.set(scale);
105
+ this.checkboxLabel.scale.set(scale);
106
+ this.dots.forEach((dot) => dot.scale.set(scale));
107
+ this.pageImage.x = RainMan.app.screen.width / 2 - this.pageImage.width / 2;
108
+ this.pageImage.y = RainMan.app.screen.height / 2 - this.pageImage.height / 2;
109
+ this.addChild(this.frameImage);
110
+ this.addChild(this.frameImage);
111
+ this.addChild(this.pageImage);
112
+ this.addChild(this.pageText);
113
+ this.addChild(this.continueButton);
114
+ this.addChild(this.checkboxSprite);
115
+ this.addChild(this.checkboxTick);
116
+ this.addChild(this.checkboxLabel);
117
+ if (this.dotShadow)
118
+ this.addChild(this.dotShadow);
119
+ this.dots.forEach((dot) => this.addChild(dot));
120
+ this.positionPageImage();
121
+ this.positionLogo();
122
+ this.positionDots();
123
+ this.positionCheckbox();
124
+ this.positionContinueButton();
125
+ this.positionText();
126
+ this.positionDotShadow();
127
+ this.frameImage.x = this.pageImage.x;
128
+ this.frameImage.y = this.pageImage.y;
129
+ this.frameImage.width = this.pageImage.width;
130
+ this.frameImage.height = this.pageImage.height;
131
+ this.frameImage.x = this.pageImage.x;
132
+ this.frameImage.y = this.pageImage.y;
133
+ this.frameImage.width = this.pageImage.width;
134
+ this.frameImage.height = this.pageImage.height;
135
+ }
136
+ showPage(index) {
137
+ if (index < 0 || index >= this.pages.length)
138
+ return;
139
+ this.currentPage = index;
140
+ const page = this.pages[index];
141
+ const isPortrait = getDeviceOrientation() === "mobile-portrait";
142
+ this.pageImage.texture = Texture.from(isPortrait ? page.imageMobile : page.imageDesktop);
143
+ this.pageText.text = page.text;
144
+ for (let i = 0; i < this.dots.length; i++) {
145
+ this.dots[i].setActive(i === index);
146
+ }
147
+ }
148
+ updateFade() {
149
+ this.fadeTween?.update();
150
+ }
151
+ cleanup() {
152
+ Ticker.shared.remove(this.updateFade, this);
153
+ RainMan.app.renderer.off("resize", this.resizeHandler);
154
+ this.destroy({ children: true });
155
+ }
156
+ /**
157
+ * Resizes dot shadow width to match all dots and centers it on them.
158
+ * Call this in positionDots() after positioning the dots.
159
+ */
160
+ positionDotShadow() {
161
+ if (!this.dotShadow || this.dots.length === 0)
162
+ return;
163
+ const firstDot = this.dots[0];
164
+ const lastDot = this.dots[this.dots.length - 1];
165
+ const dotsSpan = lastDot.x - firstDot.x;
166
+ this.dotShadow.scale.set(firstDot.scale.x);
167
+ this.dotShadow.width = dotsSpan + firstDot.width;
168
+ if (getDeviceOrientation().includes("mobile")) {
169
+ this.dotShadow.width = dotsSpan;
170
+ }
171
+ this.dotShadow.x = (firstDot.x + lastDot.x) / 2;
172
+ this.dotShadow.y = firstDot.y;
173
+ }
174
+ createDotShadow() {
175
+ const texture = getTextureSafely("dot-shadow.png");
176
+ if (!texture)
177
+ return;
178
+ this.dotShadow = new Sprite(texture);
179
+ this.dotShadow.anchor.set(0.5);
180
+ }
181
+ createDots() {
182
+ this.dots = [];
183
+ for (let i = 0; i < this.pages.length; i++) {
184
+ const dot = new IntroDotButton(i);
185
+ dot.setOnClick(() => this.navigatePage(i));
186
+ dot.setOnClick(() => this.navigatePage(i));
187
+ this.dots.push(dot);
188
+ }
189
+ }
190
+ navigatePage(index) {
191
+ if (this.isAnimating || index === this.currentPage)
192
+ return;
193
+ this.isAnimating = true;
194
+ this.fadeTween?.stop();
195
+ this.fadeTween = new TWEEN.Tween({ alpha: this.pageImage.alpha })
196
+ .to({ alpha: 0 }, this.FADE_DURATION)
197
+ .onUpdate((obj) => {
198
+ this.pageImage.alpha = obj.alpha;
199
+ this.pageText.alpha = obj.alpha;
200
+ })
201
+ .onComplete(() => {
202
+ this.showPage(index);
203
+ this.fadeTween = new TWEEN.Tween({ alpha: 0 })
204
+ .to({ alpha: 1 }, this.FADE_DURATION)
205
+ .onUpdate((obj) => {
206
+ this.pageImage.alpha = obj.alpha;
207
+ this.pageText.alpha = obj.alpha;
208
+ })
209
+ .onComplete(() => {
210
+ this.isAnimating = false;
211
+ })
212
+ .start();
213
+ })
214
+ .start();
215
+ }
216
+ handleContinue() {
217
+ if (this.isChecked) {
218
+ setToLocalStorage(LOCAL_STORAGE.introScreenHidden, true);
219
+ }
220
+ this.cleanup();
221
+ this.onContinueCallback();
222
+ }
223
+ toggleCheckbox() {
224
+ this.isChecked = !this.isChecked;
225
+ this.checkboxTick.visible = this.isChecked;
226
+ }
227
+ }
@@ -0,0 +1,13 @@
1
+ import { BaseButton } from "../buttons/BaseButton";
2
+ /**
3
+ * Button for starting game after intro screen.
4
+ * It needs following textures:
5
+ * - normal - `intro-continue-n.png`
6
+ * - hover - `intro-continue-h.png`
7
+ * - clicked - `intro-continue-c.png`
8
+ * @augments {BaseButton}
9
+ */
10
+ export declare class IntroContinueButton extends BaseButton {
11
+ static registryName: string;
12
+ constructor();
13
+ }
@@ -0,0 +1,22 @@
1
+ import { getTexture } from "../../utils";
2
+ import { BaseButton, ButtonStates } from "../buttons/BaseButton";
3
+ /**
4
+ * Button for starting game after intro screen.
5
+ * It needs following textures:
6
+ * - normal - `intro-continue-n.png`
7
+ * - hover - `intro-continue-h.png`
8
+ * - clicked - `intro-continue-c.png`
9
+ * @augments {BaseButton}
10
+ */
11
+ export class IntroContinueButton extends BaseButton {
12
+ static registryName = "introContinueButton";
13
+ constructor() {
14
+ const textureMap = {
15
+ [ButtonStates.NORMAL]: getTexture("intro-continue-n.png"),
16
+ [ButtonStates.OVER]: getTexture("intro-continue-h.png"),
17
+ [ButtonStates.CLICKED]: getTexture("intro-continue-c.png")
18
+ };
19
+ super(IntroContinueButton.registryName, textureMap);
20
+ this.cursor = "pointer";
21
+ }
22
+ }
@@ -0,0 +1,15 @@
1
+ import { BaseButton } from "../buttons/BaseButton";
2
+ /**
3
+ * Dot button for intro screen page navigation.
4
+ * Textures:
5
+ * - normal - `dot-off-n.png`
6
+ * - hover - `dot-off-h.png`
7
+ * - clicked - `dot-off-c.png`
8
+ * - active (current page) - `dot-on.png`
9
+ * @augments {BaseButton}
10
+ */
11
+ export declare class IntroDotButton extends BaseButton {
12
+ static registryName: string;
13
+ constructor(index: number);
14
+ setActive(active: boolean): void;
15
+ }
@@ -0,0 +1,30 @@
1
+ import { getTexture } from "../../utils";
2
+ import { BaseButton, ButtonStates } from "../buttons/BaseButton";
3
+ /**
4
+ * Dot button for intro screen page navigation.
5
+ * Textures:
6
+ * - normal - `dot-off-n.png`
7
+ * - hover - `dot-off-h.png`
8
+ * - clicked - `dot-off-c.png`
9
+ * - active (current page) - `dot-on.png`
10
+ * @augments {BaseButton}
11
+ */
12
+ export class IntroDotButton extends BaseButton {
13
+ static registryName = "introDotButton";
14
+ constructor(index) {
15
+ const textureMap = {
16
+ [ButtonStates.NORMAL]: getTexture("dot-off-n.png"),
17
+ [ButtonStates.OVER]: getTexture("dot-off-h.png"),
18
+ [ButtonStates.CLICKED]: getTexture("dot-off-c.png"),
19
+ [ButtonStates.ACTIVE]: getTexture("dot-on.png"),
20
+ [ButtonStates.ACTIVE_OVER]: getTexture("dot-on.png"),
21
+ [ButtonStates.ACTIVE_CLICKED]: getTexture("dot-on.png")
22
+ };
23
+ super(`${IntroDotButton.registryName}-${index}`, textureMap);
24
+ this.anchor.set(0.5);
25
+ this.cursor = "pointer";
26
+ }
27
+ setActive(active) {
28
+ this.activate(active);
29
+ }
30
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Data for a single page in the intro screen.
3
+ */
4
+ export type IntroPageData = {
5
+ imageDesktop: string;
6
+ imageMobile: string;
7
+ text: string;
8
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./AbstractIntroScreen";
2
+ export * from "./IntroContinueButton";
3
+ export * from "./IntroDotButton";
4
+ export * from "./IntroPageData";
@@ -0,0 +1,4 @@
1
+ export * from "./AbstractIntroScreen";
2
+ export * from "./IntroContinueButton";
3
+ export * from "./IntroDotButton";
4
+ export * from "./IntroPageData";
@@ -0,0 +1,16 @@
1
+ import { Container } from "pixi.js";
2
+ /**
3
+ * Static intro logo that switches between desktop and mobile sprites based on orientation.
4
+ * Uses "intro-logo" for PC/landscape and "intro-logo-mobile" for portrait.
5
+ * @class IntroLogo
6
+ * @augments {Container}
7
+ */
8
+ export declare class IntroLogo extends Container {
9
+ private desktopTextureName;
10
+ private mobileTextureName;
11
+ private maxScale;
12
+ private desktopSprite;
13
+ private mobileSprite;
14
+ constructor(desktopTextureName: string, mobileTextureName: string, maxScale?: number);
15
+ resize(): void;
16
+ }
@@ -0,0 +1,35 @@
1
+ import { Container, Sprite } from "pixi.js";
2
+ import { RainMan } from "../../Rainman";
3
+ import { getDeviceOrientation, getTexture, globalMinRatio } from "../../utils";
4
+ /**
5
+ * Static intro logo that switches between desktop and mobile sprites based on orientation.
6
+ * Uses "intro-logo" for PC/landscape and "intro-logo-mobile" for portrait.
7
+ * @class IntroLogo
8
+ * @augments {Container}
9
+ */
10
+ export class IntroLogo extends Container {
11
+ desktopTextureName;
12
+ mobileTextureName;
13
+ maxScale;
14
+ desktopSprite;
15
+ mobileSprite;
16
+ constructor(desktopTextureName, mobileTextureName, maxScale = 1.5) {
17
+ super();
18
+ this.desktopTextureName = desktopTextureName;
19
+ this.mobileTextureName = mobileTextureName;
20
+ this.maxScale = maxScale;
21
+ this.desktopSprite = new Sprite(getTexture(this.desktopTextureName));
22
+ this.desktopSprite.anchor.set(0.5);
23
+ this.mobileSprite = new Sprite(getTexture(this.mobileTextureName));
24
+ this.mobileSprite.anchor.set(0.5);
25
+ this.addChild(this.desktopSprite, this.mobileSprite);
26
+ this.resize();
27
+ }
28
+ resize() {
29
+ const isPortrait = getDeviceOrientation() === "mobile-portrait";
30
+ this.desktopSprite.visible = !isPortrait;
31
+ this.mobileSprite.visible = isPortrait;
32
+ this.scale.set(Math.min(this.maxScale, globalMinRatio()));
33
+ this.x = RainMan.app.screen.width / 2;
34
+ }
35
+ }
@@ -2,6 +2,7 @@ export * from "./AnimatedBackground";
2
2
  export * from "./AnimatedNumber";
3
3
  export * from "./Background";
4
4
  export * from "./FreeSpinButton";
5
+ export * from "./IntroLogo";
5
6
  export * from "./Logo";
6
7
  export * from "./LogoStatic";
7
8
  export * from "./ResponsiveComponent";
@@ -2,6 +2,7 @@ export * from "./AnimatedBackground";
2
2
  export * from "./AnimatedNumber";
3
3
  export * from "./Background";
4
4
  export * from "./FreeSpinButton";
5
+ export * from "./IntroLogo";
5
6
  export * from "./Logo";
6
7
  export * from "./LogoStatic";
7
8
  export * from "./ResponsiveComponent";
@@ -93,6 +93,7 @@ export class AbstractColumnsContainer extends Container {
93
93
  */
94
94
  enableQuickReelsStop() {
95
95
  this.quickReelsStop = true;
96
+ RainMan.settingsStore.isQuickSpinsActive = true;
96
97
  RainMan.settingsStore.setIsTemporarySpeed(true);
97
98
  RainMan.componentRegistry.setTemporarySpeedLevel(SPEED_LEVELS.fast);
98
99
  this.skipScatterDelay?.();
@@ -109,6 +110,7 @@ export class AbstractColumnsContainer extends Container {
109
110
  */
110
111
  clearQuickReelsStop() {
111
112
  this.quickReelsStop = false;
113
+ RainMan.settingsStore.isQuickSpinsActive = false;
112
114
  RainMan.componentRegistry.revertTemporarySpeedLevel();
113
115
  }
114
116
  /**
@@ -138,7 +140,6 @@ export class AbstractColumnsContainer extends Container {
138
140
  await Promise.all(configuredBlindSpinsActions.map(async (action) => await action()));
139
141
  configuredBlindSpinsActions.length = 0;
140
142
  SoundManager.play(SoundTracks.allRollStops);
141
- this.clearQuickReelsStop();
142
143
  break;
143
144
  }
144
145
  const configureSpinAction = configuredBlindSpinsActions.shift();
@@ -149,14 +150,17 @@ export class AbstractColumnsContainer extends Container {
149
150
  frame.invokeMysteryFx(this.currentColumn.x, this.currentColumn.y);
150
151
  this.skipScatterDelay = configureSpinAction;
151
152
  await this.currentColumn.speedUpForMysterySpin(100, 1500);
152
- if (!RainMan.settingsStore.isAutoplayEnabled ||
153
- (RainMan.settingsStore.isAutoplayEnabled && !this.quickReelsStop)) {
154
- setTimeout(() => {
155
- this.skipScatterDelay?.();
156
- this.skipScatterDelay = null;
157
- }, RainMan.config.durationOfActions.scatterDelayerTime);
153
+ if (!this.quickReelsStop) {
154
+ if (!RainMan.settingsStore.isAutoplayEnabled ||
155
+ (RainMan.settingsStore.isAutoplayEnabled && !this.quickReelsStop)) {
156
+ setTimeout(() => {
157
+ this.skipScatterDelay?.();
158
+ this.skipScatterDelay = null;
159
+ }, RainMan.config.durationOfActions.scatterDelayerTime);
160
+ }
158
161
  }
159
162
  await this.currentColumn.changeVelocity(Speed.STOP, RainMan.config.durationOfActions.scatterSpinningTime, frame);
163
+ frame.destroyMysteryFx();
160
164
  }
161
165
  if (!indexesOfReelsToExtendSpinningTime.includes(index)) {
162
166
  await configureSpinAction();
@@ -59,6 +59,8 @@ export class AbstractFrame extends ResumableContainer {
59
59
  if (this.mysteryFxHighlight) {
60
60
  SoundManager.pause(SoundTracks.mysteryFxLoop);
61
61
  this.removeChild(this.mysteryFxHighlight);
62
+ this.mysteryFxHighlight.destroy();
63
+ this.mysteryFxHighlight = null;
62
64
  }
63
65
  this.mysteryFxCoordinates = { x, y };
64
66
  const spineName = this.mysteryFxConfig.mobileSpineName && getDeviceOrientation() === "mobile-portrait"
@@ -71,8 +73,10 @@ export class AbstractFrame extends ResumableContainer {
71
73
  this.repositionMysteryFx(x, y);
72
74
  SoundManager.play(SoundTracks.mysteryFxLoop);
73
75
  this.mysteryFxHighlight.state.setAnimation(0, this.mysteryFxConfig.startAnimationName || this.mysteryFxConfig.loopAnimationName, false);
74
- this.mysteryFxHighlight.state.addListener({
75
- complete: () => this.mysteryFxHighlight?.state.setAnimation(0, this.mysteryFxConfig.loopAnimationName, true)
76
+ const spineInstance = this.mysteryFxHighlight;
77
+ const loopAnimationName = this.mysteryFxConfig.loopAnimationName;
78
+ spineInstance.state.addListener({
79
+ complete: () => spineInstance.state.setAnimation(0, loopAnimationName, true)
76
80
  });
77
81
  this.addChild(this.mysteryFxHighlight);
78
82
  }
@@ -1,4 +1,5 @@
1
1
  export * from "./AbstractFreeSpinContainer";
2
+ export * from "./AbstractIntroScreen";
2
3
  export * from "./AbstractMainContainer";
3
4
  export * from "./buttons";
4
5
  export * from "./common";
@@ -1,4 +1,5 @@
1
1
  export * from "./AbstractFreeSpinContainer";
2
+ export * from "./AbstractIntroScreen";
2
3
  export * from "./AbstractMainContainer";
3
4
  export * from "./buttons";
4
5
  export * from "./common";
@@ -142,6 +142,10 @@ export class AbstractSymbolsColumn extends Container {
142
142
  * @returns {Promise<void>} promise that resolves when the speed change is complete
143
143
  */
144
144
  async changeVelocity(velocity, duration, frame) {
145
+ if (this.tween !== null) {
146
+ this.tween.stop();
147
+ this.tween = null;
148
+ }
145
149
  await new Promise((resolve) => {
146
150
  this.tween = new TWEEN.Tween({ velocity: velocity })
147
151
  .onUpdate((value) => {
@@ -152,7 +156,7 @@ export class AbstractSymbolsColumn extends Container {
152
156
  .start()
153
157
  .onComplete(async () => {
154
158
  this.tween = null;
155
- if (frame) {
159
+ if (frame && !this.skipHidingMysteryFx) {
156
160
  frame.hideMysteryFx();
157
161
  frame.destroyMysteryFx();
158
162
  }
@@ -161,7 +165,7 @@ export class AbstractSymbolsColumn extends Container {
161
165
  })
162
166
  .onStop(async () => {
163
167
  this.tween = null;
164
- if (frame) {
168
+ if (frame && !this.skipHidingMysteryFx) {
165
169
  frame.hideMysteryFx();
166
170
  frame.destroyMysteryFx();
167
171
  }
@@ -53,6 +53,9 @@ export declare abstract class AbstractController<T> {
53
53
  protected shouldDisableBetButtons: boolean;
54
54
  protected wasAutoplayEnabledBeforeFreeSpin: boolean;
55
55
  protected setSlowerSpeedForFreeSpins: boolean;
56
+ protected quickReelsCounter: number;
57
+ protected buttonPressed: boolean;
58
+ protected buttonHeldSince: number;
56
59
  shouldStopMobileSpin: boolean;
57
60
  disableMessageBoxChange: boolean;
58
61
  protected resolveBigWin?: () => void;
@@ -53,6 +53,9 @@ export class AbstractController {
53
53
  shouldDisableBetButtons = false;
54
54
  wasAutoplayEnabledBeforeFreeSpin = false;
55
55
  setSlowerSpeedForFreeSpins = false;
56
+ quickReelsCounter = 0;
57
+ buttonPressed = false;
58
+ buttonHeldSince = 0;
56
59
  shouldStopMobileSpin = false;
57
60
  disableMessageBoxChange = false;
58
61
  resolveBigWin;
@@ -164,6 +167,13 @@ export class AbstractController {
164
167
  window.addEventListener("keydown", (keyboardEvent) => {
165
168
  if (keyboardEvent.code === "Space" || keyboardEvent.code === "Enter") {
166
169
  keyboardEvent.preventDefault();
170
+ if (this.buttonHeldSince === 0) {
171
+ this.buttonHeldSince = Date.now();
172
+ }
173
+ const buttonHold = Date.now() - this.buttonHeldSince;
174
+ if (buttonHold >= 500) {
175
+ this.columnsContainer.enableQuickReelsStop();
176
+ }
167
177
  if (RainMan.settingsStore.isGambleGameActive || RainMan.settingsStore.isSpeedModalShown) {
168
178
  return;
169
179
  }
@@ -174,6 +184,17 @@ export class AbstractController {
174
184
  this.handleSpinLogic();
175
185
  }
176
186
  });
187
+ window.addEventListener("keyup", (keyboardEvent) => {
188
+ if (keyboardEvent.code === "Space" || keyboardEvent.code === "Enter") {
189
+ keyboardEvent.preventDefault();
190
+ this.buttonHeldSince = 0;
191
+ this.columnsContainer.clearQuickReelsStop();
192
+ }
193
+ });
194
+ window.addEventListener("blur", () => {
195
+ this.buttonHeldSince = 0;
196
+ this.columnsContainer.clearQuickReelsStop();
197
+ });
177
198
  }
178
199
  /**
179
200
  * This function sets up a listener for resize event
@@ -359,7 +380,7 @@ export class AbstractController {
359
380
  if (availableFreeSpins > 0) {
360
381
  this._lastWinAmount = this.initData.round_win_amount;
361
382
  }
362
- if (spinLogic.winTotalAmount !== 0 && spinLogic.winTotalAmount) {
383
+ if (spinLogic.winTotalAmount !== 0 && spinLogic.winTotalAmount !== undefined) {
363
384
  this.uiController.recentWin = ceilToDecimal(spinLogic.winTotalAmount);
364
385
  this.uiController.totalFreeSpinWinAmount = this._lastWinAmount;
365
386
  }
@@ -407,6 +428,8 @@ export class AbstractController {
407
428
  initSpinButton() {
408
429
  const refreshButton = RainMan.componentRegistry.get(RefreshButton.registryName);
409
430
  this.clearMobileSpinTimeout = () => {
431
+ this.buttonHeldSince = 0;
432
+ this.columnsContainer.clearQuickReelsStop();
410
433
  if (this.mobileSpinTimeout) {
411
434
  clearInterval(this.mobileSpinTimeout);
412
435
  this.quickStopController.setQuickStopCounterLock(false);
@@ -450,25 +473,30 @@ export class AbstractController {
450
473
  this.clearMobileSpinTimeout();
451
474
  });
452
475
  refreshButton.on("touchstart", () => {
453
- this.mobileSpinDelay = setTimeout(() => {
454
- this.mobileSpinTimeout = setInterval(() => {
455
- this.handleDisablingButtons(true);
456
- this.quickStopController.setQuickStopCounterLock(true);
457
- this.summaryStop = this.skipFreeSpinSummary ? false : this.invokeFreeSpinSummaryPlateAfterWin;
458
- if (this.invokeFreeSpinPlateAfterWin ||
459
- this.summaryStop ||
460
- this.shouldStopMobileSpin ||
461
- this.resolveBigWin !== undefined ||
462
- this.resolveSuperBonusWin !== undefined ||
463
- this.resolveMysteryWin !== undefined) {
464
- if (this.clearMobileSpinTimeout) {
465
- this.clearMobileSpinTimeout();
466
- }
467
- return;
476
+ if (this.buttonHeldSince === 0) {
477
+ this.buttonHeldSince = Date.now();
478
+ }
479
+ this.mobileSpinTimeout = setInterval(() => {
480
+ const buttonHold = Date.now() - this.buttonHeldSince;
481
+ if (buttonHold >= 500) {
482
+ this.columnsContainer.enableQuickReelsStop();
483
+ }
484
+ this.handleDisablingButtons(true);
485
+ this.quickStopController.setQuickStopCounterLock(true);
486
+ this.summaryStop = this.skipFreeSpinSummary ? false : this.invokeFreeSpinSummaryPlateAfterWin;
487
+ if (this.invokeFreeSpinPlateAfterWin ||
488
+ this.summaryStop ||
489
+ this.shouldStopMobileSpin ||
490
+ this.resolveBigWin !== undefined ||
491
+ this.resolveSuperBonusWin !== undefined ||
492
+ this.resolveMysteryWin !== undefined) {
493
+ if (this.clearMobileSpinTimeout) {
494
+ this.clearMobileSpinTimeout();
468
495
  }
469
- this.handleSpinLogic();
470
- }, RainMan.config.durationOfActions.mobileSpinTimeout);
471
- }, 500);
496
+ return;
497
+ }
498
+ this.handleSpinLogic();
499
+ }, RainMan.config.durationOfActions.mobileSpinTimeout);
472
500
  });
473
501
  }
474
502
  /**
@@ -672,7 +700,10 @@ export class AbstractController {
672
700
  this.handleDisablingButtons(true);
673
701
  RainMan.globals.shouldShowModals = false;
674
702
  RainMan.settingsStore.closeCurrentlyOpenModal?.();
675
- this.columnsContainer.clearQuickReelsStop();
703
+ const realTime = Date.now() - this.buttonHeldSince;
704
+ if (realTime <= 500) {
705
+ this.columnsContainer.clearQuickReelsStop();
706
+ }
676
707
  if (this.mainContainer.freeSpinPlate) {
677
708
  if (this.mainContainer.freeSpinPlate.shownAt === null ||
678
709
  performance.now() - this.mainContainer.freeSpinPlate.shownAt <
@@ -729,6 +760,7 @@ export class AbstractController {
729
760
  * @returns {void}
730
761
  */
731
762
  throttledSpin() {
763
+ this.quickReelsCounter += 1;
732
764
  this.columnsContainer.setInteractivityForSymbols(false);
733
765
  this.handleDisablingButtons(true);
734
766
  RainMan.globals.shouldShowModals = false;
@@ -762,10 +794,6 @@ export class AbstractController {
762
794
  this.mainContainer.destroyBigWin();
763
795
  this.changeGamePhase(gamePhases.IDLE);
764
796
  }
765
- if (this.spinThrottlerTimeout !== null && !RainMan.settingsStore.isFreeSpinsPlayEnabled) {
766
- this.columnsContainer.clearQuickReelsStop();
767
- return;
768
- }
769
797
  if (this.gamePhase === gamePhases.IDLE || this.gamePhase === gamePhases.HANDLING_ACTIONS) {
770
798
  this.spin();
771
799
  }
@@ -1025,7 +1053,6 @@ export class AbstractController {
1025
1053
  await this.onSpinningStart();
1026
1054
  hideVolumeModal();
1027
1055
  RainMan.settingsStore.disableSpecialButtons();
1028
- this.columnsContainer.clearQuickReelsStop();
1029
1056
  this.columnsContainer.blindSpin();
1030
1057
  this.clearAndDestroyActions();
1031
1058
  const { spinLogic } = await this.fetchAndPrepareSpinData();
@@ -9,6 +9,7 @@ export declare const LOCAL_STORAGE: {
9
9
  readonly isSoundEnabled: "isSoundEnabledLocalStorage";
10
10
  readonly isSoundFxEnabled: "isSoundFxEnabledLocalStorage";
11
11
  readonly isAmbientMusicEnabled: "isAmbientMusicEnabledLocalStorage";
12
+ readonly introScreenHidden: "introScreenHiddenLocalStorage";
12
13
  };
13
14
  /**
14
15
  * Type for local storage keys
@@ -8,7 +8,8 @@ export const LOCAL_STORAGE = {
8
8
  volumeLevel: "volumeLevelLocalStorage",
9
9
  isSoundEnabled: "isSoundEnabledLocalStorage",
10
10
  isSoundFxEnabled: "isSoundFxEnabledLocalStorage",
11
- isAmbientMusicEnabled: "isAmbientMusicEnabledLocalStorage"
11
+ isAmbientMusicEnabled: "isAmbientMusicEnabledLocalStorage",
12
+ introScreenHidden: "introScreenHiddenLocalStorage"
12
13
  };
13
14
  /**
14
15
  * Get a value from local storage
@@ -12,6 +12,7 @@ export declare class SettingsStore {
12
12
  static instance: SettingsStore | null;
13
13
  betChangeDisabled: boolean;
14
14
  disableKeyboardShortcuts: boolean;
15
+ isQuickSpinsActive: boolean;
15
16
  bet: number;
16
17
  betText: string;
17
18
  shouldPlayAmbientMusic: boolean;
@@ -16,6 +16,7 @@ export class SettingsStore {
16
16
  static instance = null;
17
17
  betChangeDisabled = false;
18
18
  disableKeyboardShortcuts = false;
19
+ isQuickSpinsActive = false;
19
20
  bet = 0;
20
21
  betText = "";
21
22
  shouldPlayAmbientMusic = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixi-rainman-game-engine",
3
- "version": "0.3.26",
3
+ "version": "0.3.28",
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",