@react-shimeji/core 0.3.0 → 0.3.2

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/index.js CHANGED
@@ -654,21 +654,25 @@ var BehaviorController = class {
654
654
  };
655
655
 
656
656
  // src/physics.ts
657
- var BORDER_TOLERANCE = 0.999999;
657
+ var BORDER_TOLERANCE = 1 - Number.EPSILON / 2;
658
+ function isWithinSpan(value, minimum, maximum, tolerance) {
659
+ const distance = value < minimum ? minimum - value : value > maximum ? value - maximum : 0;
660
+ return distance <= tolerance;
661
+ }
658
662
  function clamp(value, minimum, maximum) {
659
663
  return Math.min(Math.max(value, minimum), maximum);
660
664
  }
661
665
  function isOnTop(point, rectangle, tolerance = BORDER_TOLERANCE) {
662
- return point.x >= rectangle.x - tolerance && point.x <= rectangle.x + rectangle.width + tolerance && Math.abs(point.y - rectangle.y) <= tolerance;
666
+ return isWithinSpan(point.x, rectangle.x, rectangle.x + rectangle.width, tolerance) && Math.abs(point.y - rectangle.y) <= tolerance;
663
667
  }
664
668
  function isOnBottom(point, rectangle, tolerance = BORDER_TOLERANCE) {
665
- return point.x >= rectangle.x - tolerance && point.x <= rectangle.x + rectangle.width + tolerance && Math.abs(point.y - rectangle.y - rectangle.height) <= tolerance;
669
+ return isWithinSpan(point.x, rectangle.x, rectangle.x + rectangle.width, tolerance) && Math.abs(point.y - rectangle.y - rectangle.height) <= tolerance;
666
670
  }
667
671
  function isOnLeft(point, rectangle, tolerance = BORDER_TOLERANCE) {
668
- return point.y >= rectangle.y - tolerance && point.y <= rectangle.y + rectangle.height + tolerance && Math.abs(point.x - rectangle.x) <= tolerance;
672
+ return isWithinSpan(point.y, rectangle.y, rectangle.y + rectangle.height, tolerance) && Math.abs(point.x - rectangle.x) <= tolerance;
669
673
  }
670
674
  function isOnRight(point, rectangle, tolerance = BORDER_TOLERANCE) {
671
- return point.y >= rectangle.y - tolerance && point.y <= rectangle.y + rectangle.height + tolerance && Math.abs(point.x - rectangle.x - rectangle.width) <= tolerance;
675
+ return isWithinSpan(point.y, rectangle.y, rectangle.y + rectangle.height, tolerance) && Math.abs(point.x - rectangle.x - rectangle.width) <= tolerance;
672
676
  }
673
677
  function isOnBorder(state, bounds, border, platform) {
674
678
  if (!border) return true;
@@ -1586,6 +1590,7 @@ function environmentRectangle(bounds) {
1586
1590
  };
1587
1591
  }
1588
1592
  var PLATFORM_NEARBY_DISTANCE = 400;
