@react-shimeji/core 0.3.2 → 0.3.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/index.cjs CHANGED
@@ -662,12 +662,17 @@ var BehaviorController = class {
662
662
  }
663
663
  /** Selects the weighted transition following the current behavior. */
664
664
  selectNext(environment) {
665
- const next = this.previous?.nextBehaviors ?? [];
666
- const pool = this.previous && this.previous.nextAdditive === false ? next : [...this.spec.behaviors, ...next];
667
- const selected = this.choose(pool, environment);
665
+ const selected = this.choose(this.nextPool(), environment);
668
666
  this.fallbackSelected = selected === void 0;
669
667
  return this.previous = selected ?? this.findFallBehavior();
670
668
  }
669
+ /** Tries a weighted transition from a subset without changing history when none applies. */
670
+ trySelectNext(environment, predicate) {
671
+ const selected = this.choose(this.nextPool().filter(predicate), environment);
672
+ if (!selected) return void 0;
673
+ this.fallbackSelected = false;
674
+ return this.previous = selected;
675
+ }
671
676
  /** Whether the most recent transition had no effective weighted candidate. */
672
677
  usedFallback() {
673
678
  return this.fallbackSelected;
@@ -683,6 +688,10 @@ var BehaviorController = class {
683
688
  const chosen = selectWeighted(applicable, (behavior) => behavior.frequency, this.random);
684
689
  return chosen ? this.resolve(chosen) : void 0;
685
690
  }
691
+ nextPool() {
692
+ const next = this.previous?.nextBehaviors ?? [];
693
+ return this.previous && this.previous.nextAdditive === false ? next : [...this.spec.behaviors, ...next];
694
+ }
686
695
  resolve(behavior) {
687
696
  if (behavior.type !== "Reference") return behavior;
688
697
  const target = this.spec.behaviors.find((candidate) => candidate.type === "Behavior" && candidate.name === behavior.name);
@@ -1638,6 +1647,13 @@ function environmentRectangle(bounds) {
1638
1647
  }
1639
1648
  var PLATFORM_NEARBY_DISTANCE = 400;
1640
1649
  var PLATFORM_EDGE_TOLERANCE = 2;
1650
+ var MASCOT_HITBOX_MAX_WIDTH = 32;
1651
+ var MASCOT_HITBOX_MAX_HEIGHT = 64;
1652
+ var MASCOT_COLLISION_LOOKAHEAD = 4;
1653
+ var IE_BEHAVIOR_NAME_PATTERN = /wall|climb|crawl|壁|登|よじ/i;
1654
+ function isIEBehavior(behavior) {
1655
+ return behavior.conditions.some((condition) => /activeIE/i.test(condition)) || behavior.name.includes("IE") || behavior.name.includes("\uFF29\uFF25") || IE_BEHAVIOR_NAME_PATTERN.test(behavior.name) || behavior.actionName !== void 0 && (behavior.actionName.includes("IE") || behavior.actionName.includes("\uFF29\uFF25") || IE_BEHAVIOR_NAME_PATTERN.test(behavior.actionName));
1656
+ }
1641
1657
  function distanceToRectangle(point, rectangle) {
1642
1658
  const dx = Math.max(rectangle.x - point.x, 0, point.x - rectangle.x - rectangle.width);
1643
1659
  const dy = Math.max(rectangle.y - point.y, 0, point.y - rectangle.y - rectangle.height);
@@ -1700,7 +1716,7 @@ var Mascot = class {
1700
1716
  random;
1701
1717
  forceInitialFall;
1702
1718
  /** Advances behavior, animation, physics, and rendering by one clock tick. */
1703
- tick(deltaMs, bounds, platforms = []) {
1719
+ tick(deltaMs, bounds, platforms = [], siblings = []) {
1704
1720
  if (this.destroyed) return;
1705
1721
  this.platforms = platforms;
1706
1722
  try {
@@ -1708,7 +1724,7 @@ var Mascot = class {
1708
1724
  this.accumulatedMs += Math.max(0, deltaMs);
1709
1725
  while (this.accumulatedMs >= this.frameDuration && !this.destroyed) {
1710
1726
  this.accumulatedMs -= this.frameDuration;
1711
- this.legacyTick(bounds, platforms);
1727
+ this.legacyTick(bounds, platforms, siblings);
1712
1728
  }
1713
1729
  } catch (error) {
1714
1730
  this.callbacks.error(error instanceof Error ? error : new Error(String(error)));
@@ -1721,6 +1737,24 @@ var Mascot = class {
1721
1737
  snapshot() {
1722
1738
  return { ...this.state };
1723
1739
  }
1740
+ /** Returns the mascot's compact, feet-aligned behavioral collision box. */
1741
+ collisionBox() {
1742
+ const sprite = this.spec.sprites[this.state.sprite];
1743
+ const spriteWidth = typeof sprite === "object" && sprite.width !== void 0 ? sprite.width : 128;
1744
+ const spriteHeight = typeof sprite === "object" && sprite.height !== void 0 ? sprite.height : 128;
1745
+ const width = Math.min(spriteWidth, MASCOT_HITBOX_MAX_WIDTH);
1746
+ const height = Math.min(spriteHeight, MASCOT_HITBOX_MAX_HEIGHT);
1747
+ const anchorX = this.state.lookRight ? spriteWidth - this.state.anchorX : this.state.anchorX;
1748
+ const visualLeft = this.state.x - anchorX;
1749
+ const visualTop = this.state.y - this.state.anchorY;
1750
+ return {
1751
+ id: this.id,
1752
+ x: visualLeft + (spriteWidth - width) / 2,
1753
+ y: visualTop + spriteHeight - height,
1754
+ width,
1755
+ height
1756
+ };
1757
+ }
1724
1758
  /** Removes listeners, DOM nodes, and object URLs owned by this mascot. */
1725
1759
  destroy() {
1726
1760
  if (this.destroyed) return;
@@ -1749,11 +1783,13 @@ var Mascot = class {
1749
1783
  if (!this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms)) return;
1750
1784
  }
1751
1785
  }
1752
- legacyTick(bounds, platforms) {
1786
+ legacyTick(bounds, platforms, siblings) {
1753
1787
  this.ensureBehavior(bounds, platforms, false);
1754
1788
  if (!this.currentBehavior) return;
1789
+ const previous = { x: this.state.x, y: this.state.y };
1755
1790
  const result = this.actions.step(this.createEnvironment(bounds, platforms), bounds, platforms);
1756
1791
  if (this.destroyed) return;
1792
+ this.avoidSiblingCollision(previous, bounds, platforms, siblings);
1757
1793
  if (result === "lost-ground") {
1758
1794
  this.state.dragging = false;
1759
1795
  this.actions.cancel();
@@ -1771,6 +1807,31 @@ var Mascot = class {
1771
1807
  if (this.currentBehavior) this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms);
1772
1808
  }
1773
1809
  }
1810
+ avoidSiblingCollision(previous, bounds, platforms, siblings) {
1811
+ const dx = this.state.x - previous.x;
1812
+ if (this.state.dragging || dx === 0 || this.state.y !== previous.y) return;
1813
+ const onHorizontalSurface = isOnFloor(previous, bounds, platforms) || isOnTop(previous, bounds) || platforms.some((platform) => isOnBottom(previous, platform));
1814
+ if (!onHorizontalSurface) return;
1815
+ const currentBox = this.collisionBox();
1816
+ const offsetX = currentBox.x - this.state.x;
1817
+ const previousBox = { ...currentBox, x: previous.x + offsetX };
1818
+ const movingRight = dx > 0;
1819
+ const sweptLeft = Math.min(previousBox.x, currentBox.x) - (movingRight ? 0 : MASCOT_COLLISION_LOOKAHEAD);
1820
+ const sweptRight = Math.max(previousBox.x + previousBox.width, currentBox.x + currentBox.width) + (movingRight ? MASCOT_COLLISION_LOOKAHEAD : 0);
1821
+ const previousCenterX = previousBox.x + previousBox.width / 2;
1822
+ const collision = siblings.some((sibling) => {
1823
+ if (sibling.id === this.id) return false;
1824
+ const siblingCenterX = sibling.x + sibling.width / 2;
1825
+ const isAhead = movingRight ? siblingCenterX >= previousCenterX : siblingCenterX <= previousCenterX;
1826
+ const overlapsVertically = currentBox.y < sibling.y + sibling.height && sibling.y < currentBox.y + currentBox.height;
1827
+ const crossesHorizontally = sweptLeft <= sibling.x + sibling.width && sibling.x <= sweptRight;
1828
+ return isAhead && overlapsVertically && crossesHorizontally;
1829
+ });
1830
+ if (!collision) return;
1831
+ this.state.x = previous.x;
1832
+ this.state.vx = 0;
1833
+ this.state.lookRight = !this.state.lookRight;
1834
+ }
1774
1835
  isOutsideVisibleBounds(bounds) {
1775
1836
  const sprite = this.spec.sprites[this.state.sprite];
1776
1837
  const width = typeof sprite === "object" && "width" in sprite && sprite.width !== void 0 ? sprite.width : 128;
@@ -1807,7 +1868,9 @@ var Mascot = class {
1807
1868
  else if (this.state.y > bottom) this.state.y = bottom;
1808
1869
  }
1809
1870
  }
1810
- const selected = this.selectNextBehavior(this.createEnvironment(bounds, platforms), bounds, false);
1871
+ const environment = this.createEnvironment(bounds, platforms);
1872
+ const interruptedBehavior = this.currentBehavior;
1873
+ const selected = this.behavior.trySelectNext(environment, (candidate) => candidate.name !== interruptedBehavior?.name && isIEBehavior(candidate)) ?? this.selectNextBehavior(environment, bounds, false);
1811
1874
  if (this.behavior.usedFallback() || selected?.name === "Fall" || selected?.name === "\u843D\u4E0B\u3059\u308B") {
1812
1875
  this.state.x = original.x;
1813
1876
  this.state.y = original.y;
@@ -2190,7 +2253,9 @@ var ShimejiEngine = class {
2190
2253
  this.lastFrameTime = timestamp;
2191
2254
  const delta = Math.max(0, Math.min(rawDelta, this.options.maxDeltaTime));
2192
2255
  const { bounds, platforms } = this.readFrameGeometry();
2193
- for (const mascot of [...this.mascots.values()]) mascot.tick(delta, bounds, platforms);
2256
+ const mascots = [...this.mascots.values()];
2257
+ const collisionBoxes = mascots.map((mascot) => mascot.collisionBox());
2258
+ for (const mascot of mascots) mascot.tick(delta, bounds, platforms, collisionBoxes);
2194
2259
  this.emitState();
2195
2260
  this.animationFrame = this.container.ownerDocument.defaultView?.requestAnimationFrame(this.onAnimationFrame);
2196
2261
  };