@energy8platform/game-engine 0.32.3 → 0.32.4
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/slot.cjs.js +248 -1
- package/dist/slot.cjs.js.map +1 -1
- package/dist/slot.d.ts +77 -3
- package/dist/slot.esm.js +247 -2
- package/dist/slot.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/slot/cascade/ReelStepController.ts +270 -0
- package/src/slot/grid/SymbolCell.ts +7 -0
- package/src/slot/index.ts +2 -0
- package/src/slot/system/ReelSystem.ts +27 -2
package/dist/slot.d.ts
CHANGED
|
@@ -96,6 +96,7 @@ declare class SymbolCell extends Container {
|
|
|
96
96
|
private _style;
|
|
97
97
|
private _frame;
|
|
98
98
|
private _view;
|
|
99
|
+
private _data;
|
|
99
100
|
private _badges;
|
|
100
101
|
private _multBadge;
|
|
101
102
|
private _bonusBadge;
|
|
@@ -103,6 +104,8 @@ declare class SymbolCell extends Container {
|
|
|
103
104
|
frameStyleKey: 'idle' | 'winning' | 'removed' | 'fresh';
|
|
104
105
|
constructor(config: SymbolCellConfig);
|
|
105
106
|
get view(): SymbolView | null;
|
|
107
|
+
/** The last applied cell data (symbol + badges). Read by motion controllers that rebuild tapes. */
|
|
108
|
+
get data(): CellData;
|
|
106
109
|
setData(data: CellData): void;
|
|
107
110
|
setState(state: CellState): void;
|
|
108
111
|
playWin(): Promise<void>;
|
|
@@ -816,6 +819,68 @@ declare class TumbleController {
|
|
|
816
819
|
skip(): void;
|
|
817
820
|
}
|
|
818
821
|
|
|
822
|
+
interface ReelStepData {
|
|
823
|
+
/** Cells that won on the current board — highlighted/paid before the shift. */
|
|
824
|
+
winningCells: {
|
|
825
|
+
col: number;
|
|
826
|
+
row: number;
|
|
827
|
+
}[];
|
|
828
|
+
/** How far to scroll each reel down (length = cols). 0 = the reel stays put. */
|
|
829
|
+
shifts: number[];
|
|
830
|
+
/** Board after every reel has scrolled down by shifts[col]. */
|
|
831
|
+
settledGrid: CellData[][];
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* PURE: lay out one reel's scroll tape (top→bottom). The `shift` fresh symbols (the top of the
|
|
835
|
+
* settled reel) stack above the reel's current symbols; the tape starts `shift` cells high so the
|
|
836
|
+
* current symbols fill the window, then slides down by `shift` to reveal the fresh ones. `shift` is
|
|
837
|
+
* clamped to the visible window height. Returns the stacked cells and the start offset (in cells,
|
|
838
|
+
* relative to row 0) the tape animates from.
|
|
839
|
+
*/
|
|
840
|
+
declare function buildReelStepTape(before: CellData[], settledCol: CellData[], shift: number): {
|
|
841
|
+
stack: CellData[];
|
|
842
|
+
shift: number;
|
|
843
|
+
startOffsetCells: number;
|
|
844
|
+
};
|
|
845
|
+
declare class ReelStepController {
|
|
846
|
+
private _grid;
|
|
847
|
+
private _resolve;
|
|
848
|
+
private _cfg;
|
|
849
|
+
private _win;
|
|
850
|
+
private _killed;
|
|
851
|
+
private _mult;
|
|
852
|
+
private _temp;
|
|
853
|
+
/** Board the in-flight step settles to — used to snap on skip(). */
|
|
854
|
+
private _pending;
|
|
855
|
+
constructor(grid: ReelGrid, resolve: SymbolResolver, cfg: CascadeConfig, win?: WinConfig);
|
|
856
|
+
setConfig(cfg: CascadeConfig): void;
|
|
857
|
+
setWin(win: WinConfig): void;
|
|
858
|
+
/** Killed, or the grid was torn down underneath us (rebuild mid-chain). */
|
|
859
|
+
private get _dead();
|
|
860
|
+
get multiplier(): number;
|
|
861
|
+
resetMultiplier(): void;
|
|
862
|
+
private advanceMultiplier;
|
|
863
|
+
/** Run one ReelStep: pay the winning cells, then scroll each reel down by shifts[col]. */
|
|
864
|
+
step(step: ReelStepData, stepIndex?: number, opts?: {
|
|
865
|
+
turbo?: boolean;
|
|
866
|
+
}): Promise<void>;
|
|
867
|
+
/** Highlight + hold the winning cells, then release them back to rest before the shift. */
|
|
868
|
+
private _payWins;
|
|
869
|
+
/**
|
|
870
|
+
* Scroll one reel down by `n` positions. A tape carrying the reel's current symbols with `n`
|
|
871
|
+
* fresh symbols stacked on top slides down by `n` cells: the fresh symbols enter from the top,
|
|
872
|
+
* the existing ones ride down, and the bottom `n` ride off below the window. Ends on
|
|
873
|
+
* settledGrid[col]. The tape shares the cells' parent (so the reel mask, if any, clips it).
|
|
874
|
+
*/
|
|
875
|
+
private _scrollReel;
|
|
876
|
+
private _dim;
|
|
877
|
+
private _undim;
|
|
878
|
+
private _resetPositions;
|
|
879
|
+
private _cleanupTemp;
|
|
880
|
+
/** Hard-cancel: kill tweens, drop tapes, snap to the in-flight step's settled board. */
|
|
881
|
+
skip(): void;
|
|
882
|
+
}
|
|
883
|
+
|
|
819
884
|
interface FeatureContext {
|
|
820
885
|
grid: ReelGrid;
|
|
821
886
|
resolve: SymbolResolver;
|
|
@@ -875,7 +940,16 @@ interface ReelSystem {
|
|
|
875
940
|
turbo?: boolean;
|
|
876
941
|
freeSpins?: boolean;
|
|
877
942
|
}): Promise<void>;
|
|
878
|
-
/**
|
|
943
|
+
/**
|
|
944
|
+
* Run a ReelStep™ chain: each step pays its winning cells, then scrolls every reel down by
|
|
945
|
+
* `shifts[col]` positions (0 = reel stays put). Multiplier carries over with `freeSpins` +
|
|
946
|
+
* `cascade.multiplier.persistInFreeSpins`, same as `cascade`.
|
|
947
|
+
*/
|
|
948
|
+
reelStep(steps: ReelStepData[], opts?: {
|
|
949
|
+
turbo?: boolean;
|
|
950
|
+
freeSpins?: boolean;
|
|
951
|
+
}): Promise<void>;
|
|
952
|
+
/** Current running cascade / reel-step multiplier. */
|
|
879
953
|
readonly multiplier: number;
|
|
880
954
|
/** Register a custom feature (or override a built-in by reusing its key). */
|
|
881
955
|
registerFeature(feature: ReelFeature): void;
|
|
@@ -974,5 +1048,5 @@ declare class MultiplierAccumulator {
|
|
|
974
1048
|
reset(boundary: CarryPolicy): void;
|
|
975
1049
|
}
|
|
976
1050
|
|
|
977
|
-
export { AnimatedSymbol, AnticipationController, BigWinOverlay, CascadeController, CountUpDisplay, DEFAULT_REEL_CONFIG, EASING_BY_NAME, FEATURES, FEATURE_KEYS, FEATURE_LIST, INTENSITY_SCALE, MultiplierAccumulator, PRESETS, PRESET_LIST, ReelGrid, ReelSpinController, SpinEngine, SymbolCell, TumbleController, cellPositionOf, createReelSystem, easingByName, effectiveRowsPerReel, mergeReelConfig, pickTier, resolveGeometry, resolveGridGeometry, resolveReelConfig, tierIndexAtValue, valueAt, waysCount };
|
|
978
|
-
export type { AnimatedSymbolConfig, AnticipationConfig, AnticipationDecision, BigWinOverlayConfig, BlurConfig, CarryPolicy, CascadeAnim, CascadeConfig, CascadeStepData, CascadeTimings, CellData, CellFrameStyle, CellSizeSpec, CellState, CountUpConfig, CreateReelSystemOptions, DecorationConfig, DeepPartial, EasingName, EvaluationMode, ExpandingWildConfig, FeatureContext, FeatureKey, FeaturesConfig, GeometryInput, GiantConfig, GridConfig, HoldAndSpinConfig, Intensity, MotionConfig, MotionStyle, MultiplierConfig, MysteryConfig, NudgeConfig, PresetId, RandomWildConfig, ReelFeature, ReelGridConfig, ReelModifierConfig, ReelPreset, ReelSpinData, ReelSpinTimings, ReelStopPlan$1 as ReelStopPlan, ReelSystem, ReelSystemConfig, ResolvedGeometry, SettleConfig, SpinData, SpinRunOpts, ReelStopPlan as SpinStopPlan, SplitConfig, SquashConfig, StackedConfig, StickyConfig, StopMode, StopOrder, SymbolCellConfig, SymbolResolver, SymbolTextures, SymbolView, TransformConfig, TumbleStep, WalkingWildConfig, WinConfig, WinTier };
|
|
1051
|
+
export { AnimatedSymbol, AnticipationController, BigWinOverlay, CascadeController, CountUpDisplay, DEFAULT_REEL_CONFIG, EASING_BY_NAME, FEATURES, FEATURE_KEYS, FEATURE_LIST, INTENSITY_SCALE, MultiplierAccumulator, PRESETS, PRESET_LIST, ReelGrid, ReelSpinController, ReelStepController, SpinEngine, SymbolCell, TumbleController, buildReelStepTape, cellPositionOf, createReelSystem, easingByName, effectiveRowsPerReel, mergeReelConfig, pickTier, resolveGeometry, resolveGridGeometry, resolveReelConfig, tierIndexAtValue, valueAt, waysCount };
|
|
1052
|
+
export type { AnimatedSymbolConfig, AnticipationConfig, AnticipationDecision, BigWinOverlayConfig, BlurConfig, CarryPolicy, CascadeAnim, CascadeConfig, CascadeStepData, CascadeTimings, CellData, CellFrameStyle, CellSizeSpec, CellState, CountUpConfig, CreateReelSystemOptions, DecorationConfig, DeepPartial, EasingName, EvaluationMode, ExpandingWildConfig, FeatureContext, FeatureKey, FeaturesConfig, GeometryInput, GiantConfig, GridConfig, HoldAndSpinConfig, Intensity, MotionConfig, MotionStyle, MultiplierConfig, MysteryConfig, NudgeConfig, PresetId, RandomWildConfig, ReelFeature, ReelGridConfig, ReelModifierConfig, ReelPreset, ReelSpinData, ReelSpinTimings, ReelStepData, ReelStopPlan$1 as ReelStopPlan, ReelSystem, ReelSystemConfig, ResolvedGeometry, SettleConfig, SpinData, SpinRunOpts, ReelStopPlan as SpinStopPlan, SplitConfig, SquashConfig, StackedConfig, StickyConfig, StopMode, StopOrder, SymbolCellConfig, SymbolResolver, SymbolTextures, SymbolView, TransformConfig, TumbleStep, WalkingWildConfig, WinConfig, WinTier };
|
package/dist/slot.esm.js
CHANGED
|
@@ -537,6 +537,7 @@ class SymbolCell extends Container {
|
|
|
537
537
|
_style;
|
|
538
538
|
_frame;
|
|
539
539
|
_view = null;
|
|
540
|
+
_data = { symbol: null };
|
|
540
541
|
_badges = new Container();
|
|
541
542
|
_multBadge = null;
|
|
542
543
|
_bonusBadge = null;
|
|
@@ -557,9 +558,14 @@ class SymbolCell extends Container {
|
|
|
557
558
|
get view() {
|
|
558
559
|
return this._view;
|
|
559
560
|
}
|
|
561
|
+
/** The last applied cell data (symbol + badges). Read by motion controllers that rebuild tapes. */
|
|
562
|
+
get data() {
|
|
563
|
+
return this._data;
|
|
564
|
+
}
|
|
560
565
|
setData(data) {
|
|
561
566
|
if (this.destroyed)
|
|
562
567
|
return; // a killed-tween chain may resume after the cell is gone
|
|
568
|
+
this._data = data;
|
|
563
569
|
// symbol view
|
|
564
570
|
if (data.symbol == null) {
|
|
565
571
|
if (this._view) {
|
|
@@ -2023,6 +2029,227 @@ class TumbleController {
|
|
|
2023
2029
|
}
|
|
2024
2030
|
}
|
|
2025
2031
|
|
|
2032
|
+
// packages/game-engine/src/slot/cascade/ReelStepController.ts
|
|
2033
|
+
//
|
|
2034
|
+
// ReelStep™ mechanic. Flow: reels stop → winning lines are paid → each reel scrolls DOWN by N
|
|
2035
|
+
// positions (N = winning symbols that played on that reel) → the board is re-evaluated → repeat,
|
|
2036
|
+
// until no wins remain. Unlike a cascade/tumble, nothing is removed: the existing symbols ride
|
|
2037
|
+
// down and N fresh symbols enter from the top. Reels with N=0 stay put; each reel moves
|
|
2038
|
+
// independently by its own N.
|
|
2039
|
+
//
|
|
2040
|
+
// Presentation only — the caller supplies each step's per-reel shift vector and the post-shift
|
|
2041
|
+
// board. Fits classic fixed-line grids (5×3, 5×4, 5×5, …), not ways/cluster.
|
|
2042
|
+
/**
|
|
2043
|
+
* PURE: lay out one reel's scroll tape (top→bottom). The `shift` fresh symbols (the top of the
|
|
2044
|
+
* settled reel) stack above the reel's current symbols; the tape starts `shift` cells high so the
|
|
2045
|
+
* current symbols fill the window, then slides down by `shift` to reveal the fresh ones. `shift` is
|
|
2046
|
+
* clamped to the visible window height. Returns the stacked cells and the start offset (in cells,
|
|
2047
|
+
* relative to row 0) the tape animates from.
|
|
2048
|
+
*/
|
|
2049
|
+
function buildReelStepTape(before, settledCol, shift) {
|
|
2050
|
+
const rows = before.length;
|
|
2051
|
+
const s = Math.max(0, Math.min(shift, rows));
|
|
2052
|
+
const incoming = Array.from({ length: s }, (_, i) => settledCol[i] ?? { symbol: null });
|
|
2053
|
+
return { stack: [...incoming, ...before], shift: s, startOffsetCells: 0 - s };
|
|
2054
|
+
}
|
|
2055
|
+
class ReelStepController {
|
|
2056
|
+
_grid;
|
|
2057
|
+
_resolve;
|
|
2058
|
+
_cfg;
|
|
2059
|
+
_win = DEFAULT_REEL_CONFIG.win;
|
|
2060
|
+
_killed = false;
|
|
2061
|
+
_mult;
|
|
2062
|
+
_temp = [];
|
|
2063
|
+
/** Board the in-flight step settles to — used to snap on skip(). */
|
|
2064
|
+
_pending = null;
|
|
2065
|
+
constructor(grid, resolve, cfg, win) {
|
|
2066
|
+
this._grid = grid;
|
|
2067
|
+
this._resolve = resolve;
|
|
2068
|
+
this._cfg = cfg;
|
|
2069
|
+
this._mult = cfg.multiplier.start;
|
|
2070
|
+
if (win)
|
|
2071
|
+
this._win = win;
|
|
2072
|
+
}
|
|
2073
|
+
setConfig(cfg) {
|
|
2074
|
+
this._cfg = cfg;
|
|
2075
|
+
}
|
|
2076
|
+
setWin(win) {
|
|
2077
|
+
this._win = win;
|
|
2078
|
+
}
|
|
2079
|
+
/** Killed, or the grid was torn down underneath us (rebuild mid-chain). */
|
|
2080
|
+
get _dead() {
|
|
2081
|
+
return this._killed || this._grid.destroyed;
|
|
2082
|
+
}
|
|
2083
|
+
get multiplier() {
|
|
2084
|
+
return this._mult;
|
|
2085
|
+
}
|
|
2086
|
+
resetMultiplier() {
|
|
2087
|
+
this._mult = this._cfg.multiplier.start;
|
|
2088
|
+
}
|
|
2089
|
+
advanceMultiplier() {
|
|
2090
|
+
const m = this._cfg.multiplier;
|
|
2091
|
+
if (!m.enabled)
|
|
2092
|
+
return;
|
|
2093
|
+
const next = m.mode === 'mul' ? this._mult * m.step : this._mult + m.step;
|
|
2094
|
+
this._mult = m.cap != null ? Math.min(next, m.cap) : next;
|
|
2095
|
+
}
|
|
2096
|
+
/** Run one ReelStep: pay the winning cells, then scroll each reel down by shifts[col]. */
|
|
2097
|
+
async step(step, stepIndex = 0, opts) {
|
|
2098
|
+
if (this._grid.destroyed)
|
|
2099
|
+
return;
|
|
2100
|
+
this._killed = false;
|
|
2101
|
+
this._pending = step.settledGrid;
|
|
2102
|
+
// 1. celebrate/pay the winning cells.
|
|
2103
|
+
await this._payWins(step, opts);
|
|
2104
|
+
if (this._dead)
|
|
2105
|
+
return;
|
|
2106
|
+
// No shift → just settle the board (defensive; a real ReelStep always shifts something).
|
|
2107
|
+
const hasShift = step.shifts.some((n) => n > 0);
|
|
2108
|
+
if (!this._cfg.enabled || !hasShift) {
|
|
2109
|
+
this._grid.setGrid(step.settledGrid);
|
|
2110
|
+
if (step.winningCells.length)
|
|
2111
|
+
this.advanceMultiplier();
|
|
2112
|
+
this._pending = null;
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
// 2. scroll every reel down by its own N (0 = untouched), all reels concurrently.
|
|
2116
|
+
const turbo = opts?.turbo ? 0.5 : 1;
|
|
2117
|
+
const decel = Math.min(this._cfg.perStepDecelCap, 1 + stepIndex * this._cfg.perStepDecel);
|
|
2118
|
+
const f = turbo * decel;
|
|
2119
|
+
await Promise.all(step.shifts.map((n, col) => n > 0 ? this._scrollReel(col, n, step.settledGrid, f) : Promise.resolve()));
|
|
2120
|
+
if (this._dead) {
|
|
2121
|
+
this._cleanupTemp();
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
// 3. normalise + advance multiplier.
|
|
2125
|
+
this._grid.setGrid(step.settledGrid);
|
|
2126
|
+
this._resetPositions();
|
|
2127
|
+
this._cleanupTemp();
|
|
2128
|
+
this._pending = null;
|
|
2129
|
+
if (step.winningCells.length)
|
|
2130
|
+
this.advanceMultiplier();
|
|
2131
|
+
}
|
|
2132
|
+
/** Highlight + hold the winning cells, then release them back to rest before the shift. */
|
|
2133
|
+
async _payWins(step, opts) {
|
|
2134
|
+
if (!step.winningCells.length)
|
|
2135
|
+
return;
|
|
2136
|
+
const turbo = opts?.turbo ? 0.5 : 1;
|
|
2137
|
+
const t = this._cfg.timings;
|
|
2138
|
+
const hs = this._win.highlightScale;
|
|
2139
|
+
const winSet = new Set(step.winningCells.map((w) => `${w.col}:${w.row}`));
|
|
2140
|
+
if (this._cfg.dimNonWinners)
|
|
2141
|
+
this._dim(winSet);
|
|
2142
|
+
await Promise.all(step.winningCells.map((w) => {
|
|
2143
|
+
const cell = this._grid.getCell(w.col, w.row);
|
|
2144
|
+
if (this._win.glow)
|
|
2145
|
+
cell.setState({ winning: true });
|
|
2146
|
+
return Tween.to(cell, { 'scale.x': hs, 'scale.y': hs }, t.highlight * turbo, easingByName(this._cfg.easings.highlight));
|
|
2147
|
+
}));
|
|
2148
|
+
if (this._dead) {
|
|
2149
|
+
this._undim();
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
await Tween.delay(t.wait * turbo);
|
|
2153
|
+
for (const w of step.winningCells) {
|
|
2154
|
+
const cell = this._grid.getCell(w.col, w.row);
|
|
2155
|
+
cell.setState({});
|
|
2156
|
+
cell.scale.set(1);
|
|
2157
|
+
}
|
|
2158
|
+
this._undim();
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Scroll one reel down by `n` positions. A tape carrying the reel's current symbols with `n`
|
|
2162
|
+
* fresh symbols stacked on top slides down by `n` cells: the fresh symbols enter from the top,
|
|
2163
|
+
* the existing ones ride down, and the bottom `n` ride off below the window. Ends on
|
|
2164
|
+
* settledGrid[col]. The tape shares the cells' parent (so the reel mask, if any, clips it).
|
|
2165
|
+
*/
|
|
2166
|
+
async _scrollReel(col, n, settledGrid, f) {
|
|
2167
|
+
const rows = this._grid.rowsOf(col);
|
|
2168
|
+
if (rows === 0 || this._dead)
|
|
2169
|
+
return;
|
|
2170
|
+
const realCells = Array.from({ length: rows }, (_, r) => this._grid.getCell(col, r));
|
|
2171
|
+
const layer = realCells[0].parent ?? this._grid;
|
|
2172
|
+
const base = this._grid.cellPosition(col, 0);
|
|
2173
|
+
const step = rows > 1 ? this._grid.cellPosition(col, 1).y - base.y : this._grid.cellSize(col).height;
|
|
2174
|
+
// current visible symbols (top→bottom), captured before we hide them
|
|
2175
|
+
const before = realCells.map((c) => ({ ...c.data }));
|
|
2176
|
+
const { stack, startOffsetCells } = buildReelStepTape(before, settledGrid[col] ?? [], n);
|
|
2177
|
+
// Tape laid out top→bottom at local y = i*step: [incoming(shift)] above [before(rows)].
|
|
2178
|
+
const tape = new Container();
|
|
2179
|
+
tape.x = base.x;
|
|
2180
|
+
for (let i = 0; i < stack.length; i++) {
|
|
2181
|
+
const cell = new SymbolCell({ size: this._grid.cellSize(col), resolve: this._resolve });
|
|
2182
|
+
cell.setData(stack[i]);
|
|
2183
|
+
cell.position.set(0, i * step);
|
|
2184
|
+
tape.addChild(cell);
|
|
2185
|
+
}
|
|
2186
|
+
// Start: the `before` block fills the window; the incoming block sits above it (masked off).
|
|
2187
|
+
tape.y = base.y + startOffsetCells * step;
|
|
2188
|
+
realCells.forEach((c) => (c.visible = false));
|
|
2189
|
+
layer.addChild(tape);
|
|
2190
|
+
this._temp.push(tape);
|
|
2191
|
+
// Slide down by `shift` positions, with a small overshoot then settle-back.
|
|
2192
|
+
const overshoot = step * 0.12;
|
|
2193
|
+
await Tween.to(tape, { y: base.y + overshoot }, this._cfg.timings.drop * f, easingByName(this._cfg.easings.drop));
|
|
2194
|
+
if (this._dead)
|
|
2195
|
+
return;
|
|
2196
|
+
await Tween.to(tape, { y: base.y }, Math.max(90, this._cfg.timings.refill * f), easingByName('easeOutQuad'));
|
|
2197
|
+
if (this._dead)
|
|
2198
|
+
return;
|
|
2199
|
+
// Hand the settled symbols back to the real cells.
|
|
2200
|
+
for (let r = 0; r < rows; r++)
|
|
2201
|
+
realCells[r].setData(settledGrid[col]?.[r] ?? { symbol: null });
|
|
2202
|
+
realCells.forEach((c) => (c.visible = true));
|
|
2203
|
+
tape.destroy();
|
|
2204
|
+
this._temp = this._temp.filter((tp) => tp !== tape);
|
|
2205
|
+
}
|
|
2206
|
+
_dim(winSet) {
|
|
2207
|
+
for (let c = 0; c < this._grid.cols; c++)
|
|
2208
|
+
for (let r = 0; r < this._grid.rowsOf(c); r++)
|
|
2209
|
+
if (!winSet.has(`${c}:${r}`))
|
|
2210
|
+
this._grid.getCell(c, r).alpha = this._cfg.dimAlpha;
|
|
2211
|
+
}
|
|
2212
|
+
_undim() {
|
|
2213
|
+
for (let c = 0; c < this._grid.cols; c++)
|
|
2214
|
+
for (let r = 0; r < this._grid.rowsOf(c); r++) {
|
|
2215
|
+
const cell = this._grid.getCell(c, r);
|
|
2216
|
+
if (cell.alpha !== 0)
|
|
2217
|
+
cell.alpha = 1;
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
_resetPositions() {
|
|
2221
|
+
if (this._grid.destroyed)
|
|
2222
|
+
return;
|
|
2223
|
+
for (let c = 0; c < this._grid.cols; c++)
|
|
2224
|
+
for (let r = 0; r < this._grid.rowsOf(c); r++) {
|
|
2225
|
+
const cell = this._grid.getCell(c, r);
|
|
2226
|
+
const { x, y } = this._grid.cellPosition(c, r);
|
|
2227
|
+
cell.position.set(x, y);
|
|
2228
|
+
cell.scale.set(1);
|
|
2229
|
+
cell.alpha = 1;
|
|
2230
|
+
cell.visible = true;
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
_cleanupTemp() {
|
|
2234
|
+
for (const t of this._temp)
|
|
2235
|
+
if (!t.destroyed)
|
|
2236
|
+
t.destroy();
|
|
2237
|
+
this._temp = [];
|
|
2238
|
+
}
|
|
2239
|
+
/** Hard-cancel: kill tweens, drop tapes, snap to the in-flight step's settled board. */
|
|
2240
|
+
skip() {
|
|
2241
|
+
this._killed = true;
|
|
2242
|
+
for (let c = 0; c < this._grid.cols; c++)
|
|
2243
|
+
for (let r = 0; r < this._grid.rowsOf(c); r++)
|
|
2244
|
+
Tween.killTweensOf(this._grid.getCell(c, r));
|
|
2245
|
+
this._cleanupTemp();
|
|
2246
|
+
if (this._pending && !this._grid.destroyed)
|
|
2247
|
+
this._grid.setGrid(this._pending);
|
|
2248
|
+
this._pending = null;
|
|
2249
|
+
this._resetPositions();
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2026
2253
|
// packages/game-engine/src/slot/features/types.ts
|
|
2027
2254
|
//
|
|
2028
2255
|
// Uniform interface for special reel feature mechanics. Each feature is a presentation module:
|
|
@@ -2571,6 +2798,7 @@ function createReelSystem(opts) {
|
|
|
2571
2798
|
let spin;
|
|
2572
2799
|
let anticipation;
|
|
2573
2800
|
let tumble;
|
|
2801
|
+
let reelStepCtl;
|
|
2574
2802
|
let board = opts.board ?? emptyBoard(config);
|
|
2575
2803
|
// custom features keyed by id; built-ins live in FEATURES/FEATURE_LIST
|
|
2576
2804
|
const custom = new Map();
|
|
@@ -2593,6 +2821,7 @@ function createReelSystem(opts) {
|
|
|
2593
2821
|
if (grid) {
|
|
2594
2822
|
spin?.skip();
|
|
2595
2823
|
tumble?.skip();
|
|
2824
|
+
reelStepCtl?.skip();
|
|
2596
2825
|
for (const child of fx?.children.slice() ?? [])
|
|
2597
2826
|
Tween.killTweensOf(child);
|
|
2598
2827
|
grid.destroy({ children: true });
|
|
@@ -2621,6 +2850,7 @@ function createReelSystem(opts) {
|
|
|
2621
2850
|
spin = new SpinEngine(grid, resolve, config.motion, config.win);
|
|
2622
2851
|
anticipation = new AnticipationController(config.anticipation);
|
|
2623
2852
|
tumble = new TumbleController(grid, config.cascade, config.win);
|
|
2853
|
+
reelStepCtl = new ReelStepController(grid, resolve, config.cascade, config.win);
|
|
2624
2854
|
grid.setGrid(board);
|
|
2625
2855
|
}
|
|
2626
2856
|
function geometryChanged(next) {
|
|
@@ -2676,6 +2906,8 @@ function createReelSystem(opts) {
|
|
|
2676
2906
|
anticipation.setConfig(config.anticipation);
|
|
2677
2907
|
tumble.setConfig(config.cascade);
|
|
2678
2908
|
tumble.setWin(config.win);
|
|
2909
|
+
reelStepCtl.setConfig(config.cascade);
|
|
2910
|
+
reelStepCtl.setWin(config.win);
|
|
2679
2911
|
}
|
|
2680
2912
|
},
|
|
2681
2913
|
update(partial) {
|
|
@@ -2700,7 +2932,8 @@ function createReelSystem(opts) {
|
|
|
2700
2932
|
board = target;
|
|
2701
2933
|
},
|
|
2702
2934
|
get multiplier() {
|
|
2703
|
-
|
|
2935
|
+
// cascade and reelStep share the same config start; only the active mechanic climbs.
|
|
2936
|
+
return Math.max(tumble.multiplier, reelStepCtl.multiplier);
|
|
2704
2937
|
},
|
|
2705
2938
|
async cascade(steps, cOpts) {
|
|
2706
2939
|
// keep the multiplier climbing across free-spins when configured; otherwise reset per spin
|
|
@@ -2714,6 +2947,17 @@ function createReelSystem(opts) {
|
|
|
2714
2947
|
if (config.cascade.multiplier.enabled)
|
|
2715
2948
|
log?.(`Cascade multiplier ×${tumble.multiplier}`);
|
|
2716
2949
|
},
|
|
2950
|
+
async reelStep(steps, rOpts) {
|
|
2951
|
+
const persist = config.cascade.multiplier.persistInFreeSpins && !!rOpts?.freeSpins;
|
|
2952
|
+
if (!persist)
|
|
2953
|
+
reelStepCtl.resetMultiplier();
|
|
2954
|
+
for (let i = 0; i < steps.length; i++) {
|
|
2955
|
+
await reelStepCtl.step(steps[i], i, rOpts);
|
|
2956
|
+
board = steps[i].settledGrid;
|
|
2957
|
+
}
|
|
2958
|
+
if (config.cascade.multiplier.enabled)
|
|
2959
|
+
log?.(`ReelStep multiplier ×${reelStepCtl.multiplier}`);
|
|
2960
|
+
},
|
|
2717
2961
|
registerFeature(feature) {
|
|
2718
2962
|
custom.set(feature.key, feature);
|
|
2719
2963
|
},
|
|
@@ -2745,6 +2989,7 @@ function createReelSystem(opts) {
|
|
|
2745
2989
|
skip() {
|
|
2746
2990
|
spin.skip();
|
|
2747
2991
|
tumble.skip();
|
|
2992
|
+
reelStepCtl.skip();
|
|
2748
2993
|
// kill in-flight overlay tweens (labels/rings) so a rebuild never animates destroyed nodes
|
|
2749
2994
|
for (const child of fx.children.slice())
|
|
2750
2995
|
Tween.killTweensOf(child);
|
|
@@ -2924,5 +3169,5 @@ class MultiplierAccumulator {
|
|
|
2924
3169
|
}
|
|
2925
3170
|
}
|
|
2926
3171
|
|
|
2927
|
-
export { AnimatedSymbol, AnticipationController, BigWinOverlay, CascadeController, CountUpDisplay, DEFAULT_REEL_CONFIG, EASING_BY_NAME, FEATURES, FEATURE_KEYS, FEATURE_LIST, INTENSITY_SCALE, MultiplierAccumulator, PRESETS, PRESET_LIST, ReelGrid, ReelSpinController, SpinEngine, SymbolCell, TumbleController, cellPositionOf, createReelSystem, easingByName, effectiveRowsPerReel, mergeReelConfig, pickTier, resolveGeometry, resolveGridGeometry, resolveReelConfig, tierIndexAtValue, valueAt, waysCount };
|
|
3172
|
+
export { AnimatedSymbol, AnticipationController, BigWinOverlay, CascadeController, CountUpDisplay, DEFAULT_REEL_CONFIG, EASING_BY_NAME, FEATURES, FEATURE_KEYS, FEATURE_LIST, INTENSITY_SCALE, MultiplierAccumulator, PRESETS, PRESET_LIST, ReelGrid, ReelSpinController, ReelStepController, SpinEngine, SymbolCell, TumbleController, buildReelStepTape, cellPositionOf, createReelSystem, easingByName, effectiveRowsPerReel, mergeReelConfig, pickTier, resolveGeometry, resolveGridGeometry, resolveReelConfig, tierIndexAtValue, valueAt, waysCount };
|
|
2928
3173
|
//# sourceMappingURL=slot.esm.js.map
|