@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.d.cts CHANGED
@@ -422,11 +422,14 @@ declare class BehaviorController {
422
422
  selectInitial(environment: MascotEnvironment, requestedName?: string): BehaviorDefinition | undefined;
423
423
  /** Selects the weighted transition following the current behavior. */
424
424
  selectNext(environment: MascotEnvironment): BehaviorDefinition | undefined;
425
+ /** Tries a weighted transition from a subset without changing history when none applies. */
426
+ trySelectNext(environment: MascotEnvironment, predicate: (behavior: BehaviorDefinition) => boolean): BehaviorDefinition | undefined;
425
427
  /** Whether the most recent transition had no effective weighted candidate. */
426
428
  usedFallback(): boolean;
427
429
  /** Replaces selection history so an external interaction can force a behavior. */
428
430
  force(name: string): BehaviorDefinition | undefined;
429
431
  private choose;
432
+ private nextPool;
430
433
  private resolve;
431
434
  private findFallBehavior;
432
435
  }
package/dist/index.d.ts CHANGED
@@ -422,11 +422,14 @@ declare class BehaviorController {
422
422
  selectInitial(environment: MascotEnvironment, requestedName?: string): BehaviorDefinition | undefined;
423
423
  /** Selects the weighted transition following the current behavior. */
424
424
  selectNext(environment: MascotEnvironment): BehaviorDefinition | undefined;
425
+ /** Tries a weighted transition from a subset without changing history when none applies. */
426
+ trySelectNext(environment: MascotEnvironment, predicate: (behavior: BehaviorDefinition) => boolean): BehaviorDefinition | undefined;
425
427
  /** Whether the most recent transition had no effective weighted candidate. */
426
428
  usedFallback(): boolean;
427
429
  /** Replaces selection history so an external interaction can force a behavior. */
428
430
  force(name: string): BehaviorDefinition | undefined;
429
431
  private choose;
432
+ private nextPool;
430
433
  private resolve;
431
434
  private findFallBehavior;
432
435
  }
package/dist/index.js CHANGED
@@ -615,12 +615,17 @@ var BehaviorController = class {
615
615
  }
616
616
  /** Selects the weighted transition following the current behavior. */
617
617
  selectNext(environment) {
618
- const next = this.previous?.nextBehaviors ?? [];
619
- const pool = this.previous && this.previous.nextAdditive === false ? next : [...this.spec.behaviors, ...next];
620
- const selected = this.choose(pool, environment);
618
+ const selected = this.choose(this.nextPool(), environment);
621
619
  this.fallbackSelected = selected === void 0;
622
620
  return this.previous = selected ?? this.findFallBehavior();
623
621
  }
622
+ /** Tries a weighted transition from a subset without changing history when none applies. */
623
+ trySelectNext(environment, predicate) {
624
+ const selected = this.choose(this.nextPool().filter(predicate), environment);
625
+ if (!selected) return void 0;
626
+ this.fallbackSelected = false;
627
+ return this.previous = selected;
628
+ }
624
629
  /** Whether the most recent transition had no effective weighted candidate. */
