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.
- package/dist/components/AbstractIntroScreen/AbstractIntroScreen.d.ts +75 -0
- package/dist/components/AbstractIntroScreen/AbstractIntroScreen.js +227 -0
- package/dist/components/AbstractIntroScreen/IntroContinueButton.d.ts +13 -0
- package/dist/components/AbstractIntroScreen/IntroContinueButton.js +22 -0
- package/dist/components/AbstractIntroScreen/IntroDotButton.d.ts +15 -0
- package/dist/components/AbstractIntroScreen/IntroDotButton.js +30 -0
- package/dist/components/AbstractIntroScreen/IntroPageData.d.ts +8 -0
- package/dist/components/AbstractIntroScreen/IntroPageData.js +1 -0
- package/dist/components/AbstractIntroScreen/index.d.ts +4 -0
- package/dist/components/AbstractIntroScreen/index.js +4 -0
- package/dist/components/common/IntroLogo.d.ts +16 -0
- package/dist/components/common/IntroLogo.js +35 -0
- package/dist/components/common/index.d.ts +1 -0
- package/dist/components/common/index.js +1 -0
- package/dist/components/frame/AbstractColumnsContainer.js +11 -7
- package/dist/components/frame/AbstractFrame.js +6 -2
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/symbols/AbstractSymbolsColumn.js +6 -2
- package/dist/controllers/AbstractController.d.ts +3 -0
- package/dist/controllers/AbstractController.js +52 -25
- package/dist/localStorage/localStorage.d.ts +1 -0
- package/dist/localStorage/localStorage.js +2 -1
- package/dist/stores/SettingsStore.d.ts +1 -0
- package/dist/stores/SettingsStore.js +1 -0
- 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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 (!
|
|
153
|
-
(RainMan.settingsStore.isAutoplayEnabled
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
75
|
-
|
|
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
|
}
|
package/dist/components/index.js
CHANGED
|
@@ -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.
|
|
454
|
-
this.
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
470
|
-
}
|
|
471
|
-
|
|
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
|
-
|
|
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
|
package/package.json
CHANGED