1593
+ var PLATFORM_EDGE_TOLERANCE = 2;
1589
1594
  function distanceToRectangle(point, rectangle) {
1590
1595
  const dx = Math.max(rectangle.x - point.x, 0, point.x - rectangle.x - rectangle.width);
1591
1596
  const dy = Math.max(rectangle.y - point.y, 0, point.y - rectangle.y - rectangle.height);
@@ -1616,6 +1621,7 @@ var Mascot = class {
1616
1621
  this.domHandle = dom.createMascot(spec, id, options.mascotClassName || void 0);
1617
1622
  this.frameDuration = options.frameDuration;
1618
1623
  this.random = options.random ?? Math.random;
1624
+ this.forceInitialFall = spawnOptions.behaviorName === void 0;
1619
1625
  this.behavior = new BehaviorController(spec, options.random);
1620
1626
  this.actions = new ActionExecutor(spec, this.state, options, {
1621
1627
  spawn: (position, characterId) => this.callbacks.spawn(characterId ?? this.spec.id, position),
@@ -1645,6 +1651,7 @@ var Mascot = class {
1645
1651
  accumulatedMs = 0;
1646
1652
  frameDuration;
1647
1653
  random;
1654
+ forceInitialFall;
1648
1655
  /** Advances behavior, animation, physics, and rendering by one clock tick. */
1649
1656
  tick(deltaMs, bounds, platforms = []) {
1650
1657
  if (this.destroyed) return;
@@ -1684,7 +1691,7 @@ var Mascot = class {
1684
1691
  for (let guard = 0; guard < 32 && !this.destroyed; guard += 1) {
1685
1692
  const environment = this.createEnvironment(bounds, platforms);
1686
1693
  if (!this.currentBehavior) {
1687
- this.currentBehavior = initial ? this.behavior.selectInitial(environment, this.state.behaviorName) : this.selectNextBehavior(environment, bounds);
1694
+ this.currentBehavior = initial ? this.forceInitialFall ? this.findFallBehavior() ?? this.behavior.selectInitial(environment) : this.behavior.selectInitial(environment, this.state.behaviorName) : this.selectNextBehavior(environment, bounds);
1688
1695
  initial = false;
1689
1696
  if (!this.currentBehavior) this.currentBehavior = this.findFallBehavior();
1690
1697
  if (!this.currentBehavior || !this.startBehavior(environment, bounds, platforms)) return;
@@ -1703,7 +1710,7 @@ var Mascot = class {
1703
1710
  if (result === "lost-ground") {
1704
1711
  this.state.dragging = false;
1705
1712
  this.actions.cancel();
1706
- this.currentBehavior = this.findFallBehavior();
1713
+ this.currentBehavior = this.selectEdgeBehavior(bounds, platforms);
1707
1714
  if (this.currentBehavior) this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms);
1708
1715
  } else if (result === "complete") {
1709
1716
  this.currentBehavior = this.selectNextBehavior(this.createEnvironment(bounds, platforms), bounds);
@@ -1729,14 +1736,37 @@ var Mascot = class {
1729
1736
  findFallBehavior() {
1730
1737
  return this.behavior.force("Fall") ?? this.behavior.force("\u843D\u4E0B\u3059\u308B");
1731
1738
  }
1732
- selectNextBehavior(environment, bounds) {
1739
+ selectNextBehavior(environment, bounds, relocateOnFallback = true) {
1733
1740
  const selected = this.behavior.selectNext(environment);
1734
- if (this.behavior.usedFallback()) {
1741
+ if (relocateOnFallback && this.behavior.usedFallback()) {
1735
1742
  this.state.x = Math.trunc(bounds.x + this.random() * bounds.width);
1736
1743
  this.state.y = bounds.y - 256;
1737
1744
  }
1738
1745
  return selected;
1739
1746
  }
1747
+ selectEdgeBehavior(bounds, platforms) {
1748
+ const original = { x: this.state.x, y: this.state.y };
1749
+ const platform = platforms.find((candidate) => candidate.element === this.activePlatformElement);
1750
+ if (platform) {
1751
+ const left = platform.x;
1752
+ const right = platform.x + platform.width;
1753
+ const top = platform.y;
1754
+ const bottom = platform.y + platform.height;
1755
+ if (Math.abs(this.state.y - top) <= PLATFORM_EDGE_TOLERANCE || Math.abs(this.state.y - bottom) <= PLATFORM_EDGE_TOLERANCE) {
1756
+ if (this.state.x < left) this.state.x = left;
1757
+ else if (this.state.x > right) this.state.x = right;
1758
+ } else if (Math.abs(this.state.x - left) <= PLATFORM_EDGE_TOLERANCE || Math.abs(this.state.x - right) <= PLATFORM_EDGE_TOLERANCE) {
1759
+ if (this.state.y < top) this.state.y = top;
1760
+ else if (this.state.y > bottom) this.state.y = bottom;
1761
+ }
1762
+ }
1763
+ const selected = this.selectNextBehavior(this.createEnvironment(bounds, platforms), bounds, false);
1764
+ if (this.behavior.usedFallback() || selected?.name === "Fall" || selected?.name === "\u843D\u4E0B\u3059\u308B") {
1765
+ this.state.x = original.x;
1766
+ this.state.y = original.y;
1767
+ }
1768
+ return selected ?? this.findFallBehavior();
1769
+ }
1740
1770
  createEnvironment(bounds, platforms = this.platforms) {
1741
1771
  const workArea = environmentRectangle(bounds);
1742
1772
  const inactive = environmentRectangle({ x: -100, y: -100, width: 0, height: 0 });
@@ -1767,7 +1797,7 @@ var Mascot = class {
1767
1797
  }
1768
1798
  const anchor = this.state;
1769
1799
  const current = platforms.find((platform) => platform.element === this.activePlatformElement);
1770
- if (current && (isOnTop(anchor, current, 2) || isOnBottom(anchor, current, 2) || isOnLeft(anchor, current, 2) || isOnRight(anchor, current, 2))) return current;
1800
+ if (current && (isOnTop(anchor, current, PLATFORM_EDGE_TOLERANCE) || isOnBottom(anchor, current, PLATFORM_EDGE_TOLERANCE) || isOnLeft(anchor, current, PLATFORM_EDGE_TOLERANCE) || isOnRight(anchor, current, PLATFORM_EDGE_TOLERANCE))) return current;
1771
1801
  if (this.state.vy >= 0) {
1772
1802
  const landingPlatform = platforms.filter((platform) => anchor.x >= platform.x && anchor.x <= platform.x + platform.width && platform.y >= anchor.y - 1).sort((left, right) => left.y - right.y)[0];
1773
1803
  if (landingPlatform) {
@@ -2020,9 +2050,13 @@ var ShimejiEngine = class {
2020
2050
  if (!spec) throw new Error(`Character '${characterId}' is not registered`);
2021
2051
  const { bounds, platforms } = this.readFrameGeometry();
2022
2052
  const random = this.options.random ?? Math.random;
2053
+ const randomX = Math.trunc(bounds.x + random() * bounds.width);
2054
+ const spawnInset = Math.min(2, bounds.width / 2);
2023
2055
  const spawnOptions = {
2024
2056
  ...position,
2025
- x: position.x ?? Math.trunc(bounds.x + random() * bounds.width),
2057
+ // Fall treats the wall in the facing direction as ground. Keep implicit
2058
+ // spawns clear of both side-wall tolerances so even random() === 0 falls.
2059
+ x: position.x ?? Math.min(Math.max(randomX, bounds.x + spawnInset), bounds.x + bounds.width - spawnInset),
2026
2060
  y: position.y ?? bounds.y + 2
2027
2061
  };
2028
2062
  const id = `shimeji-${this.nextMascotId++}`;