625
630
  usedFallback() {
626
631
  return this.fallbackSelected;
@@ -636,6 +641,10 @@ var BehaviorController = class {
636
641
  const chosen = selectWeighted(applicable, (behavior) => behavior.frequency, this.random);
637
642
  return chosen ? this.resolve(chosen) : void 0;
638
643
  }
644
+ nextPool() {
645
+ const next = this.previous?.nextBehaviors ?? [];
646
+ return this.previous && this.previous.nextAdditive === false ? next : [...this.spec.behaviors, ...next];
647
+ }
639
648
  resolve(behavior) {
640
649
  if (behavior.type !== "Reference") return behavior;
641
650
  const target = this.spec.behaviors.find((candidate) => candidate.type === "Behavior" && candidate.name === behavior.name);
@@ -1591,6 +1600,13 @@ function environmentRectangle(bounds) {
1591
1600
  }
1592
1601
  var PLATFORM_NEARBY_DISTANCE = 400;
1593
1602
  var PLATFORM_EDGE_TOLERANCE = 2;
1603
+ var MASCOT_HITBOX_MAX_WIDTH = 32;
1604
+ var MASCOT_HITBOX_MAX_HEIGHT = 64;
1605
+ var MASCOT_COLLISION_LOOKAHEAD = 4;
1606
+ var IE_BEHAVIOR_NAME_PATTERN = /wall|climb|crawl|壁|登|よじ/i;
1607
+ function isIEBehavior(behavior) {
1608
+ 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));
1609
+ }
1594
1610
  function distanceToRectangle(point, rectangle) {
1595
1611
  const dx = Math.max(rectangle.x - point.x, 0, point.x - rectangle.x - rectangle.width);
1596
1612
  const dy = Math.max(rectangle.y - point.y, 0, point.y - rectangle.y - rectangle.height);
@@ -1653,7 +1669,7 @@ var Mascot = class {
1653
1669
  random;
1654
1670
  forceInitialFall;
1655
1671
  /** Advances behavior, animation, physics, and rendering by one clock tick. */
1656
- tick(deltaMs, bounds, platforms = []) {
1672
+ tick(deltaMs, bounds, platforms = [], siblings = []) {
1657
1673
  if (this.destroyed) return;
1658
1674
  this.platforms = platforms;
1659
1675
  try {
@@ -1661,7 +1677,7 @@ var Mascot = class {
1661
1677
  this.accumulatedMs += Math.max(0, deltaMs);
1662
1678
  while (this.accumulatedMs >= this.frameDuration && !this.destroyed) {
1663
1679
  this.accumulatedMs -= this.frameDuration;
1664
- this.legacyTick(bounds, platforms);
1680
+ this.legacyTick(bounds, platforms, siblings);
1665
1681
  }
1666
1682
  } catch (error) {
1667
1683
  this.callbacks.error(error instanceof Error ? error : new Error(String(error)));
@@ -1674,6 +1690,24 @@ var Mascot = class {
1674
1690
  snapshot() {
1675
1691
  return { ...this.state };
1676
1692
  }
1693
+ /** Returns the mascot's compact, feet-aligned behavioral collision box. */
1694
+ collisionBox() {
1695
+ const sprite = this.spec.sprites[this.state.sprite];
1696
+ const spriteWidth = typeof sprite === "object" && sprite.width !== void 0 ? sprite.width : 128;
1697
+ const spriteHeight = typeof sprite === "object" && sprite.height !== void 0 ? sprite.height : 128;
1698
+ const width = Math.min(spriteWidth, MASCOT_HITBOX_MAX_WIDTH);
1699
+ const height = Math.min(spriteHeight, MASCOT_HITBOX_MAX_HEIGHT);
1700
+ const anchorX = this.state.lookRight ? spriteWidth - this.state.anchorX : this.state.anchorX;
1701
+ const visualLeft = this.state.x - anchorX;
1702
+ const visualTop = this.state.y - this.state.anchorY;
1703
+ return {
1704
+ id: this.id,
1705
+ x: visualLeft + (spriteWidth - width) / 2,
1706
+ y: visualTop + spriteHeight - height,
1707
+ width,
1708
+ height
1709
+ };
1710
+ }
1677
1711
  /** Removes listeners, DOM nodes, and object URLs owned by this mascot. */
1678
1712
  destroy() {
1679
1713
  if (this.destroyed) return;
@@ -1702,11 +1736,13 @@ var Mascot = class {
1702
1736
  if (!this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms)) return;
1703
1737
  }
1704
1738
  }
1705
- legacyTick(bounds, platforms) {
1739
+ legacyTick(bounds, platforms, siblings) {
1706
1740
  this.ensureBehavior(bounds, platforms, false);
1707
1741
  if (!this.currentBehavior) return;
1742
+ const previous = { x: this.state.x, y: this.state.y };
1708
1743
  const result = this.actions.step(this.createEnvironment(bounds, platforms), bounds, platforms);
1709
1744
  if (this.destroyed) return;
1745
+ this.avoidSiblingCollision(previous, bounds, platforms, siblings);
1710
1746
  if (result === "lost-ground") {
1711
1747
  this.state.dragging = false;
1712
1748
  this.actions.cancel();
@@ -1724,6 +1760,31 @@ var Mascot = class {
1724
1760
  if (this.currentBehavior) this.startBehavior(this.createEnvironment(bounds, platforms), bounds, platforms);
1725
1761
  }
1726
1762
  }
1763
+ avoidSiblingCollision(previous, bounds, platforms, siblings) {
1764
+ const dx = this.state.x - previous.x;
1765
+ if (this.state.dragging || dx === 0 || this.state.y !== previous.y) return;
1766
+ const onHorizontalSurface = isOnFloor(previous, bounds, platforms) || isOnTop(previous, bounds) || platforms.some((platform) => isOnBottom(previous, platform));
1767
+ if (!onHorizontalSurface) return;
1768
+ const currentBox = this.collisionBox();
1769
+ const offsetX = currentBox.x - this.state.x;
1770
+ const previousBox = { ...currentBox, x: previous.x + offsetX };
1771
+ const movingRight = dx > 0;
1772
+ const sweptLeft = Math.min(previousBox.x, currentBox.x) - (movingRight ? 0 : MASCOT_COLLISION_LOOKAHEAD);
1773
+ const sweptRight = Math.max(previousBox.x + previousBox.width, currentBox.x + currentBox.width) + (movingRight ? MASCOT_COLLISION_LOOKAHEAD : 0);
1774
+ const previousCenterX = previousBox.x + previousBox.width / 2;
1775
+ const collision = siblings.some((sibling) => {
1776
+ if (sibling.id === this.id) return false;
1777
+ const siblingCenterX = sibling.x + sibling.width / 2;
1778
+ const isAhead = movingRight ? siblingCenterX >= previousCenterX : siblingCenterX <= previousCenterX;
1779
+ const overlapsVertically = currentBox.y < sibling.y + sibling.height && sibling.y < currentBox.y + currentBox.height;
1780
+ const crossesHorizontally = sweptLeft <= sibling.x + sibling.width && sibling.x <= sweptRight;
1781
+ return isAhead && overlapsVertically && crossesHorizontally;
1782
+ });
1783
+ if (!collision) return;
1784
+ this.state.x = previous.x;
1785
+ this.state.vx = 0;
1786
+ this.state.lookRight = !this.state.lookRight;
1787
+ }
1727
1788
  isOutsideVisibleBounds(bounds) {
1728
1789
  const sprite = this.spec.sprites[this.state.sprite];
1729
1790
  const width = typeof sprite === "object" && "width" in sprite && sprite.width !== void 0 ? sprite.width : 128;
@@ -1760,7 +1821,9 @@ var Mascot = class {
1760
1821
  else if (this.state.y > bottom) this.state.y = bottom;
1761
1822
  }
1762
1823
  }
1763
- const selected = this.selectNextBehavior(this.createEnvironment(bounds, platforms), bounds, false);
1824
+ const environment = this.createEnvironment(bounds, platforms);
1825
+ const interruptedBehavior = this.currentBehavior;
1826
+ const selected = this.behavior.trySelectNext(environment, (candidate) => candidate.name !== interruptedBehavior?.name && isIEBehavior(candidate)) ?? this.selectNextBehavior(environment, bounds, false);
1764
1827
  if (this.behavior.usedFallback() || selected?.name === "Fall" || selected?.name === "\u843D\u4E0B\u3059\u308B") {
1765
1828
  this.state.x = original.x;
1766
1829
  this.state.y = original.y;
@@ -2143,7 +2206,9 @@ var ShimejiEngine = class {
2143
2206
  this.lastFrameTime = timestamp;
2144
2207
  const delta = Math.max(0, Math.min(rawDelta, this.options.maxDeltaTime));
2145
2208
  const { bounds, platforms } = this.readFrameGeometry();
2146
- for (const mascot of [...this.mascots.values()]) mascot.tick(delta, bounds, platforms);
2209
+ const mascots = [...this.mascots.values()];
2210
+ const collisionBoxes = mascots.map((mascot) => mascot.collisionBox());
2211
+ for (const mascot of mascots) mascot.tick(delta, bounds, platforms, collisionBoxes);
2147
2212
  this.emitState();
2148
2213
  this.animationFrame = this.container.ownerDocument.defaultView?.requestAnimationFrame(this.onAnimationFrame);
2149
2214
  };