pixi-rainman-game-engine 0.3.27 → 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.
@@ -1,11 +1,17 @@
1
1
  import { Container, Sprite, Text, TextStyle, Texture } from "pixi.js";
2
- import { Logo } from "../common";
2
+ import { IntroLogo } from "../common";
3
3
  import { IntroContinueButton } from "./IntroContinueButton";
4
4
  import { IntroDotButton } from "./IntroDotButton";
5
5
  import { IntroPageData } 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.
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
9
15
  * @abstract
10
16
  * @class AbstractIntroScreen
11
17
  * @typedef {AbstractIntroScreen}
@@ -21,7 +27,7 @@ export declare abstract class AbstractIntroScreen extends Container {
21
27
  frame: Texture;
22
28
  tick: Texture;
23
29
  };
24
- protected abstract logo: Logo;
30
+ protected abstract logo: IntroLogo;
25
31
  protected abstract pages: IntroPageData[];
26
32
  protected abstract pageTextStyle: TextStyle;
27
33
  protected checkboxLabel: Text;
@@ -29,6 +35,7 @@ export declare abstract class AbstractIntroScreen extends Container {
29
35
  protected checkboxTick: Sprite;
30
36
  protected continueButton: IntroContinueButton;
31
37
  protected dots: IntroDotButton[];
38
+ protected dotShadow?: Sprite;
32
39
  protected frameImage: Sprite;
33
40
  protected pageImage: Sprite;
34
41
  protected pageText: Text;
@@ -55,6 +62,12 @@ export declare abstract class AbstractIntroScreen extends Container {
55
62
  protected showPage(index: number): void;
56
63
  private updateFade;
57
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;
58
71
  private createDots;
59
72
  private navigatePage;
60
73
  private handleContinue;
@@ -3,12 +3,18 @@ import { Container, Sprite, Text, Texture, Ticker } from "pixi.js";
3
3
  import { UXLayer } from "../../layers";
4
4
  import { getFromLocalStorage, LOCAL_STORAGE, setToLocalStorage } from "../../localStorage";
5
5
  import { RainMan } from "../../Rainman";
6
- import { getDeviceOrientation, getTexture, scaleToParent } from "../../utils";
6
+ import { getDeviceOrientation, getTexture, getTextureSafely, scaleToParent } from "../../utils";
7
7
  import { IntroContinueButton } from "./IntroContinueButton";
8
8
  import { IntroDotButton } from "./IntroDotButton";
9
9
  /**
10
10
  * Abstract intro screen shown between loading and gameplay.
11
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
12
18
  * @abstract
13
19
  * @class AbstractIntroScreen
14
20
  * @typedef {AbstractIntroScreen}
@@ -23,6 +29,7 @@ export class AbstractIntroScreen extends Container {
23
29
  checkboxTick;
24
30
  continueButton;
25
31
  dots = [];
32
+ dotShadow;
26
33
  frameImage;
27
34
  pageImage;
28
35
  pageText;
@@ -49,6 +56,7 @@ export class AbstractIntroScreen extends Container {
49
56
  this.handleContinue();
50
57
  });
51
58
  this.createDots();
59
+ this.createDotShadow();
52
60
  this.checkboxSprite = new Sprite(this.checkboxTextures.frame);
53
61
  this.checkboxSprite.eventMode = "static";
54
62
  this.checkboxSprite.cursor = "pointer";
@@ -106,6 +114,8 @@ export class AbstractIntroScreen extends Container {
106
114
  this.addChild(this.checkboxSprite);
107
115
  this.addChild(this.checkboxTick);
108
116
  this.addChild(this.checkboxLabel);
117
+ if (this.dotShadow)
118
+ this.addChild(this.dotShadow);
109
119
  this.dots.forEach((dot) => this.addChild(dot));
110
120
  this.positionPageImage();
111
121
  this.positionLogo();
@@ -113,6 +123,7 @@ export class AbstractIntroScreen extends Container {
113
123
  this.positionCheckbox();
114
124
  this.positionContinueButton();
115
125
  this.positionText();
126
+ this.positionDotShadow();
116
127
  this.frameImage.x = this.pageImage.x;
117
128
  this.frameImage.y = this.pageImage.y;
118
129
  this.frameImage.width = this.pageImage.width;
@@ -138,11 +149,35 @@ export class AbstractIntroScreen extends Container {
138
149
  this.fadeTween?.update();
139
150
  }
140
151
  cleanup() {
141
- Ticker.shared.remove(this.updateFade, this);
142
152
  Ticker.shared.remove(this.updateFade, this);
143
153
  RainMan.app.renderer.off("resize", this.resizeHandler);
144
154
  this.destroy({ children: true });
145
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
+ }
146
181
  createDots() {
147
182
  this.dots = [];
148
183
  for (let i = 0; i < this.pages.length; i++) {
@@ -17,5 +17,6 @@ export class IntroContinueButton extends BaseButton {
17
17
  [ButtonStates.CLICKED]: getTexture("intro-continue-c.png")
18
18
  };
19
19
  super(IntroContinueButton.registryName, textureMap);
20
+ this.cursor = "pointer";
20
21
  }
21
22
  }
@@ -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";
@@ -150,14 +150,17 @@ export class AbstractColumnsContainer extends Container {
150
150
  frame.invokeMysteryFx(this.currentColumn.x, this.currentColumn.y);
151
151
  this.skipScatterDelay = configureSpinAction;
152
152
  await this.currentColumn.speedUpForMysterySpin(100, 1500);
153
- if (!RainMan.settingsStore.isAutoplayEnabled ||
154
- (RainMan.settingsStore.isAutoplayEnabled && !this.quickReelsStop)) {
155
- setTimeout(() => {
156
- this.skipScatterDelay?.();
157
- this.skipScatterDelay = null;
158
- }, 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
+ }
159
161
  }
160
162
  await this.currentColumn.changeVelocity(Speed.STOP, RainMan.config.durationOfActions.scatterSpinningTime, frame);
163
+ frame.destroyMysteryFx();
161
164
  }
162
165
  if (!indexesOfReelsToExtendSpinningTime.includes(index)) {
163
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
  }
@@ -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
  }
@@ -773,6 +773,7 @@ export class AbstractController {
773
773
  if (this.gamePhase === gamePhases.DATA_FETCH ||
774
774
  this.gamePhase === gamePhases.SPINNING ||
775
775
  this.gamePhase === gamePhases.CONFIGURING_SPIN) {
776
+ this.columnsContainer.enableQuickReelsStop();
776
777
  this.isSpinThrottled = true;
777
778
  this.quickStopController.handleResetCounter(this.spinThrottlerTimeout !== null);
778
779
  this.quickStopController.handleInvokingSpeedPopup();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixi-rainman-game-engine",
3
- "version": "0.3.27",
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",