@energy8platform/game-engine 0.39.0 → 0.40.0
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/README.md +37 -1
- package/dist/devtools.cjs.js +4 -0
- package/dist/devtools.cjs.js.map +1 -1
- package/dist/devtools.d.ts +23 -0
- package/dist/devtools.esm.js +4 -0
- package/dist/devtools.esm.js.map +1 -1
- package/dist/reel-panel-client.cjs.js +4 -0
- package/dist/reel-panel-client.cjs.js.map +1 -1
- package/dist/reel-panel-client.esm.js +4 -0
- package/dist/reel-panel-client.esm.js.map +1 -1
- package/dist/slot.cjs.js +51 -7
- package/dist/slot.cjs.js.map +1 -1
- package/dist/slot.d.ts +38 -1
- package/dist/slot.esm.js +51 -7
- package/dist/slot.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/slot/config/ReelSystemConfig.ts +25 -0
- package/src/slot/devtools/fieldSchema.ts +2 -0
- package/src/slot/index.ts +2 -0
- package/src/slot/motion/SpinEngine.ts +63 -12
package/dist/slot.d.ts
CHANGED
|
@@ -411,6 +411,10 @@ interface GridConfig {
|
|
|
411
411
|
}
|
|
412
412
|
type MotionStyle = 'swap' | 'strip' | 'cascade-drop';
|
|
413
413
|
type StopMode = 'sequential' | 'sync' | 'random';
|
|
414
|
+
/** `cascade-drop` fill direction within one reel. */
|
|
415
|
+
type DropOrder = 'top-down' | 'bottom-up';
|
|
416
|
+
/** How `cascade-drop` spaces reels: by formula, or one strictly after the other. */
|
|
417
|
+
type DropSequence = 'parallel' | 'chained' | 'chained-when-anticipated';
|
|
414
418
|
type StopOrder = 'ltr' | 'rtl';
|
|
415
419
|
type Intensity = 'full' | 'reduced' | 'minimal';
|
|
416
420
|
interface SettleConfig {
|
|
@@ -462,6 +466,25 @@ interface MotionConfig {
|
|
|
462
466
|
reelStaggerFactor: number;
|
|
463
467
|
/** `cascade-drop`: fall duration as a fraction of `spinUp`. Default 0.6. */
|
|
464
468
|
dropFallFactor: number;
|
|
469
|
+
/**
|
|
470
|
+
* `cascade-drop`: which cell of a reel lands first. `'top-down'` (default, the engine's original
|
|
471
|
+
* behaviour) deals the reel like cards from the top; `'bottom-up'` fills it the way gravity
|
|
472
|
+
* would — the lowest cell arrives first and the rest stack on top of it.
|
|
473
|
+
*/
|
|
474
|
+
dropOrder: DropOrder;
|
|
475
|
+
/**
|
|
476
|
+
* `cascade-drop`: how reels are spaced.
|
|
477
|
+
* - `'parallel'` (default) starts every reel at its own formula offset —
|
|
478
|
+
* `reel * stopStagger * reelStaggerFactor` — so a reel can open while the previous one is
|
|
479
|
+
* still dropping. Fast, and what a normal spin wants.
|
|
480
|
+
* - `'chained'` starts a reel only once the previous one has seated its LAST cell, plus that
|
|
481
|
+
* same offset as the gap. One reel at a time, always — correct, but it makes every spin as
|
|
482
|
+
* long as the sum of its reels.
|
|
483
|
+
* - `'chained-when-anticipated'` runs the un-armed reels in parallel and switches to the chain
|
|
484
|
+
* from the first ANTICIPATED reel onwards. The base spin keeps its stop window; the hunt for
|
|
485
|
+
* the last scatter goes strictly reel by reel, each slower than the last.
|
|
486
|
+
*/
|
|
487
|
+
dropSequence: DropSequence;
|
|
465
488
|
}
|
|
466
489
|
/** What a game-supplied `AnticipationConfig.decide` may return instead of a bare reel list. */
|
|
467
490
|
interface AnticipationOverride {
|
|
@@ -773,6 +796,13 @@ interface ReelStopPlan {
|
|
|
773
796
|
slowdown: number;
|
|
774
797
|
/** True when `deferReveal` withheld this reel's landing (see `SpinRunOpts.deferReveal`). */
|
|
775
798
|
deferred: boolean;
|
|
799
|
+
/**
|
|
800
|
+
* `cascade-drop` only: ms from spin start at which each cell of this reel seats, INDEXED BY ROW.
|
|
801
|
+
* The reel's fill direction lives in these numbers (`motion.dropOrder` decides which row is the
|
|
802
|
+
* smallest), as does its place in the chain (`motion.dropSequence`). `stopTime` is the largest
|
|
803
|
+
* of them — the frame the whole reel has landed.
|
|
804
|
+
*/
|
|
805
|
+
cellStopTimes?: number[];
|
|
776
806
|
}
|
|
777
807
|
declare class SpinEngine {
|
|
778
808
|
private _grid;
|
|
@@ -791,6 +821,13 @@ declare class SpinEngine {
|
|
|
791
821
|
private scale;
|
|
792
822
|
/** PURE: per-reel stop schedule. No Pixi mutation. */
|
|
793
823
|
plan(data: SpinData, opts?: SpinRunOpts): ReelStopPlan[];
|
|
824
|
+
/**
|
|
825
|
+
* `cascade-drop` lays its reels out on a different clock from the tape styles: a reel is a
|
|
826
|
+
* sequence of per-cell arrivals, not one deceleration. Overwrite `stopTime` with the moment the
|
|
827
|
+
* reel has fully landed and fill in `cellStopTimes`, so `plan()` stays the single source of truth
|
|
828
|
+
* for WHEN anything happens — `_runDrop` below only executes these numbers.
|
|
829
|
+
*/
|
|
830
|
+
private _planDrop;
|
|
794
831
|
/** Execute the spin for every reel concurrently. */
|
|
795
832
|
run(data: SpinData, opts?: SpinRunOpts): Promise<void>;
|
|
796
833
|
private _runReel;
|
|
@@ -1135,4 +1172,4 @@ declare class MultiplierAccumulator {
|
|
|
1135
1172
|
}
|
|
1136
1173
|
|
|
1137
1174
|
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, perReelValue, pickTier, resolveGeometry, resolveGridGeometry, resolveReelConfig, tierIndexAtValue, valueAt, waysCount };
|
|
1138
|
-
export type { AnimatedSymbolConfig, AnticipationConfig, AnticipationDecision, AnticipationOverride, 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, PerReel, 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, StepRunOpts, StickyConfig, StopMode, StopOrder, SymbolCellConfig, SymbolResolver, SymbolTextures, SymbolView, TransformConfig, TumbleStep, WalkingWildConfig, WinConfig, WinTier };
|
|
1175
|
+
export type { AnimatedSymbolConfig, AnticipationConfig, AnticipationDecision, AnticipationOverride, BigWinOverlayConfig, BlurConfig, CarryPolicy, CascadeAnim, CascadeConfig, CascadeStepData, CascadeTimings, CellData, CellFrameStyle, CellSizeSpec, CellState, CountUpConfig, CreateReelSystemOptions, DecorationConfig, DeepPartial, DropOrder, DropSequence, EasingName, EvaluationMode, ExpandingWildConfig, FeatureContext, FeatureKey, FeaturesConfig, GeometryInput, GiantConfig, GridConfig, HoldAndSpinConfig, Intensity, MotionConfig, MotionStyle, MultiplierConfig, MysteryConfig, NudgeConfig, PerReel, 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, StepRunOpts, StickyConfig, StopMode, StopOrder, SymbolCellConfig, SymbolResolver, SymbolTextures, SymbolView, TransformConfig, TumbleStep, WalkingWildConfig, WinConfig, WinTier };
|
package/dist/slot.esm.js
CHANGED
|
@@ -1129,6 +1129,8 @@ const DEFAULT_REEL_CONFIG = {
|
|
|
1129
1129
|
cellStagger: 24,
|
|
1130
1130
|
reelStaggerFactor: 0.4,
|
|
1131
1131
|
dropFallFactor: 0.6,
|
|
1132
|
+
dropOrder: 'top-down',
|
|
1133
|
+
dropSequence: 'parallel',
|
|
1132
1134
|
},
|
|
1133
1135
|
anticipation: {
|
|
1134
1136
|
enabled: false,
|
|
@@ -1569,6 +1571,7 @@ class SpinEngine {
|
|
|
1569
1571
|
const order = (reel) => (this._cfg.stopOrder === 'rtl' ? cols - 1 - reel : reel);
|
|
1570
1572
|
const anticipate = new Set(opts?.anticipateReels ?? []);
|
|
1571
1573
|
const defer = new Set(opts?.deferReveal ?? []);
|
|
1574
|
+
const holds = [];
|
|
1572
1575
|
const out = [];
|
|
1573
1576
|
for (let reel = 0; reel < cols; reel++) {
|
|
1574
1577
|
const idx = order(reel);
|
|
@@ -1584,8 +1587,9 @@ class SpinEngine {
|
|
|
1584
1587
|
else
|
|
1585
1588
|
stopTime = (this._cfg.spinUp + this._cfg.hold + idx * this._cfg.stopStagger) * f;
|
|
1586
1589
|
const isAnticipated = anticipate.has(reel);
|
|
1587
|
-
|
|
1588
|
-
|
|
1590
|
+
const hold = isAnticipated ? perReelValue(opts?.anticipateHoldMs, reel, 0) * f : 0;
|
|
1591
|
+
stopTime += hold;
|
|
1592
|
+
holds[reel] = hold;
|
|
1589
1593
|
const speed = isAnticipated ? perReelValue(opts?.anticipateSlowdown, reel, 1) : 1;
|
|
1590
1594
|
out.push({
|
|
1591
1595
|
reel,
|
|
@@ -1597,8 +1601,48 @@ class SpinEngine {
|
|
|
1597
1601
|
deferred: defer.has(reel),
|
|
1598
1602
|
});
|
|
1599
1603
|
}
|
|
1604
|
+
if (this._cfg.style === 'cascade-drop')
|
|
1605
|
+
this._planDrop(out, holds, f, order);
|
|
1600
1606
|
return out;
|
|
1601
1607
|
}
|
|
1608
|
+
/**
|
|
1609
|
+
* `cascade-drop` lays its reels out on a different clock from the tape styles: a reel is a
|
|
1610
|
+
* sequence of per-cell arrivals, not one deceleration. Overwrite `stopTime` with the moment the
|
|
1611
|
+
* reel has fully landed and fill in `cellStopTimes`, so `plan()` stays the single source of truth
|
|
1612
|
+
* for WHEN anything happens — `_runDrop` below only executes these numbers.
|
|
1613
|
+
*/
|
|
1614
|
+
_planDrop(out, holds, f, order) {
|
|
1615
|
+
const bottomUp = this._cfg.dropOrder === 'bottom-up';
|
|
1616
|
+
// walk the reels in STOP order, so `stopOrder: 'rtl'` reverses the drop as well
|
|
1617
|
+
const byPosition = [];
|
|
1618
|
+
for (let reel = 0; reel < out.length; reel++)
|
|
1619
|
+
byPosition[order(reel)] = reel;
|
|
1620
|
+
// the position from which reels stop overlapping and start queueing behind each other
|
|
1621
|
+
const chainFrom = this._cfg.dropSequence === 'chained'
|
|
1622
|
+
? 0
|
|
1623
|
+
: this._cfg.dropSequence === 'chained-when-anticipated'
|
|
1624
|
+
? byPosition.findIndex((reel) => out[reel].anticipated)
|
|
1625
|
+
: -1;
|
|
1626
|
+
let previousEnd = 0;
|
|
1627
|
+
for (let position = 0; position < byPosition.length; position++) {
|
|
1628
|
+
const p = out[byPosition[position]];
|
|
1629
|
+
const rows = this._grid.rowsOf(p.reel);
|
|
1630
|
+
const scale = f * p.slowdown;
|
|
1631
|
+
const gap = this._cfg.stopStagger * this._cfg.reelStaggerFactor * scale;
|
|
1632
|
+
const fall = this._cfg.spinUp * this._cfg.dropFallFactor * scale;
|
|
1633
|
+
// by formula while reels may overlap; queued behind the previous reel once the chain starts
|
|
1634
|
+
const chained = chainFrom >= 0 && position > 0 && position >= chainFrom;
|
|
1635
|
+
const start = (chained ? previousEnd + gap : position * gap) + (holds[p.reel] ?? 0);
|
|
1636
|
+
const cells = [];
|
|
1637
|
+
for (let i = 0; i < rows; i++) {
|
|
1638
|
+
const row = bottomUp ? rows - 1 - i : i;
|
|
1639
|
+
cells[row] = start + i * this._cfg.cellStagger * scale + fall;
|
|
1640
|
+
}
|
|
1641
|
+
p.cellStopTimes = cells;
|
|
1642
|
+
p.stopTime = cells.length ? Math.max(...cells) : start;
|
|
1643
|
+
previousEnd = p.stopTime;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1602
1646
|
/** Execute the spin for every reel concurrently. */
|
|
1603
1647
|
async run(data, opts) {
|
|
1604
1648
|
this._killed = false;
|
|
@@ -1740,6 +1784,8 @@ class SpinEngine {
|
|
|
1740
1784
|
}
|
|
1741
1785
|
const step = this._grid.cellPosition(p.reel, 1).y - this._grid.cellPosition(p.reel, 0).y;
|
|
1742
1786
|
const slow = this.slowOf(p); // anticipation drops the reel in more slowly
|
|
1787
|
+
const fall = this._cfg.spinUp * this._cfg.dropFallFactor * f * slow;
|
|
1788
|
+
const schedule = p.cellStopTimes ?? [];
|
|
1743
1789
|
await Promise.all(Array.from({ length: rows }, (_, r) => r).map(async (r) => {
|
|
1744
1790
|
if (this._killed)
|
|
1745
1791
|
return;
|
|
@@ -1749,13 +1795,11 @@ class SpinEngine {
|
|
|
1749
1795
|
cell.setData(data);
|
|
1750
1796
|
cell.position.set(to.x, to.y - step * (rows + 1));
|
|
1751
1797
|
cell.alpha = 1;
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
f *
|
|
1755
|
-
slow;
|
|
1798
|
+
// the plan says WHEN this cell seats; back off the fall to get when it must let go
|
|
1799
|
+
const delay = Math.max(0, (schedule[r] ?? fall) - fall);
|
|
1756
1800
|
if (delay)
|
|
1757
1801
|
await Tween.delay(delay);
|
|
1758
|
-
await Tween.to(cell, { 'position.y': to.y },
|
|
1802
|
+
await Tween.to(cell, { 'position.y': to.y }, fall, easingByName(this._cfg.settle.easing));
|
|
1759
1803
|
// the impact frame — fired before the squash so a game can sync its own hit feedback
|
|
1760
1804
|
opts?.onCellSeated?.(p.reel, r, data);
|
|
1761
1805
|
await this._squashCell(cell, f);
|