ecspresso 0.13.4 → 0.14.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/dist/index.js +2 -2
- package/dist/index.js.map +3 -3
- package/dist/plugins/ai/behavior-tree.d.ts +369 -0
- package/dist/plugins/ai/behavior-tree.js +4 -0
- package/dist/plugins/ai/behavior-tree.js.map +10 -0
- package/dist/plugins/ai/detection.js +2 -2
- package/dist/plugins/ai/detection.js.map +2 -2
- package/dist/plugins/ai/flocking.js +2 -2
- package/dist/plugins/ai/flocking.js.map +2 -2
- package/dist/plugins/audio/audio.js +2 -2
- package/dist/plugins/audio/audio.js.map +2 -2
- package/dist/plugins/combat/health.js +2 -2
- package/dist/plugins/combat/health.js.map +2 -2
- package/dist/plugins/combat/projectile.js +2 -2
- package/dist/plugins/combat/projectile.js.map +2 -2
- package/dist/plugins/debug/diagnostics.js +3 -3
- package/dist/plugins/debug/diagnostics.js.map +2 -2
- package/dist/plugins/input/input.js +2 -2
- package/dist/plugins/input/input.js.map +2 -2
- package/dist/plugins/input/selection.js +2 -2
- package/dist/plugins/input/selection.js.map +2 -2
- package/dist/plugins/isometric/depth-sort.js +2 -2
- package/dist/plugins/isometric/depth-sort.js.map +2 -2
- package/dist/plugins/isometric/projection.js +2 -2
- package/dist/plugins/isometric/projection.js.map +2 -2
- package/dist/plugins/physics/collision.js +2 -2
- package/dist/plugins/physics/collision.js.map +2 -2
- package/dist/plugins/physics/collision3D.d.ts +83 -0
- package/dist/plugins/physics/collision3D.js +4 -0
- package/dist/plugins/physics/collision3D.js.map +13 -0
- package/dist/plugins/physics/physics2D.js +2 -2
- package/dist/plugins/physics/physics2D.js.map +2 -2
- package/dist/plugins/physics/physics3D.d.ts +140 -0
- package/dist/plugins/physics/physics3D.js +4 -0
- package/dist/plugins/physics/physics3D.js.map +11 -0
- package/dist/plugins/physics/steering.js +2 -2
- package/dist/plugins/physics/steering.js.map +2 -2
- package/dist/plugins/rendering/particles.js +2 -2
- package/dist/plugins/rendering/particles.js.map +2 -2
- package/dist/plugins/rendering/renderer2D.js +2 -2
- package/dist/plugins/rendering/renderer2D.js.map +3 -3
- package/dist/plugins/rendering/renderer3D.d.ts +226 -0
- package/dist/plugins/rendering/renderer3D.js +4052 -0
- package/dist/plugins/rendering/renderer3D.js.map +12 -0
- package/dist/plugins/rendering/sprite-animation.js +2 -2
- package/dist/plugins/rendering/sprite-animation.js.map +2 -2
- package/dist/plugins/scripting/coroutine.js +2 -2
- package/dist/plugins/scripting/coroutine.js.map +2 -2
- package/dist/plugins/scripting/state-machine.js +2 -2
- package/dist/plugins/scripting/state-machine.js.map +2 -2
- package/dist/plugins/scripting/timers.js +2 -2
- package/dist/plugins/scripting/timers.js.map +2 -2
- package/dist/plugins/scripting/tween.js +2 -2
- package/dist/plugins/scripting/tween.js.map +2 -2
- package/dist/plugins/spatial/bounds.js +2 -2
- package/dist/plugins/spatial/bounds.js.map +2 -2
- package/dist/plugins/spatial/camera.js +2 -2
- package/dist/plugins/spatial/camera.js.map +2 -2
- package/dist/plugins/spatial/camera3D.d.ts +92 -0
- package/dist/plugins/spatial/camera3D.js +4 -0
- package/dist/plugins/spatial/camera3D.js.map +10 -0
- package/dist/plugins/spatial/spatial-index.js +2 -2
- package/dist/plugins/spatial/spatial-index.js.map +3 -3
- package/dist/plugins/spatial/spatial-index3D.d.ts +80 -0
- package/dist/plugins/spatial/spatial-index3D.js +4 -0
- package/dist/plugins/spatial/spatial-index3D.js.map +11 -0
- package/dist/plugins/spatial/transform.js +2 -2
- package/dist/plugins/spatial/transform.js.map +2 -2
- package/dist/plugins/spatial/transform3D.d.ts +148 -0
- package/dist/plugins/spatial/transform3D.js +4 -0
- package/dist/plugins/spatial/transform3D.js.map +10 -0
- package/dist/utils/math.d.ts +65 -1
- package/dist/utils/narrowphase3D.d.ts +120 -0
- package/dist/utils/spatial-hash3D.d.ts +72 -0
- package/package.json +29 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"/**\n * Shared Narrowphase Module\n *\n * Provides contact-computing narrowphase tests and a generic collision\n * iteration pipeline used by both the collision plugin (event-only) and\n * the physics2D plugin (impulse response).\n */\n\nimport type { SpatialIndex } from './spatial-hash';\n\n// ==================== Contact ====================\n\n/**\n * Contact result from a narrowphase test. Normal points from A toward B.\n *\n * Narrowphase functions use this as an out-parameter: the caller owns the\n * struct, the function writes fields in place and returns `true` on hit.\n * The `onContact` callback in `detectCollisions` receives a shared module-\n * level instance — **subscribers must consume it synchronously and must not\n * retain the reference across frames**.\n */\nexport interface Contact {\n\tnormalX: number;\n\tnormalY: number;\n\t/** Penetration depth (positive = overlapping) */\n\tdepth: number;\n}\n\n/**\n * Module-level reusable Contact passed down from `detectCollisions` into\n * narrowphase tests and forwarded to the `onContact` callback. Reused across\n * every pair in every frame — zero allocation in the narrowphase hot path.\n */\nconst _sharedContact: Contact = { normalX: 0, normalY: 0, depth: 0 };\n\n// ==================== BaseColliderInfo ====================\n\n/** Collider shape discriminator for the flattened BaseColliderInfo layout. */\nexport const AABB_SHAPE = 0;\nexport const CIRCLE_SHAPE = 1;\nexport type ColliderShape = typeof AABB_SHAPE | typeof CIRCLE_SHAPE;\n\n/**\n * Minimum collider data shared by collision and physics bundles.\n *\n * Flat layout (no nested `aabb` / `circle` sub-objects): the `shape`\n * discriminator tells you whether to read `halfWidth`/`halfHeight`\n * (AABB) or `radius` (Circle). Unused fields are set to 0.\n *\n * This shape is pool-friendly — all fields are assigned in place each\n * frame without allocating nested objects.\n */\nexport interface BaseColliderInfo<L extends string = string> {\n\tentityId: number;\n\tx: number;\n\ty: number;\n\tlayer: L;\n\tcollidesWith: readonly L[];\n\tshape: ColliderShape;\n\thalfWidth: number;\n\thalfHeight: number;\n\tradius: number;\n}\n\n// ==================== Collider Construction ====================\n\n/**\n * Populate a `BaseColliderInfo` slot in place from raw component data.\n * Returns `true` if the slot was filled, `false` if the entity has no\n * collider (caller should skip it).\n *\n * If an entity has both AABB and circle colliders, AABB wins and only\n * the AABB offset is applied. This matches the dispatch precedence in\n * `computeContact`; the previous implementation stacked both offsets,\n * which was a bug.\n */\nexport function fillBaseColliderInfo<L extends string>(\n\tinfo: BaseColliderInfo<L>,\n\tentityId: number,\n\tx: number,\n\ty: number,\n\tlayer: L,\n\tcollidesWith: readonly L[],\n\taabb: { width: number; height: number; offsetX?: number; offsetY?: number } | undefined,\n\tcircle: { radius: number; offsetX?: number; offsetY?: number } | undefined,\n): boolean {\n\tinfo.entityId = entityId;\n\tinfo.layer = layer;\n\tinfo.collidesWith = collidesWith;\n\n\tif (aabb) {\n\t\tinfo.x = x + (aabb.offsetX ?? 0);\n\t\tinfo.y = y + (aabb.offsetY ?? 0);\n\t\tinfo.shape = AABB_SHAPE;\n\t\tinfo.halfWidth = aabb.width / 2;\n\t\tinfo.halfHeight = aabb.height / 2;\n\t\tinfo.radius = 0;\n\t\treturn true;\n\t}\n\n\tif (circle) {\n\t\tinfo.x = x + (circle.offsetX ?? 0);\n\t\tinfo.y = y + (circle.offsetY ?? 0);\n\t\tinfo.shape = CIRCLE_SHAPE;\n\t\tinfo.halfWidth = 0;\n\t\tinfo.halfHeight = 0;\n\t\tinfo.radius = circle.radius;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n// ==================== Spatial Index Lookup ====================\n\n/**\n * Retrieve the optional spatialIndex resource, returning undefined when absent.\n * Centralizes the cross-plugin typed lookup so individual plugins don't each\n * need to import SpatialIndex or repeat the tryGetResource pattern.\n */\nexport function tryGetSpatialIndex(\n\ttryGetResource: <T>(key: string) => T | undefined,\n): SpatialIndex | undefined {\n\treturn tryGetResource<SpatialIndex>('spatialIndex');\n}\n\n// ==================== Narrowphase Tests ====================\n\n/**\n * Write an AABB-AABB contact into `out`. Returns `true` if the shapes\n * overlap (out was filled), `false` otherwise.\n */\nexport function computeAABBvsAABB(\n\tax: number, ay: number, ahw: number, ahh: number,\n\tbx: number, by: number, bhw: number, bhh: number,\n\tout: Contact,\n): boolean {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst overlapX = (ahw + bhw) - Math.abs(dx);\n\tconst overlapY = (ahh + bhh) - Math.abs(dy);\n\n\tif (overlapX <= 0 || overlapY <= 0) return false;\n\n\tif (overlapX < overlapY) {\n\t\tout.normalX = dx >= 0 ? 1 : -1;\n\t\tout.normalY = 0;\n\t\tout.depth = overlapX;\n\t\treturn true;\n\t}\n\tout.normalX = 0;\n\tout.normalY = dy >= 0 ? 1 : -1;\n\tout.depth = overlapY;\n\treturn true;\n}\n\nexport function computeCircleVsCircle(\n\tax: number, ay: number, ar: number,\n\tbx: number, by: number, br: number,\n\tout: Contact,\n): boolean {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst distSq = dx * dx + dy * dy;\n\tconst radiusSum = ar + br;\n\n\tif (distSq >= radiusSum * radiusSum) return false;\n\n\tconst dist = Math.sqrt(distSq);\n\tif (dist === 0) {\n\t\tout.normalX = 1;\n\t\tout.normalY = 0;\n\t\tout.depth = radiusSum;\n\t\treturn true;\n\t}\n\tout.normalX = dx / dist;\n\tout.normalY = dy / dist;\n\tout.depth = radiusSum - dist;\n\treturn true;\n}\n\nexport function computeAABBvsCircle(\n\taabbX: number, aabbY: number, ahw: number, ahh: number,\n\tcircleX: number, circleY: number, radius: number,\n\tout: Contact,\n): boolean {\n\tconst closestX = Math.max(aabbX - ahw, Math.min(circleX, aabbX + ahw));\n\tconst closestY = Math.max(aabbY - ahh, Math.min(circleY, aabbY + ahh));\n\n\tconst dx = circleX - closestX;\n\tconst dy = circleY - closestY;\n\tconst distSq = dx * dx + dy * dy;\n\n\tif (distSq >= radius * radius) return false;\n\n\t// Circle center inside AABB\n\tif (distSq === 0) {\n\t\tconst pushLeft = (circleX - (aabbX - ahw));\n\t\tconst pushRight = ((aabbX + ahw) - circleX);\n\t\tconst pushUp = (circleY - (aabbY - ahh));\n\t\tconst pushDown = ((aabbY + ahh) - circleY);\n\t\tconst minPush = Math.min(pushLeft, pushRight, pushUp, pushDown);\n\n\t\tif (minPush === pushRight) {\n\t\t\tout.normalX = 1; out.normalY = 0; out.depth = pushRight + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushLeft) {\n\t\t\tout.normalX = -1; out.normalY = 0; out.depth = pushLeft + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushDown) {\n\t\t\tout.normalX = 0; out.normalY = 1; out.depth = pushDown + radius;\n\t\t\treturn true;\n\t\t}\n\t\tout.normalX = 0; out.normalY = -1; out.depth = pushUp + radius;\n\t\treturn true;\n\t}\n\n\tconst dist = Math.sqrt(distSq);\n\tout.normalX = dx / dist;\n\tout.normalY = dy / dist;\n\tout.depth = radius - dist;\n\treturn true;\n}\n\n// ==================== Contact Dispatcher ====================\n\n/**\n * Dispatch to the correct narrowphase function for the given pair and\n * write the contact into `out`. Returns `true` if the pair overlaps.\n */\nexport function computeContact(a: BaseColliderInfo, b: BaseColliderInfo, out: Contact): boolean {\n\tif (a.shape === AABB_SHAPE && b.shape === AABB_SHAPE) {\n\t\treturn computeAABBvsAABB(\n\t\t\ta.x, a.y, a.halfWidth, a.halfHeight,\n\t\t\tb.x, b.y, b.halfWidth, b.halfHeight,\n\t\t\tout,\n\t\t);\n\t}\n\n\tif (a.shape === CIRCLE_SHAPE && b.shape === CIRCLE_SHAPE) {\n\t\treturn computeCircleVsCircle(\n\t\t\ta.x, a.y, a.radius,\n\t\t\tb.x, b.y, b.radius,\n\t\t\tout,\n\t\t);\n\t}\n\n\tif (a.shape === AABB_SHAPE && b.shape === CIRCLE_SHAPE) {\n\t\treturn computeAABBvsCircle(\n\t\t\ta.x, a.y, a.halfWidth, a.halfHeight,\n\t\t\tb.x, b.y, b.radius,\n\t\t\tout,\n\t\t);\n\t}\n\n\t// a is Circle, b is AABB — compute as AABB-vs-Circle, then flip normal in place\n\tif (!computeAABBvsCircle(\n\t\tb.x, b.y, b.halfWidth, b.halfHeight,\n\t\ta.x, a.y, a.radius,\n\t\tout,\n\t)) return false;\n\tout.normalX = -out.normalX;\n\tout.normalY = -out.normalY;\n\treturn true;\n}\n\n// ==================== Collision Iteration ====================\n\n/** Module-level reusable set for broadphase candidates. */\nconst _broadphaseCandidates = new Set<number>();\n\nlet _bruteForceWarned = false;\nconst BRUTE_FORCE_WARN_THRESHOLD = 50;\n\n/**\n * Generic collision detection pipeline: brute-force or broadphase,\n * with layer filtering and contact computation.\n *\n * `count` is the number of live entries at the front of `colliders`.\n * The array itself may be a grow-only pool — only indices `[0, count)`\n * are iterated, so trailing pool slots are ignored.\n *\n * `workingMap` is a caller-owned `Map<number, I>` used by the broadphase\n * path as an entityId → collider lookup. It is cleared and repopulated on\n * each call; callers should allocate it once and pass the same instance\n * every frame. Unused by the brute-force path but still required so that\n * typed reuse is the default, not an opt-in.\n *\n * Uses a context parameter forwarded to the callback to avoid\n * per-frame closure allocation.\n */\nexport function detectCollisions<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tcount: number,\n\tworkingMap: Map<number, I>,\n\tspatialIndex: SpatialIndex | undefined,\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tif (spatialIndex) {\n\t\tbroadphaseDetect(colliders, count, workingMap, spatialIndex, onContact, context);\n\t} else {\n\t\tbruteForceDetect(colliders, count, onContact, context);\n\t}\n}\n\nfunction bruteForceDetect<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tcount: number,\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tif (!_bruteForceWarned && count >= BRUTE_FORCE_WARN_THRESHOLD) {\n\t\t_bruteForceWarned = true;\n\t\tconsole.warn(\n\t\t\t`[ecspresso] Collision detection is using O(n²) brute force with ${count} colliders. ` +\n\t\t\t`For better performance, install createSpatialIndexPlugin() alongside your collision or physics2D plugin.`,\n\t\t);\n\t}\n\n\tfor (let i = 0; i < count; i++) {\n\t\tconst a = colliders[i];\n\t\tif (!a) continue;\n\n\t\tfor (let j = i + 1; j < count; j++) {\n\t\t\tconst b = colliders[j];\n\t\t\tif (!b) continue;\n\n\t\t\tif (!a.collidesWith.includes(b.layer) && !b.collidesWith.includes(a.layer)) continue;\n\n\t\t\tif (!computeContact(a, b, _sharedContact)) continue;\n\n\t\t\tonContact(a, b, _sharedContact, context);\n\t\t}\n\t}\n}\n\nfunction broadphaseDetect<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tcount: number,\n\tcolliderMap: Map<number, I>,\n\tspatialIndex: SpatialIndex,\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tcolliderMap.clear();\n\tfor (let i = 0; i < count; i++) {\n\t\tconst c = colliders[i];\n\t\tif (!c) continue;\n\t\tcolliderMap.set(c.entityId, c);\n\t}\n\n\tfor (let i = 0; i < count; i++) {\n\t\tconst a = colliders[i];\n\t\tif (!a) continue;\n\n\t\tconst aHalfW = a.shape === AABB_SHAPE ? a.halfWidth : a.radius;\n\t\tconst aHalfH = a.shape === AABB_SHAPE ? a.halfHeight : a.radius;\n\n\t\t_broadphaseCandidates.clear();\n\t\tspatialIndex.queryRectInto(\n\t\t\ta.x - aHalfW, a.y - aHalfH,\n\t\t\ta.x + aHalfW, a.y + aHalfH,\n\t\t\t_broadphaseCandidates,\n\t\t);\n\n\t\tfor (const bId of _broadphaseCandidates) {\n\t\t\tif (bId <= a.entityId) continue;\n\n\t\t\tconst b = colliderMap.get(bId);\n\t\t\tif (!b) continue;\n\n\t\t\tif (!a.collidesWith.includes(b.layer) && !b.collidesWith.includes(a.layer)) continue;\n\n\t\t\tif (!computeContact(a, b, _sharedContact)) continue;\n\n\t\t\tonContact(a, b, _sharedContact, context);\n\t\t}\n\t}\n}\n",
|
|
7
7
|
"/**\n * Flocking Plugin for ECSpresso\n *\n * Classic boid simulation — separation, alignment, cohesion. Produces\n * emergent group movement from simple per-entity steering forces.\n *\n * Composes with the physics2D plugin: flocking computes steering forces\n * and feeds them through `applyForce()`. Physics integration handles\n * velocity and position updates.\n *\n * Requires the spatial-index plugin for efficient neighbor queries.\n * Entities must have a `circleCollider` (or `aabbCollider`) to appear\n * in spatial index queries.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { WorldConfigFrom } from 'ecspresso';\nimport type { TransformWorldConfig } from '../spatial/transform';\nimport type { Physics2DOwnComponentTypes } from '../physics/physics2D';\nimport { applyForce } from '../physics/physics2D';\nimport type { SpatialIndexResourceTypes } from '../spatial/spatial-index';\n\n// ==================== Component Types ====================\n\n/**\n * Configures flocking behavior for a boid entity.\n *\n * Entities with this component must also have:\n * - `localTransform` + `worldTransform` (transform plugin)\n * - `velocity` + `force` + `rigidBody` (physics2D plugin)\n * - `circleCollider` with radius >= perceptionRadius (for spatial index queries)\n */\nexport interface FlockingAgent {\n\t/** Radius within which neighbors are detected */\n\tperceptionRadius: number;\n\t/** Separation weight — steer away from nearby neighbors (default: 1.5) */\n\tseparationWeight: number;\n\t/** Alignment weight — match average heading of neighbors (default: 1.0) */\n\talignmentWeight: number;\n\t/** Cohesion weight — steer toward average position of neighbors (default: 1.0) */\n\tcohesionWeight: number;\n\t/** Maximum steering force magnitude per frame */\n\tmaxForce: number;\n\t/** Maximum velocity magnitude (hard speed cap) */\n\tmaxSpeed: number;\n\t/** Flock group ID for independent flocks (default: 0) */\n\tflockGroup: number;\n}\n\n/**\n * Component types provided by the flocking plugin.\n */\nexport interface FlockingComponentTypes {\n\tflockingAgent: FlockingAgent;\n}\n\n// ==================== WorldConfig ====================\n\n/**\n * WorldConfig representing the flocking plugin's provided types.\n */\nexport type FlockingWorldConfig = WorldConfigFrom<FlockingComponentTypes>;\n\n// ==================== Plugin Options ====================\n\nexport interface FlockingPluginOptions<G extends string = 'ai'> extends BasePluginOptions<G> {\n\t/** Priority for the heading/speed-clamp system (default: 200) */\n\theadingPriority?: number;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a flockingAgent component with sensible defaults.\n *\n * Entities must also have a `circleCollider` with radius >= perceptionRadius\n * for the spatial index to find them as neighbors.\n *\n * @param options Partial overrides for flocking agent fields\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createFlockingAgent({ perceptionRadius: 80, maxSpeed: 150 }),\n * ...createRigidBody('dynamic', { mass: 1, drag: 1, gravityScale: 0 }),\n * ...createCircleCollider(80),\n * ...createGraphicsComponents(boidGraphics, { x: 100, y: 200 }),\n * });\n * ```\n */\nexport function createFlockingAgent(\n\toptions?: Partial<FlockingAgent>,\n): Pick<FlockingComponentTypes, 'flockingAgent'> {\n\treturn {\n\t\tflockingAgent: {\n\t\t\tperceptionRadius: options?.perceptionRadius ?? 100,\n\t\t\tseparationWeight: options?.separationWeight ?? 1.5,\n\t\t\talignmentWeight: options?.alignmentWeight ?? 1.0,\n\t\t\tcohesionWeight: options?.cohesionWeight ?? 1.0,\n\t\t\tmaxForce: options?.maxForce ?? 400,\n\t\t\tmaxSpeed: options?.maxSpeed ?? 200,\n\t\t\tflockGroup: options?.flockGroup ?? 0,\n\t\t},\n\t};\n}\n\n// ==================== Plugin Factory ====================\n\n// Module-scoped reusable set to reduce GC pressure in neighbor queries\nconst _neighborSet = new Set<number>();\n\nconst SPEED_EPSILON = 0.01;\n\n/**\n * Create a flocking plugin for ECSpresso.\n *\n * Installs two systems:\n * - `flocking-forces` — computes separation/alignment/cohesion and applies via applyForce()\n * - `flocking-heading` — clamps speed to maxSpeed and orients rotation to match velocity\n *\n * Requires the transform, physics2D, and spatial-index plugins to be installed.\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createRenderer2DPlugin({ background: '#0a0a2e' }))\n * .withPlugin(createPhysics2DPlugin())\n * .withPlugin(createSpatialIndexPlugin())\n * .withPlugin(createFlockingPlugin())\n * .build();\n * ```\n */\nexport function createFlockingPlugin<G extends string = 'ai'>(\n\toptions?: FlockingPluginOptions<G>,\n) {\n\tconst {\n\t\tsystemGroup = 'ai',\n\t\tpriority = 500,\n\t\tphase = 'update',\n\t\theadingPriority = 200,\n\t} = options ?? {};\n\n\treturn definePlugin('flocking')\n\t\t.withComponentTypes<FlockingComponentTypes>()\n\t\t.withLabels<'flocking-forces' | 'flocking-heading'>()\n\t\t.withGroups<G>()\n\t\t.requires<\n\t\t\tTransformWorldConfig &\n\t\t\tWorldConfigFrom<Pick<Physics2DOwnComponentTypes, 'velocity' | 'force'>> &\n\t\t\tWorldConfigFrom<{}, {}, SpatialIndexResourceTypes>\n\t\t>()\n\t\t.install((world) => {\n\t\t\t// --- System 1: Compute and apply flocking forces ---\n\t\t\tworld\n\t\t\t\t.addSystem('flocking-forces')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('boids', {\n\t\t\t\t\twith: ['flockingAgent', 'worldTransform', 'velocity', 'force'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tconst spatialIndex = ecs.getResource('spatialIndex');\n\n\t\t\t\t\tfor (const entity of queries.boids) {\n\t\t\t\t\t\tconst { flockingAgent, worldTransform, velocity } = entity.components;\n\t\t\t\t\t\tconst { perceptionRadius, separationWeight, alignmentWeight, cohesionWeight, maxForce, flockGroup } = flockingAgent;\n\n\t\t\t\t\t\t// Query neighbors via spatial index\n\t\t\t\t\t\t_neighborSet.clear();\n\t\t\t\t\t\tspatialIndex.queryRadiusInto(worldTransform.x, worldTransform.y, perceptionRadius, _neighborSet);\n\n\t\t\t\t\t\t// Accumulate steering forces — all inline scalars, no allocations\n\t\t\t\t\t\tlet sepX = 0, sepY = 0, sepCount = 0;\n\t\t\t\t\t\tlet alignX = 0, alignY = 0, alignCount = 0;\n\t\t\t\t\t\tlet cohX = 0, cohY = 0, cohCount = 0;\n\n\t\t\t\t\t\tconst separationRadius = perceptionRadius * 0.5;\n\t\t\t\t\t\tconst separationRadiusSq = separationRadius * separationRadius;\n\n\t\t\t\t\t\tfor (const neighborId of _neighborSet) {\n\t\t\t\t\t\t\tif (neighborId === entity.id) continue;\n\n\t\t\t\t\t\t\tconst neighborAgent = ecs.getComponent(neighborId, 'flockingAgent');\n\t\t\t\t\t\t\tif (!neighborAgent) continue;\n\t\t\t\t\t\t\tif (neighborAgent.flockGroup !== flockGroup) continue;\n\n\t\t\t\t\t\t\tconst neighborTransform = ecs.getComponent(neighborId, 'worldTransform');\n\t\t\t\t\t\t\tif (!neighborTransform) continue;\n\n\t\t\t\t\t\t\tconst dx = worldTransform.x - neighborTransform.x;\n\t\t\t\t\t\t\tconst dy = worldTransform.y - neighborTransform.y;\n\t\t\t\t\t\t\tconst distSq = dx * dx + dy * dy;\n\n\t\t\t\t\t\t\t// Separation — closer neighbors push harder\n\t\t\t\t\t\t\tif (distSq > 0 && distSq < separationRadiusSq) {\n\t\t\t\t\t\t\t\tconst dist = Math.sqrt(distSq);\n\t\t\t\t\t\t\t\tsepX += dx / dist;\n\t\t\t\t\t\t\t\tsepY += dy / dist;\n\t\t\t\t\t\t\t\tsepCount++;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Alignment — average velocity of neighbors\n\t\t\t\t\t\t\tconst neighborVel = ecs.getComponent(neighborId, 'velocity');\n\t\t\t\t\t\t\tif (neighborVel) {\n\t\t\t\t\t\t\t\talignX += neighborVel.x;\n\t\t\t\t\t\t\t\talignY += neighborVel.y;\n\t\t\t\t\t\t\t\talignCount++;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Cohesion — average position of neighbors\n\t\t\t\t\t\t\tcohX += neighborTransform.x;\n\t\t\t\t\t\t\tcohY += neighborTransform.y;\n\t\t\t\t\t\t\tcohCount++;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet totalFx = 0, totalFy = 0;\n\n\t\t\t\t\t\t// Separation: steer away from crowded neighbors\n\t\t\t\t\t\tif (sepCount > 0) {\n\t\t\t\t\t\t\ttotalFx += (sepX / sepCount) * separationWeight;\n\t\t\t\t\t\t\ttotalFy += (sepY / sepCount) * separationWeight;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Alignment: steer toward average heading\n\t\t\t\t\t\tif (alignCount > 0) {\n\t\t\t\t\t\t\tconst avgVx = alignX / alignCount;\n\t\t\t\t\t\t\tconst avgVy = alignY / alignCount;\n\t\t\t\t\t\t\t// Desired = average velocity - current velocity\n\t\t\t\t\t\t\ttotalFx += (avgVx - velocity.x) * alignmentWeight;\n\t\t\t\t\t\t\ttotalFy += (avgVy - velocity.y) * alignmentWeight;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Cohesion: steer toward average position\n\t\t\t\t\t\tif (cohCount > 0) {\n\t\t\t\t\t\t\tconst avgPx = cohX / cohCount;\n\t\t\t\t\t\t\tconst avgPy = cohY / cohCount;\n\t\t\t\t\t\t\t// Desired = direction to center of mass - current velocity\n\t\t\t\t\t\t\ttotalFx += (avgPx - worldTransform.x - velocity.x) * cohesionWeight;\n\t\t\t\t\t\t\ttotalFy += (avgPy - worldTransform.y - velocity.y) * cohesionWeight;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Clamp total steering force to maxForce\n\t\t\t\t\t\tconst forceMagSq = totalFx * totalFx + totalFy * totalFy;\n\t\t\t\t\t\tif (forceMagSq > maxForce * maxForce) {\n\t\t\t\t\t\t\tconst forceMag = Math.sqrt(forceMagSq);\n\t\t\t\t\t\t\ttotalFx = (totalFx / forceMag) * maxForce;\n\t\t\t\t\t\t\ttotalFy = (totalFy / forceMag) * maxForce;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tapplyForce(ecs, entity.id, totalFx, totalFy);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// --- System 2: Clamp speed and orient heading from velocity ---\n\t\t\tworld\n\t\t\t\t.addSystem('flocking-heading')\n\t\t\t\t.setPriority(headingPriority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('boids', {\n\t\t\t\t\twith: ['flockingAgent', 'velocity', 'localTransform'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tfor (const entity of queries.boids) {\n\t\t\t\t\t\tconst { flockingAgent, velocity, localTransform } = entity.components;\n\t\t\t\t\t\tconst { maxSpeed } = flockingAgent;\n\n\t\t\t\t\t\t// Clamp velocity to maxSpeed\n\t\t\t\t\t\tconst speedSq = velocity.x * velocity.x + velocity.y * velocity.y;\n\t\t\t\t\t\tif (speedSq > maxSpeed * maxSpeed) {\n\t\t\t\t\t\t\tconst speed = Math.sqrt(speedSq);\n\t\t\t\t\t\t\tvelocity.x = (velocity.x / speed) * maxSpeed;\n\t\t\t\t\t\t\tvelocity.y = (velocity.y / speed) * maxSpeed;\n\t\t\t\t\t\t\tecs.markChanged(entity.id, 'velocity');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Orient rotation to match velocity heading\n\t\t\t\t\t\tif (speedSq > SPEED_EPSILON * SPEED_EPSILON) {\n\t\t\t\t\t\t\tconst heading = Math.atan2(velocity.y, velocity.x);\n\t\t\t\t\t\t\tif (heading !== localTransform.rotation) {\n\t\t\t\t\t\t\t\tlocalTransform.rotation = heading;\n\t\t\t\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n}\n"
|
|
8
8
|
],
|
|
9
|
-
"mappings": "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",
|
|
10
|
-
"debugId": "
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "2E4E2EA277C75A1C64756E2164756E21",
|
|
11
11
|
"names": []
|
|
12
12
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var A=((
|
|
1
|
+
var A=Object.defineProperty;var B=(J)=>J;function S(J,L){this[J]=B.bind(null,L)}var f=(J,L)=>{for(var Z in L)A(J,Z,{get:L[Z],enumerable:!0,configurable:!0,set:S.bind(L,Z)})};var w=(J,L)=>()=>(J&&(L=J(J=0)),L);var C=((J)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(J,{get:(L,Z)=>(typeof require<"u"?require:L)[Z]}):J)(function(J){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+J+'" is not supported')});import{definePlugin as V}from"ecspresso";function y(J){return Object.freeze(J)}function g(J,L,Z){return{audioSource:{sound:J,channel:L,volume:Z?.volume??1,loop:Z?.loop??!1,autoRemove:Z?.autoRemove??!1,playing:!1,_soundId:-1}}}function m(J,L){return()=>import("howler").then(({Howl:Z})=>new Promise((T,H)=>{let _,M=!1;if(_=new Z({src:Array.isArray(J)?J:[J],html5:L?.html5??!1,preload:L?.preload??!0,onload:()=>{M=!0,T(_)},onloaderror:(X,G)=>H(G instanceof Error?G:Error(String(G)))}),!M&&_.state?.()==="loaded")T(_)}))}function h(J){let{channels:L,systemGroup:Z="audio",priority:T=0,phase:H="update"}=J,_=new Map,M=new Map,X=new Map,G=1,j=!1,N=[];for(let[z,q]of Object.entries(L))_.set(z,q.volume),N.push(z);let P=N[0];function E(z,q){if(j)return 0;let F=_.get(q)??1;return z*F*G}function R(z){for(let F of M.values()){if(F.channel!==z)continue;F.howl.volume(E(F.individualVolume,z),F.soundId)}let q=X.get(z);if(q)q.howl.volume(E(q.individualVolume,z),q.soundId)}function W(){for(let z of N)R(z)}function b(z){let q=M.get(z);if(!q)return;q.howl.stop(z),M.delete(z)}let k=null,D=null,O={play(z,q){if(!D)return-1;let F=q?.channel??P,Y=q?.volume??1,Q=q?.loop??!1,U=D(z);U.volume(E(Y,F)),U.loop(Q);let K=U.play(),$={howl:U,soundId:K,channel:F,individualVolume:Y,assetKey:z,entityId:-1};return M.set(K,$),U.once("end",()=>{M.delete(K),k?.publish("soundEnded",{entityId:-1,soundId:K,sound:z})},K),K},stop(z){b(z)},playMusic(z,q){if(!D)return;let F=q?.channel??P,Y=q?.volume??1,Q=q?.loop??!0,U=X.get(F);if(U)U.howl.stop(U.soundId),M.delete(U.soundId);let K=D(z);K.volume(E(Y,F)),K.loop(Q);let $=K.play(),x={howl:K,soundId:$,channel:F,individualVolume:Y,assetKey:z};X.set(F,x),M.set($,{...x,entityId:-1}),K.once("end",()=>{if(M.delete($),X.get(F)?.soundId===$)X.delete(F)},$)},stopMusic(z){if(z!==void 0){let q=X.get(z);if(q)q.howl.stop(q.soundId),M.delete(q.soundId),X.delete(z)}else for(let[q,F]of X)F.howl.stop(F.soundId),M.delete(F.soundId),X.delete(q)},pauseMusic(z){if(z!==void 0){let q=X.get(z);if(q)q.howl.pause(q.soundId)}else for(let q of X.values())q.howl.pause(q.soundId)},resumeMusic(z){if(z!==void 0){let q=X.get(z);if(q)q.howl.play(q.soundId)}else for(let q of X.values())q.howl.play(q.soundId)},setChannelVolume(z,q){_.set(z,q),R(z)},getChannelVolume(z){return _.get(z)??1},setMasterVolume(z){G=z,W()},getMasterVolume(){return G},mute(){j=!0,W()},unmute(){j=!1,W()},toggleMute(){j=!j,W()},isMuted(){return j}};return V("audio").withComponentTypes().withEventTypes().withResourceTypes().withLabels().withGroups().withReactiveQueryNames().install((z)=>{z.addResource("audioState",O),z.registerDispose("audioSource",({value:q})=>{if(q._soundId!==-1)b(q._soundId)}),z.addSystem("audio-sync").setPriority(T).inPhase(H).inGroup(Z).setOnInitialize((q)=>{k=q.eventBus;let F=q.tryGetResource("$assets");if(F)D=(Y)=>F.get(Y);q.addReactiveQuery("audio-sources",{with:["audioSource"],onEnter:(Y)=>{let Q=Y.components.audioSource;if(!D)return;if(Q._soundId!==-1)return;let U=D(Q.sound);U.volume(E(Q.volume,Q.channel)),U.loop(Q.loop);let K=U.play();Q._soundId=K,Q.playing=!0;let $={howl:U,soundId:K,channel:Q.channel,individualVolume:Q.volume,assetKey:Q.sound,entityId:Y.id};M.set(K,$),U.once("end",()=>{if(M.delete(K),Q.playing=!1,k?.publish("soundEnded",{entityId:Y.id,soundId:K,sound:Q.sound}),Q.autoRemove)q.commands.removeEntity(Y.id)},K)},onExit:(Y)=>{}})}).setEventHandlers({playSound({data:q,ecs:F}){F.getResource("audioState").play(q.sound,{channel:q.channel,volume:q.volume,loop:q.loop})},stopMusic({data:q,ecs:F}){F.getResource("audioState").stopMusic(q.channel)}}).setOnDetach(()=>{for(let q of M.values())q.howl.stop(q.soundId);M.clear(),X.clear(),k=null,D=null})})}function r(J){return{createAudioSource:g}}export{m as loadSound,y as defineAudioChannels,g as createAudioSource,h as createAudioPlugin,r as createAudioHelpers};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=E5072AB917D540E664756E2164756E21
|
|
4
4
|
//# sourceMappingURL=audio.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * Audio Plugin for ECSpresso\n *\n * Web Audio API integration via Howler.js for sound effects and music playback.\n * User-defined channels with type-safe volume control, hybrid resource + component API,\n * and asset manager integration.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { AssetsOfWorld, AnyECSpresso, ChannelOfWorld } from 'ecspresso';\nimport type { Howl } from 'howler';\n\n// ==================== Channel Definition ====================\n\n/**\n * Configuration for a single audio channel.\n */\nexport interface AudioChannelConfig {\n\treadonly volume: number;\n}\n\n/**\n * Define audio channels with type-safe names and initial volumes.\n * Mirrors `defineCollisionLayers` pattern.\n *\n * @param channels Object mapping channel names to their configuration\n * @returns Frozen channel configuration with inferred channel name union\n *\n * @example\n * ```typescript\n * const channels = defineAudioChannels({\n * sfx: { volume: 1 },\n * music: { volume: 0.7 },\n * ui: { volume: 0.8 },\n * });\n * type Ch = ChannelsOf<typeof channels>; // 'sfx' | 'music' | 'ui'\n * ```\n */\nexport function defineAudioChannels<const T extends Record<string, AudioChannelConfig>>(\n\tchannels: T\n): Readonly<T> {\n\treturn Object.freeze(channels);\n}\n\n/**\n * Extract channel name union from a `defineAudioChannels` result.\n */\nexport type ChannelsOf<T> = T extends Record<infer K extends string, AudioChannelConfig> ? K : never;\n\n// ==================== Component Types ====================\n\n/**\n * Audio source component attached to entities for positional/entity-bound audio.\n */\nexport interface AudioSource<Ch extends string = string> {\n\t/** Asset key for the sound */\n\treadonly sound: string;\n\t/** Channel this sound plays on */\n\treadonly channel: Ch;\n\t/** Individual volume (0-1) */\n\tvolume: number;\n\t/** Whether sound loops */\n\tloop: boolean;\n\t/** Remove entity when sound ends (like timer autoRemove) */\n\tautoRemove: boolean;\n\t/** Whether sound is currently playing (system-managed) */\n\tplaying: boolean;\n\t/** Howler sound ID (system-managed, -1 = not started) */\n\t_soundId: number;\n}\n\n/**\n * Component types provided by the audio plugin.\n */\nexport interface AudioComponentTypes<Ch extends string = string> {\n\taudioSource: AudioSource<Ch>;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event to trigger fire-and-forget sound playback from any system.\n */\nexport interface PlaySoundEvent<Ch extends string = string> {\n\t/** Asset key for the sound */\n\tsound: string;\n\t/** Channel to play on */\n\tchannel?: Ch;\n\t/** Individual volume (0-1) */\n\tvolume?: number;\n\t/** Whether sound loops */\n\tloop?: boolean;\n}\n\n/**\n * Event to stop music on a channel.\n */\nexport interface StopMusicEvent<Ch extends string = string> {\n\t/** Channel to stop music on. If omitted, stops all music. */\n\tchannel?: Ch;\n}\n\n/**\n * Event published when a sound finishes playing.\n */\nexport interface SoundEndedEvent {\n\t/** Entity ID if sound was entity-attached, -1 for fire-and-forget */\n\tentityId: number;\n\t/** Howler sound ID */\n\tsoundId: number;\n\t/** Asset key of the sound */\n\tsound: string;\n}\n\n/**\n * Event types provided by the audio plugin.\n */\nexport interface AudioEventTypes<Ch extends string = string> {\n\tplaySound: PlaySoundEvent<Ch>;\n\tstopMusic: StopMusicEvent<Ch>;\n\tsoundEnded: SoundEndedEvent;\n}\n\n// ==================== Resource Types ====================\n\n/**\n * Play options for fire-and-forget sound effects.\n */\nexport interface PlayOptions<Ch extends string = string> {\n\t/** Channel to play on (uses first defined channel if omitted) */\n\tchannel?: Ch;\n\t/** Individual volume (0-1, default: 1) */\n\tvolume?: number;\n\t/** Whether to loop (default: false) */\n\tloop?: boolean;\n}\n\n/**\n * Music playback options.\n */\nexport interface MusicOptions<Ch extends string = string> {\n\t/** Channel to play music on (uses first defined channel if omitted) */\n\tchannel?: Ch;\n\t/** Volume (0-1, default: 1) */\n\tvolume?: number;\n\t/** Whether to loop (default: true) */\n\tloop?: boolean;\n}\n\n/**\n * Audio state resource providing fire-and-forget SFX and music control.\n * Effective volume = individual * channel * master.\n */\nexport interface AudioState<Ch extends string = string> {\n\t/** Play a fire-and-forget sound effect. Returns the Howler sound ID. */\n\tplay(sound: string, options?: PlayOptions<Ch>): number;\n\t/** Stop a specific sound by its Howler sound ID. */\n\tstop(soundId: number): void;\n\n\t/** Play music on a channel. Stops any existing music on that channel first. */\n\tplayMusic(sound: string, options?: MusicOptions<Ch>): void;\n\t/** Stop music on a channel. If omitted, stops all music. */\n\tstopMusic(channel?: Ch): void;\n\t/** Pause music on a channel. If omitted, pauses all music. */\n\tpauseMusic(channel?: Ch): void;\n\t/** Resume music on a channel. If omitted, resumes all music. */\n\tresumeMusic(channel?: Ch): void;\n\n\t/** Set volume for a channel (0-1). */\n\tsetChannelVolume(channel: Ch, volume: number): void;\n\t/** Get current volume for a channel. */\n\tgetChannelVolume(channel: Ch): number;\n\t/** Set master volume (0-1). */\n\tsetMasterVolume(volume: number): void;\n\t/** Get current master volume. */\n\tgetMasterVolume(): number;\n\t/** Mute all audio. */\n\tmute(): void;\n\t/** Unmute all audio. */\n\tunmute(): void;\n\t/** Toggle mute state. */\n\ttoggleMute(): void;\n\t/** Check if audio is muted. */\n\tisMuted(): boolean;\n}\n\n/**\n * Resource types provided by the audio plugin.\n */\nexport interface AudioResourceTypes<Ch extends string = string> {\n\taudioState: AudioState<Ch>;\n}\n\n// ==================== Plugin Options ====================\n\n/**\n * Configuration options for the audio plugin.\n */\nexport interface AudioPluginOptions<Ch extends string, G extends string = 'audio'> extends BasePluginOptions<G> {\n\t/** Channel definitions from defineAudioChannels */\n\tchannels: Readonly<Record<Ch, AudioChannelConfig>>;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create an audioSource component for entity-attached audio.\n *\n * @param sound Asset key for the sound\n * @param channel Channel to play on\n * @param options Optional configuration\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createAudioSource('explosion', 'sfx'),\n * ...createTransform(100, 200),\n * });\n * ```\n */\nexport function createAudioSource<Ch extends string>(\n\tsound: string,\n\tchannel: Ch,\n\toptions?: { volume?: number; loop?: boolean; autoRemove?: boolean }\n): Pick<AudioComponentTypes<Ch>, 'audioSource'> {\n\treturn {\n\t\taudioSource: {\n\t\t\tsound,\n\t\t\tchannel,\n\t\t\tvolume: options?.volume ?? 1,\n\t\t\tloop: options?.loop ?? false,\n\t\t\tautoRemove: options?.autoRemove ?? false,\n\t\t\tplaying: false,\n\t\t\t_soundId: -1,\n\t\t},\n\t};\n}\n\n/**\n * Create a loader function for use with the asset manager.\n * Returns a factory function that loads a Howl when called.\n *\n * @param src URL(s) for the sound file\n * @param options Optional Howl configuration\n * @returns Factory function compatible with asset manager's loader parameter\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withAssets(a => a\n * .add('explosion', loadSound('/sounds/explosion.mp3'))\n * .add('bgm', loadSound(['/sounds/bgm.webm', '/sounds/bgm.mp3']))\n * )\n * .build();\n * ```\n */\nexport function loadSound(\n\tsrc: string | string[],\n\toptions?: { html5?: boolean; preload?: boolean }\n): () => Promise<Howl> {\n\treturn () => import('howler').then(({ Howl: HowlClass }) =>\n\t\tnew Promise<Howl>((resolve, reject) => {\n\t\t\tlet howl: Howl;\n\t\t\tlet resolved = false;\n\t\t\thowl = new HowlClass({\n\t\t\t\tsrc: Array.isArray(src) ? src : [src],\n\t\t\t\thtml5: options?.html5 ?? false,\n\t\t\t\tpreload: options?.preload ?? true,\n\t\t\t\tonload: () => {\n\t\t\t\t\tresolved = true;\n\t\t\t\t\tresolve(howl);\n\t\t\t\t},\n\t\t\t\tonloaderror: (_id: number, err: unknown) => reject(\n\t\t\t\t\terr instanceof Error ? err : new Error(String(err))\n\t\t\t\t),\n\t\t\t});\n\t\t\t// If onload fired synchronously during construction (e.g. cached),\n\t\t\t// howl is now assigned and the promise is already resolved.\n\t\t\tif (!resolved && howl.state?.() === 'loaded') {\n\t\t\t\tresolve(howl);\n\t\t\t}\n\t\t})\n\t);\n}\n\n// ==================== Internal Types ====================\n\ninterface ActiveSound<Ch extends string> {\n\thowl: Howl;\n\tsoundId: number;\n\tchannel: Ch;\n\tindividualVolume: number;\n\tassetKey: string;\n\tentityId: number;\n}\n\ninterface MusicEntry<Ch extends string> {\n\thowl: Howl;\n\tsoundId: number;\n\tchannel: Ch;\n\tindividualVolume: number;\n\tassetKey: string;\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create an audio plugin for ECSpresso.\n *\n * Provides:\n * - `audioState` resource for fire-and-forget SFX and music\n * - `audioSource` component for entity-attached sounds\n * - Volume hierarchy: individual * channel * master\n * - `playSound` / `stopMusic` event handlers\n * - `soundEnded` event on completion\n * - Automatic cleanup on entity removal (dispose callback)\n *\n * Sounds must be preloaded through the asset pipeline (`loadSound` helper).\n *\n * @example\n * ```typescript\n * const channels = defineAudioChannels({\n * sfx: { volume: 1 },\n * music: { volume: 0.7 },\n * });\n *\n * const ecs = ECSpresso.create()\n * .withAssets(a => a.add('explosion', loadSound('/sfx/boom.mp3')))\n * .withPlugin(createAudioPlugin({ channels }))\n * .build();\n *\n * await ecs.initialize();\n * const audio = ecs.getResource('audioState');\n * audio.play('explosion', { channel: 'sfx' });\n * ```\n */\nexport function createAudioPlugin<Ch extends string, G extends string = 'audio'>(\n\toptions: AudioPluginOptions<Ch, G>\n) {\n\tconst {\n\t\tchannels: channelDefs,\n\t\tsystemGroup = 'audio',\n\t\tpriority = 0,\n\t\tphase = 'update',\n\t} = options;\n\n\t// Closure state\n\tconst channelVolumes = new Map<Ch, number>();\n\tconst activeSounds = new Map<number, ActiveSound<Ch>>();\n\tconst musicByChannel = new Map<Ch, MusicEntry<Ch>>();\n\tlet masterVolume = 1;\n\tlet muted = false;\n\n\t// Initialize channel volumes from definitions\n\tconst channelNames: Ch[] = [];\n\tfor (const [name, config] of Object.entries(channelDefs) as Array<[Ch, AudioChannelConfig]>) {\n\t\tchannelVolumes.set(name, config.volume);\n\t\tchannelNames.push(name);\n\t}\n\n\tconst defaultChannel = channelNames[0] as Ch;\n\n\t// Volume computation\n\tfunction effectiveVolume(individualVol: number, channel: Ch): number {\n\t\tif (muted) return 0;\n\t\tconst chanVol = channelVolumes.get(channel) ?? 1;\n\t\treturn individualVol * chanVol * masterVolume;\n\t}\n\n\t// Propagate volume changes to all active sounds on a channel\n\tfunction propagateChannelVolume(channel: Ch): void {\n\t\tfor (const sound of activeSounds.values()) {\n\t\t\tif (sound.channel !== channel) continue;\n\t\t\tsound.howl.volume(effectiveVolume(sound.individualVolume, channel), sound.soundId);\n\t\t}\n\t\tconst music = musicByChannel.get(channel);\n\t\tif (music) {\n\t\t\tmusic.howl.volume(effectiveVolume(music.individualVolume, channel), music.soundId);\n\t\t}\n\t}\n\n\t// Propagate volume to all sounds across all channels\n\tfunction propagateAllVolumes(): void {\n\t\tfor (const ch of channelNames) {\n\t\t\tpropagateChannelVolume(ch);\n\t\t}\n\t}\n\n\t// Stop a sound by its Howler sound ID\n\tfunction stopSoundById(soundId: number): void {\n\t\tconst entry = activeSounds.get(soundId);\n\t\tif (!entry) return;\n\t\tentry.howl.stop(soundId);\n\t\tactiveSounds.delete(soundId);\n\t}\n\n\t// Event bus reference, set during initialization\n\tlet eventBusRef: { publish(event: string, data: unknown): void } | null = null;\n\n\t// Resolve Howl from asset key\n\tlet getAsset: ((key: string) => Howl) | null = null;\n\n\t// AudioState resource implementation\n\tconst audioState: AudioState<Ch> = {\n\t\tplay(sound, playOpts) {\n\t\t\tif (!getAsset) return -1;\n\t\t\tconst channel = playOpts?.channel ?? defaultChannel;\n\t\t\tconst individualVol = playOpts?.volume ?? 1;\n\t\t\tconst loop = playOpts?.loop ?? false;\n\n\t\t\tconst howl = getAsset(sound);\n\t\t\thowl.volume(effectiveVolume(individualVol, channel));\n\t\t\thowl.loop(loop);\n\t\t\tconst soundId = howl.play();\n\n\t\t\tconst entry: ActiveSound<Ch> = {\n\t\t\t\thowl,\n\t\t\t\tsoundId,\n\t\t\t\tchannel,\n\t\t\t\tindividualVolume: individualVol,\n\t\t\t\tassetKey: sound,\n\t\t\t\tentityId: -1,\n\t\t\t};\n\t\t\tactiveSounds.set(soundId, entry);\n\n\t\t\thowl.once('end', () => {\n\t\t\t\tactiveSounds.delete(soundId);\n\t\t\t\teventBusRef?.publish('soundEnded', {\n\t\t\t\t\tentityId: -1,\n\t\t\t\t\tsoundId,\n\t\t\t\t\tsound,\n\t\t\t\t} satisfies SoundEndedEvent);\n\t\t\t}, soundId);\n\n\t\t\treturn soundId;\n\t\t},\n\n\t\tstop(soundId) {\n\t\t\tstopSoundById(soundId);\n\t\t},\n\n\t\tplayMusic(sound, musicOpts) {\n\t\t\tif (!getAsset) return;\n\t\t\tconst channel = musicOpts?.channel ?? defaultChannel;\n\t\t\tconst individualVol = musicOpts?.volume ?? 1;\n\t\t\tconst loop = musicOpts?.loop ?? true;\n\n\t\t\t// Stop existing music on this channel\n\t\t\tconst existing = musicByChannel.get(channel);\n\t\t\tif (existing) {\n\t\t\t\texisting.howl.stop(existing.soundId);\n\t\t\t\tactiveSounds.delete(existing.soundId);\n\t\t\t}\n\n\t\t\tconst howl = getAsset(sound);\n\t\t\thowl.volume(effectiveVolume(individualVol, channel));\n\t\t\thowl.loop(loop);\n\t\t\tconst soundId = howl.play();\n\n\t\t\tconst entry: MusicEntry<Ch> = {\n\t\t\t\thowl,\n\t\t\t\tsoundId,\n\t\t\t\tchannel,\n\t\t\t\tindividualVolume: individualVol,\n\t\t\t\tassetKey: sound,\n\t\t\t};\n\t\t\tmusicByChannel.set(channel, entry);\n\t\t\tactiveSounds.set(soundId, {\n\t\t\t\t...entry,\n\t\t\t\tentityId: -1,\n\t\t\t});\n\n\t\t\thowl.once('end', () => {\n\t\t\t\tactiveSounds.delete(soundId);\n\t\t\t\tconst current = musicByChannel.get(channel);\n\t\t\t\tif (current?.soundId === soundId) {\n\t\t\t\t\tmusicByChannel.delete(channel);\n\t\t\t\t}\n\t\t\t}, soundId);\n\t\t},\n\n\t\tstopMusic(channel) {\n\t\t\tif (channel !== undefined) {\n\t\t\t\tconst entry = musicByChannel.get(channel);\n\t\t\t\tif (entry) {\n\t\t\t\t\tentry.howl.stop(entry.soundId);\n\t\t\t\t\tactiveSounds.delete(entry.soundId);\n\t\t\t\t\tmusicByChannel.delete(channel);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor (const [ch, entry] of musicByChannel) {\n\t\t\t\t\tentry.howl.stop(entry.soundId);\n\t\t\t\t\tactiveSounds.delete(entry.soundId);\n\t\t\t\t\tmusicByChannel.delete(ch);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tpauseMusic(channel) {\n\t\t\tif (channel !== undefined) {\n\t\t\t\tconst entry = musicByChannel.get(channel);\n\t\t\t\tif (entry) entry.howl.pause(entry.soundId);\n\t\t\t} else {\n\t\t\t\tfor (const entry of musicByChannel.values()) {\n\t\t\t\t\tentry.howl.pause(entry.soundId);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tresumeMusic(channel) {\n\t\t\tif (channel !== undefined) {\n\t\t\t\tconst entry = musicByChannel.get(channel);\n\t\t\t\tif (entry) entry.howl.play(entry.soundId);\n\t\t\t} else {\n\t\t\t\tfor (const entry of musicByChannel.values()) {\n\t\t\t\t\tentry.howl.play(entry.soundId);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tsetChannelVolume(channel, volume) {\n\t\t\tchannelVolumes.set(channel, volume);\n\t\t\tpropagateChannelVolume(channel);\n\t\t},\n\n\t\tgetChannelVolume(channel) {\n\t\t\treturn channelVolumes.get(channel) ?? 1;\n\t\t},\n\n\t\tsetMasterVolume(volume) {\n\t\t\tmasterVolume = volume;\n\t\t\tpropagateAllVolumes();\n\t\t},\n\n\t\tgetMasterVolume() {\n\t\t\treturn masterVolume;\n\t\t},\n\n\t\tmute() {\n\t\t\tmuted = true;\n\t\t\tpropagateAllVolumes();\n\t\t},\n\n\t\tunmute() {\n\t\t\tmuted = false;\n\t\t\tpropagateAllVolumes();\n\t\t},\n\n\t\ttoggleMute() {\n\t\t\tmuted = !muted;\n\t\t\tpropagateAllVolumes();\n\t\t},\n\n\t\tisMuted() {\n\t\t\treturn muted;\n\t\t},\n\t};\n\n\treturn definePlugin('audio')\n\t\t.withComponentTypes<AudioComponentTypes<Ch>>()\n\t\t.withEventTypes<AudioEventTypes<Ch>>()\n\t\t.withResourceTypes<AudioResourceTypes<Ch>>()\n\t\t.withLabels<'audio-sync'>()\n\t\t.withGroups<G>()\n\t\t.withReactiveQueryNames<'audio-sources'>()\n\t\t.install((world) => {\n\t\t\tworld.addResource('audioState', audioState);\n\n\t\t\t// Dispose callback: stop sounds when audioSource component is removed\n\t\t\tworld.registerDispose('audioSource', ({ value: source }: { value: AudioSource<Ch>; entityId: number }) => {\n\t\t\t\tif (source._soundId !== -1) {\n\t\t\t\t\tstopSoundById(source._soundId);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tworld\n\t\t\t\t.addSystem('audio-sync')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize((ecs) => {\n\t\t\t\t\teventBusRef = ecs.eventBus;\n\n\t\t\t\t\t// Resolve asset getter - works with $assets resource if available\n\t\t\t\t\tconst assets = ecs.tryGetResource<{ get(k: string): unknown }>('$assets');\n\t\t\t\t\tif (assets) {\n\t\t\t\t\t\tgetAsset = (key: string) => assets.get(key) as Howl;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Register reactive query for audioSource components\n\t\t\t\t\tecs.addReactiveQuery('audio-sources', {\n\t\t\t\t\t\twith: ['audioSource'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\tconst source = entity.components.audioSource;\n\t\t\t\t\t\t\tif (!getAsset) return;\n\t\t\t\t\t\t\tif (source._soundId !== -1) return; // Already started\n\n\t\t\t\t\t\t\tconst howl = getAsset(source.sound);\n\t\t\t\t\t\t\thowl.volume(effectiveVolume(source.volume, source.channel));\n\t\t\t\t\t\t\thowl.loop(source.loop);\n\t\t\t\t\t\t\tconst soundId = howl.play();\n\n\t\t\t\t\t\t\tsource._soundId = soundId;\n\t\t\t\t\t\t\tsource.playing = true;\n\n\t\t\t\t\t\t\tconst entry: ActiveSound<Ch> = {\n\t\t\t\t\t\t\t\thowl,\n\t\t\t\t\t\t\t\tsoundId,\n\t\t\t\t\t\t\t\tchannel: source.channel,\n\t\t\t\t\t\t\t\tindividualVolume: source.volume,\n\t\t\t\t\t\t\t\tassetKey: source.sound,\n\t\t\t\t\t\t\t\tentityId: entity.id,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tactiveSounds.set(soundId, entry);\n\n\t\t\t\t\t\t\thowl.once('end', () => {\n\t\t\t\t\t\t\t\tactiveSounds.delete(soundId);\n\t\t\t\t\t\t\t\tsource.playing = false;\n\n\t\t\t\t\t\t\t\teventBusRef?.publish('soundEnded', {\n\t\t\t\t\t\t\t\t\tentityId: entity.id,\n\t\t\t\t\t\t\t\t\tsoundId,\n\t\t\t\t\t\t\t\t\tsound: source.sound,\n\t\t\t\t\t\t\t\t} satisfies SoundEndedEvent);\n\n\t\t\t\t\t\t\t\tif (source.autoRemove) {\n\t\t\t\t\t\t\t\t\tecs.commands.removeEntity(entity.id);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}, soundId);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (_entityId) => {\n\t\t\t\t\t\t\t// Cleanup handled by dispose callback\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t})\n\t\t\t\t.setEventHandlers({\n\t\t\t\t\tplaySound({ data, ecs }) {\n\t\t\t\t\t\tconst audio = ecs.getResource('audioState');\n\t\t\t\t\t\taudio.play(data.sound, {\n\t\t\t\t\t\t\tchannel: data.channel,\n\t\t\t\t\t\t\tvolume: data.volume,\n\t\t\t\t\t\t\tloop: data.loop,\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t\tstopMusic({ data, ecs }) {\n\t\t\t\t\t\tconst audio = ecs.getResource('audioState');\n\t\t\t\t\t\taudio.stopMusic(data.channel);\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\t.setOnDetach(() => {\n\t\t\t\t\t// Stop all active sounds\n\t\t\t\t\tfor (const entry of activeSounds.values()) {\n\t\t\t\t\t\tentry.howl.stop(entry.soundId);\n\t\t\t\t\t}\n\t\t\t\t\tactiveSounds.clear();\n\t\t\t\t\tmusicByChannel.clear();\n\t\t\t\t\teventBusRef = null;\n\t\t\t\t\tgetAsset = null;\n\t\t\t\t});\n\t\t});\n}\n\n// ==================== Post-Build Helpers ====================\n\n/**\n * Typed helpers for the audio plugin.\n * Creates helpers that validate sound keys and channel names against the world type W.\n * Call after .build() using typeof ecs.\n *\n * @template W - Concrete ECS world type (e.g. `typeof ecs`)\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createAudioPlugin({ channels }))\n * .withAssets(a => a.add('boom', loadSound('/sfx/boom.mp3')))\n * .build();\n *\n * const { createAudioSource } = createAudioHelpers<typeof ecs>();\n * // Type-safe: 'boom' must be a registered asset, 'sfx' a valid channel\n * createAudioSource('boom', 'sfx');\n * ```\n */\nexport interface AudioHelpers<W extends AnyECSpresso> {\n\tcreateAudioSource: (\n\t\tsound: keyof AssetsOfWorld<W> & string,\n\t\tchannel: ChannelOfWorld<W>,\n\t\toptions?: { volume?: number; loop?: boolean; autoRemove?: boolean },\n\t) => Pick<AudioComponentTypes<ChannelOfWorld<W>>, 'audioSource'>;\n}\n\nexport function createAudioHelpers<W extends AnyECSpresso>(_world?: W): AudioHelpers<W> {\n\treturn {\n\t\tcreateAudioSource: createAudioSource as AudioHelpers<W>['createAudioSource'],\n\t};\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "4cAQA,uBAAS,kBA8BF,SAAS,CAAuE,CACtF,EACc,CACd,OAAO,OAAO,OAAO,CAAQ,EAoLvB,SAAS,CAAoC,CACnD,EACA,EACA,EAC+C,CAC/C,MAAO,CACN,YAAa,CACZ,QACA,UACA,OAAQ,GAAS,QAAU,EAC3B,KAAM,GAAS,MAAQ,GACvB,WAAY,GAAS,YAAc,GACnC,QAAS,GACT,SAAU,EACX,CACD,EAqBM,SAAS,CAAS,CACxB,EACA,EACsB,CACtB,MAAO,IAAa,iBAAU,KAAK,EAAG,KAAM,KAC3C,IAAI,QAAc,CAAC,EAAS,IAAW,CACtC,IAAI,EACA,EAAW,GAef,GAdA,EAAO,IAAI,EAAU,CACpB,IAAK,MAAM,QAAQ,CAAG,EAAI,EAAM,CAAC,CAAG,EACpC,MAAO,GAAS,OAAS,GACzB,QAAS,GAAS,SAAW,GAC7B,OAAQ,IAAM,CACb,EAAW,GACX,EAAQ,CAAI,GAEb,YAAa,CAAC,EAAa,IAAiB,EAC3C,aAAe,MAAQ,EAAU,MAAM,OAAO,CAAG,CAAC,CACnD,CACD,CAAC,EAGG,CAAC,GAAY,EAAK,QAAQ,IAAM,SACnC,EAAQ,CAAI,EAEb,CACF,EAsDM,SAAS,CAAgE,CAC/E,EACC,CACD,IACC,SAAU,EACV,cAAc,QACd,WAAW,EACX,QAAQ,UACL,EAGE,EAAiB,IAAI,IACrB,EAAe,IAAI,IACnB,EAAiB,IAAI,IACvB,EAAe,EACf,EAAQ,GAGN,EAAqB,CAAC,EAC5B,QAAY,EAAM,KAAW,OAAO,QAAQ,CAAW,EACtD,EAAe,IAAI,EAAM,EAAO,MAAM,EACtC,EAAa,KAAK,CAAI,EAGvB,IAAM,EAAiB,EAAa,GAGpC,SAAS,CAAe,CAAC,EAAuB,EAAqB,CACpE,GAAI,EAAO,MAAO,GAClB,IAAM,EAAU,EAAe,IAAI,CAAO,GAAK,EAC/C,OAAO,EAAgB,EAAU,EAIlC,SAAS,CAAsB,CAAC,EAAmB,CAClD,QAAW,KAAS,EAAa,OAAO,EAAG,CAC1C,GAAI,EAAM,UAAY,EAAS,SAC/B,EAAM,KAAK,OAAO,EAAgB,EAAM,iBAAkB,CAAO,EAAG,EAAM,OAAO,EAElF,IAAM,EAAQ,EAAe,IAAI,CAAO,EACxC,GAAI,EACH,EAAM,KAAK,OAAO,EAAgB,EAAM,iBAAkB,CAAO,EAAG,EAAM,OAAO,EAKnF,SAAS,CAAmB,EAAS,CACpC,QAAW,KAAM,EAChB,EAAuB,CAAE,EAK3B,SAAS,CAAa,CAAC,EAAuB,CAC7C,IAAM,EAAQ,EAAa,IAAI,CAAO,EACtC,GAAI,CAAC,EAAO,OACZ,EAAM,KAAK,KAAK,CAAO,EACvB,EAAa,OAAO,CAAO,EAI5B,IAAI,EAAsE,KAGtE,EAA2C,KAGzC,EAA6B,CAClC,IAAI,CAAC,EAAO,EAAU,CACrB,GAAI,CAAC,EAAU,MAAO,GACtB,IAAM,EAAU,GAAU,SAAW,EAC/B,EAAgB,GAAU,QAAU,EACpC,EAAO,GAAU,MAAQ,GAEzB,EAAO,EAAS,CAAK,EAC3B,EAAK,OAAO,EAAgB,EAAe,CAAO,CAAC,EACnD,EAAK,KAAK,CAAI,EACd,IAAM,EAAU,EAAK,KAAK,EAEpB,EAAyB,CAC9B,OACA,UACA,UACA,iBAAkB,EAClB,SAAU,EACV,SAAU,EACX,EAYA,OAXA,EAAa,IAAI,EAAS,CAAK,EAE/B,EAAK,KAAK,MAAO,IAAM,CACtB,EAAa,OAAO,CAAO,EAC3B,GAAa,QAAQ,aAAc,CAClC,SAAU,GACV,UACA,OACD,CAA2B,GACzB,CAAO,EAEH,GAGR,IAAI,CAAC,EAAS,CACb,EAAc,CAAO,GAGtB,SAAS,CAAC,EAAO,EAAW,CAC3B,GAAI,CAAC,EAAU,OACf,IAAM,EAAU,GAAW,SAAW,EAChC,EAAgB,GAAW,QAAU,EACrC,EAAO,GAAW,MAAQ,GAG1B,EAAW,EAAe,IAAI,CAAO,EAC3C,GAAI,EACH,EAAS,KAAK,KAAK,EAAS,OAAO,EACnC,EAAa,OAAO,EAAS,OAAO,EAGrC,IAAM,EAAO,EAAS,CAAK,EAC3B,EAAK,OAAO,EAAgB,EAAe,CAAO,CAAC,EACnD,EAAK,KAAK,CAAI,EACd,IAAM,EAAU,EAAK,KAAK,EAEpB,EAAwB,CAC7B,OACA,UACA,UACA,iBAAkB,EAClB,SAAU,CACX,EACA,EAAe,IAAI,EAAS,CAAK,EACjC,EAAa,IAAI,EAAS,IACtB,EACH,SAAU,EACX,CAAC,EAED,EAAK,KAAK,MAAO,IAAM,CAGtB,GAFA,EAAa,OAAO,CAAO,EACX,EAAe,IAAI,CAAO,GAC7B,UAAY,EACxB,EAAe,OAAO,CAAO,GAE5B,CAAO,GAGX,SAAS,CAAC,EAAS,CAClB,GAAI,IAAY,OAAW,CAC1B,IAAM,EAAQ,EAAe,IAAI,CAAO,EACxC,GAAI,EACH,EAAM,KAAK,KAAK,EAAM,OAAO,EAC7B,EAAa,OAAO,EAAM,OAAO,EACjC,EAAe,OAAO,CAAO,EAG9B,aAAY,EAAI,KAAU,EACzB,EAAM,KAAK,KAAK,EAAM,OAAO,EAC7B,EAAa,OAAO,EAAM,OAAO,EACjC,EAAe,OAAO,CAAE,GAK3B,UAAU,CAAC,EAAS,CACnB,GAAI,IAAY,OAAW,CAC1B,IAAM,EAAQ,EAAe,IAAI,CAAO,EACxC,GAAI,EAAO,EAAM,KAAK,MAAM,EAAM,OAAO,EAEzC,aAAW,KAAS,EAAe,OAAO,EACzC,EAAM,KAAK,MAAM,EAAM,OAAO,GAKjC,WAAW,CAAC,EAAS,CACpB,GAAI,IAAY,OAAW,CAC1B,IAAM,EAAQ,EAAe,IAAI,CAAO,EACxC,GAAI,EAAO,EAAM,KAAK,KAAK,EAAM,OAAO,EAExC,aAAW,KAAS,EAAe,OAAO,EACzC,EAAM,KAAK,KAAK,EAAM,OAAO,GAKhC,gBAAgB,CAAC,EAAS,EAAQ,CACjC,EAAe,IAAI,EAAS,CAAM,EAClC,EAAuB,CAAO,GAG/B,gBAAgB,CAAC,EAAS,CACzB,OAAO,EAAe,IAAI,CAAO,GAAK,GAGvC,eAAe,CAAC,EAAQ,CACvB,EAAe,EACf,EAAoB,GAGrB,eAAe,EAAG,CACjB,OAAO,GAGR,IAAI,EAAG,CACN,EAAQ,GACR,EAAoB,GAGrB,MAAM,EAAG,CACR,EAAQ,GACR,EAAoB,GAGrB,UAAU,EAAG,CACZ,EAAQ,CAAC,EACT,EAAoB,GAGrB,OAAO,EAAG,CACT,OAAO,EAET,EAEA,OAAO,EAAa,OAAO,EACzB,mBAA4C,EAC5C,eAAoC,EACpC,kBAA0C,EAC1C,WAAyB,EACzB,WAAc,EACd,uBAAwC,EACxC,QAAQ,CAAC,IAAU,CACnB,EAAM,YAAY,aAAc,CAAU,EAG1C,EAAM,gBAAgB,cAAe,EAAG,MAAO,KAA2D,CACzG,GAAI,EAAO,WAAa,GACvB,EAAc,EAAO,QAAQ,EAE9B,EAED,EACE,UAAU,YAAY,EACtB,YAAY,CAAQ,EACpB,QAAQ,CAAK,EACb,QAAQ,CAAW,EACnB,gBAAgB,CAAC,IAAQ,CACzB,EAAc,EAAI,SAGlB,IAAM,EAAS,EAAI,eAA4C,SAAS,EACxE,GAAI,EACH,EAAW,CAAC,IAAgB,EAAO,IAAI,CAAG,EAI3C,EAAI,iBAAiB,gBAAiB,CACrC,KAAM,CAAC,aAAa,EACpB,QAAS,CAAC,IAAW,CACpB,IAAM,EAAS,EAAO,WAAW,YACjC,GAAI,CAAC,EAAU,OACf,GAAI,EAAO,WAAa,GAAI,OAE5B,IAAM,EAAO,EAAS,EAAO,KAAK,EAClC,EAAK,OAAO,EAAgB,EAAO,OAAQ,EAAO,OAAO,CAAC,EAC1D,EAAK,KAAK,EAAO,IAAI,EACrB,IAAM,EAAU,EAAK,KAAK,EAE1B,EAAO,SAAW,EAClB,EAAO,QAAU,GAEjB,IAAM,EAAyB,CAC9B,OACA,UACA,QAAS,EAAO,QAChB,iBAAkB,EAAO,OACzB,SAAU,EAAO,MACjB,SAAU,EAAO,EAClB,EACA,EAAa,IAAI,EAAS,CAAK,EAE/B,EAAK,KAAK,MAAO,IAAM,CAUtB,GATA,EAAa,OAAO,CAAO,EAC3B,EAAO,QAAU,GAEjB,GAAa,QAAQ,aAAc,CAClC,SAAU,EAAO,GACjB,UACA,MAAO,EAAO,KACf,CAA2B,EAEvB,EAAO,WACV,EAAI,SAAS,aAAa,EAAO,EAAE,GAElC,CAAO,GAEX,OAAQ,CAAC,IAAc,EAGxB,CAAC,EACD,EACA,iBAAiB,CACjB,SAAS,EAAG,OAAM,OAAO,CACV,EAAI,YAAY,YAAY,EACpC,KAAK,EAAK,MAAO,CACtB,QAAS,EAAK,QACd,OAAQ,EAAK,OACb,KAAM,EAAK,IACZ,CAAC,GAEF,SAAS,EAAG,OAAM,OAAO,CACV,EAAI,YAAY,YAAY,EACpC,UAAU,EAAK,OAAO,EAE9B,CAAC,EACA,YAAY,IAAM,CAElB,QAAW,KAAS,EAAa,OAAO,EACvC,EAAM,KAAK,KAAK,EAAM,OAAO,EAE9B,EAAa,MAAM,EACnB,EAAe,MAAM,EACrB,EAAc,KACd,EAAW,KACX,EACF,EAgCI,SAAS,CAA0C,CAAC,EAA6B,CACvF,MAAO,CACN,kBAAmB,CACpB",
|
|
8
|
+
"debugId": "E5072AB917D540E664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var K=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(k,
|
|
1
|
+
var J=Object.defineProperty;var K=(j)=>j;function L(j,k){this[j]=K.bind(null,k)}var N=(j,k)=>{for(var q in k)J(j,q,{get:k[q],enumerable:!0,configurable:!0,set:L.bind(k,q)})};var O=(j,k)=>()=>(j&&(k=j(j=0)),k);var Q=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(k,q)=>(typeof require<"u"?require:k)[q]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as M}from"ecspresso";function V(j){return{health:{current:j,max:j}}}function W(j,k){return{health:{current:j,max:k}}}function X(j){let{systemGroup:k="combat"}=j??{};return M("health").withComponentTypes().withEventTypes().withLabels().withGroups().install((q)=>{q.addSystem("health-damage").inGroup(k).setEventHandlers({damage({data:z,ecs:F}){let A=F.getComponent(z.entityId,"health");if(!A)return;if(A.current<=0)return;if(A.current=Math.max(0,A.current-z.amount),F.markChanged(z.entityId,"health"),A.current<=0)F.eventBus.publish("entityDied",{entityId:z.entityId,killerId:z.sourceId})}})})}export{W as createHealthWith,X as createHealthPlugin,V as createHealth};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=B2A67A4C6953F66664756E2164756E21
|
|
4
4
|
//# sourceMappingURL=health.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * Health Plugin for ECSpresso\n *\n * Provides a standard health/damage/death lifecycle.\n * Entities with a `health` component can receive `damage` events.\n * When health reaches zero, an `entityDied` event is published.\n * The plugin does NOT remove dead entities — game-specific logic\n * decides when and how to handle death (animations, loot, etc).\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { WorldConfigFrom } from 'ecspresso';\n\n// ==================== Component Types ====================\n\n/**\n * Health state for an entity.\n */\nexport interface Health {\n\tcurrent: number;\n\tmax: number;\n}\n\n/**\n * Component types provided by the health plugin.\n */\nexport interface HealthComponentTypes {\n\thealth: Health;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event requesting damage to an entity.\n */\nexport interface DamageEvent {\n\tentityId: number;\n\tamount: number;\n\tsourceId?: number;\n}\n\n/**\n * Event fired when an entity's health reaches zero.\n */\nexport interface EntityDiedEvent {\n\tentityId: number;\n\tkillerId?: number;\n}\n\n/**\n * Event types provided by the health plugin.\n */\nexport interface HealthEventTypes {\n\tdamage: DamageEvent;\n\tentityDied: EntityDiedEvent;\n}\n\n// ==================== WorldConfig ====================\n\n/**\n * WorldConfig representing the health plugin's provided types.\n * Used as the `Requires` type parameter by plugins that depend on health.\n */\nexport type HealthWorldConfig = WorldConfigFrom<HealthComponentTypes, HealthEventTypes>;\n\n// ==================== Plugin Options ====================\n\nexport interface HealthPluginOptions<G extends string = 'combat'> extends BasePluginOptions<G> {}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a health component at full HP.\n *\n * @param max Maximum (and initial) health\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createHealth(100),\n * ...createLocalTransform(200, 300),\n * });\n * ```\n */\nexport function createHealth(max: number): Pick<HealthComponentTypes, 'health'> {\n\treturn { health: { current: max, max } };\n}\n\n/**\n * Create a health component with a specific current value.\n *\n * @param current Current health\n * @param max Maximum health\n * @returns Component object suitable for spreading into spawn()\n */\nexport function createHealthWith(current: number, max: number): Pick<HealthComponentTypes, 'health'> {\n\treturn { health: { current, max } };\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a health plugin for ECSpresso.\n *\n * Provides event-driven damage processing. Subscribe to `damage` events\n * to deal damage, and listen to `entityDied` events to react to deaths.\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createHealthPlugin())\n * .build();\n *\n * // Deal damage:\n * ecs.eventBus.publish('damage', { entityId: targetId, amount: 25 });\n *\n * // React to death:\n * ecs.on('entityDied', ({ entityId }) => {\n * ecs.commands.removeEntity(entityId);\n * });\n * ```\n */\nexport function createHealthPlugin<G extends string = 'combat'>(\n\toptions?: HealthPluginOptions<G>,\n) {\n\tconst {\n\t\tsystemGroup = 'combat',\n\t} = options ?? {};\n\n\treturn definePlugin('health')\n\t\t.withComponentTypes<HealthComponentTypes>()\n\t\t.withEventTypes<HealthEventTypes>()\n\t\t.withLabels<'health-damage'>()\n\t\t.withGroups<G>()\n\t\t.install((world) => {\n\t\t\tworld\n\t\t\t\t.addSystem('health-damage')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setEventHandlers({\n\t\t\t\t\tdamage({ data, ecs }) {\n\t\t\t\t\t\tconst health = ecs.getComponent(data.entityId, 'health');\n\t\t\t\t\t\tif (!health) return;\n\t\t\t\t\t\tif (health.current <= 0) return;\n\n\t\t\t\t\t\thealth.current = Math.max(0, health.current - data.amount);\n\t\t\t\t\t\tecs.markChanged(data.entityId, 'health');\n\n\t\t\t\t\t\tif (health.current <= 0) {\n\t\t\t\t\t\t\tecs.eventBus.publish('entityDied', {\n\t\t\t\t\t\t\t\tentityId: data.entityId,\n\t\t\t\t\t\t\t\tkillerId: data.sourceId,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t});\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "4cAUA,uBAAS,kBA2EF,SAAS,CAAY,CAAC,EAAmD,CAC/E,MAAO,CAAE,OAAQ,CAAE,QAAS,EAAK,KAAI,CAAE,EAUjC,SAAS,CAAgB,CAAC,EAAiB,EAAmD,CACpG,MAAO,CAAE,OAAQ,CAAE,UAAS,KAAI,CAAE,EA0B5B,SAAS,CAA+C,CAC9D,EACC,CACD,IACC,cAAc,UACX,GAAW,CAAC,EAEhB,OAAO,EAAa,QAAQ,EAC1B,mBAAyC,EACzC,eAAiC,EACjC,WAA4B,EAC5B,WAAc,EACd,QAAQ,CAAC,IAAU,CACnB,EACE,UAAU,eAAe,EACzB,QAAQ,CAAW,EACnB,iBAAiB,CACjB,MAAM,EAAG,OAAM,OAAO,CACrB,IAAM,EAAS,EAAI,aAAa,EAAK,SAAU,QAAQ,EACvD,GAAI,CAAC,EAAQ,OACb,GAAI,EAAO,SAAW,EAAG,OAKzB,GAHA,EAAO,QAAU,KAAK,IAAI,EAAG,EAAO,QAAU,EAAK,MAAM,EACzD,EAAI,YAAY,EAAK,SAAU,QAAQ,EAEnC,EAAO,SAAW,EACrB,EAAI,SAAS,QAAQ,aAAc,CAClC,SAAU,EAAK,SACf,SAAU,EAAK,QAChB,CAAC,EAGJ,CAAC,EACF",
|
|
8
|
+
"debugId": "B2A67A4C6953F66664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var Z=((v)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(
|
|
1
|
+
var Y=Object.defineProperty;var Z=(k)=>k;function _(k,v){this[k]=Z.bind(null,v)}var b=(k,v)=>{for(var C in v)Y(k,C,{get:v[C],enumerable:!0,configurable:!0,set:_.bind(v,C)})};var B=(k,v)=>()=>(k&&(v=k(k=0)),v);var S=((k)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(k,{get:(v,C)=>(typeof require<"u"?require:v)[C]}):k)(function(k){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+k+'" is not supported')});import{definePlugin as $}from"ecspresso";function A(k,v,C){return{projectile:{damage:k,speed:v,sourceId:C}}}function q(k){return{projectileTarget:{entityId:k}}}function w(k,v){let C=Math.sqrt(k*k+v*v);if(C===0)return{projectileDirection:{x:0,y:-1}};return{projectileDirection:{x:k/C,y:v/C}}}function h(k){let{systemGroup:v="combat",priority:C=300,phase:U="update",publishDamage:X=!0}=k??{};return $("projectile").withComponentTypes().withEventTypes().withLabels().withGroups().requires().install((R)=>{R.addSystem("projectile-homing").setPriority(C).inPhase(U).inGroup(v).addQuery("homing",{with:["projectile","projectileTarget","localTransform"]}).setProcess(({queries:F,ecs:z,dt:M})=>{for(let K of F.homing){let{projectile:L,projectileTarget:H,localTransform:E}=K.components;if(!z.getEntity(H.entityId)){z.commands.removeEntity(K.id);continue}let J=z.getComponent(H.entityId,"worldTransform");if(!J){z.commands.removeEntity(K.id);continue}let N=J.x-E.x,O=J.y-E.y,V=N*N+O*O,Q=L.speed*M;if(V<=Q*Q)E.x=J.x,E.y=J.y;else{let W=Math.sqrt(V);E.x+=N/W*Q,E.y+=O/W*Q,E.rotation=Math.atan2(O,N)}z.markChanged(K.id,"localTransform")}}),R.addSystem("projectile-linear").setPriority(C).inPhase(U).inGroup(v).addQuery("linear",{with:["projectile","projectileDirection","localTransform"]}).setProcess(({queries:F,dt:z})=>{for(let M of F.linear){let{projectile:K,projectileDirection:L,localTransform:H}=M.components,E=K.speed*z;H.x+=L.x*E,H.y+=L.y*E}}),R.addSystem("projectile-collision").inGroup(v).setEventHandlers({collision({data:F,ecs:z}){if(!z.getEntity(F.entityA)||!z.getEntity(F.entityB))return;let M=z.getComponent(F.entityA,"projectile"),K=z.getComponent(F.entityB,"projectile"),L=M!==void 0,H=L?M:K;if(!H)return;let E=L?F.entityA:F.entityB,J=L?F.entityB:F.entityA;if(J===H.sourceId)return;if(z.eventBus.publish("projectileHit",{projectileId:E,targetId:J,damage:H.damage}),X)z.eventBus.publish("damage",{entityId:J,amount:H.damage,sourceId:H.sourceId});z.commands.removeEntity(E)}})})}export{q as createProjectileTarget,h as createProjectilePlugin,w as createProjectileDirection,A as createProjectile};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=2FD63C0EC856D97064756E2164756E21
|
|
4
4
|
//# sourceMappingURL=projectile.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * Projectile Plugin for ECSpresso\n *\n * Provides projectile movement (homing and linear) and collision integration.\n * Homing projectiles track a target entity's position each frame.\n * Linear projectiles move in a fixed direction.\n * When a collision involves a projectile, a `projectileHit` event is published\n * and a `damage` event is forwarded to the target (if the health plugin is present).\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { WorldConfigFrom } from 'ecspresso';\nimport type { TransformWorldConfig } from '../spatial/transform';\nimport type { CollisionEventTypes } from '../physics/collision';\nimport type { DamageEvent } from './health';\n\n// ==================== Component Types ====================\n\n/**\n * Core projectile data.\n */\nexport interface Projectile {\n\tdamage: number;\n\tspeed: number;\n\t/** Entity that fired this projectile */\n\tsourceId: number;\n}\n\n/**\n * Homing target — projectile tracks this entity's position each frame.\n */\nexport interface ProjectileTarget {\n\tentityId: number;\n}\n\n/**\n * Fixed direction for non-homing projectiles (normalized).\n */\nexport interface ProjectileDirection {\n\tx: number;\n\ty: number;\n}\n\n/**\n * Component types provided by the projectile plugin.\n */\nexport interface ProjectileComponentTypes {\n\tprojectile: Projectile;\n\tprojectileTarget: ProjectileTarget;\n\tprojectileDirection: ProjectileDirection;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event fired when a projectile hits a target via collision.\n */\nexport interface ProjectileHitEvent {\n\tprojectileId: number;\n\ttargetId: number;\n\tdamage: number;\n}\n\n/**\n * Event types provided by the projectile plugin.\n */\nexport interface ProjectileEventTypes {\n\tprojectileHit: ProjectileHitEvent;\n\tdamage: DamageEvent;\n}\n\n// ==================== WorldConfig ====================\n\n/**\n * WorldConfig representing the projectile plugin's provided types.\n */\nexport type ProjectileWorldConfig = WorldConfigFrom<ProjectileComponentTypes, ProjectileEventTypes>;\n\n// ==================== Plugin Options ====================\n\nexport interface ProjectilePluginOptions<G extends string = 'combat'> extends BasePluginOptions<G> {\n\t/**\n\t * Whether to auto-publish `damage` events on hit.\n\t * Requires the health plugin to be installed. (default: true)\n\t */\n\tpublishDamage?: boolean;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a projectile component.\n *\n * @param damage Damage dealt on hit\n * @param speed Movement speed in pixels per second\n * @param sourceId Entity that fired this projectile\n * @returns Component object suitable for spreading into spawn()\n */\nexport function createProjectile(\n\tdamage: number,\n\tspeed: number,\n\tsourceId: number,\n): Pick<ProjectileComponentTypes, 'projectile'> {\n\treturn { projectile: { damage, speed, sourceId } };\n}\n\n/**\n * Create a homing projectile target component.\n *\n * @param entityId Target entity to track\n * @returns Component object suitable for spreading into spawn()\n */\nexport function createProjectileTarget(entityId: number): Pick<ProjectileComponentTypes, 'projectileTarget'> {\n\treturn { projectileTarget: { entityId } };\n}\n\n/**\n * Create a fixed-direction projectile component (auto-normalizes).\n *\n * @param x Direction x\n * @param y Direction y\n * @returns Component object suitable for spreading into spawn()\n */\nexport function createProjectileDirection(x: number, y: number): Pick<ProjectileComponentTypes, 'projectileDirection'> {\n\tconst len = Math.sqrt(x * x + y * y);\n\tif (len === 0) return { projectileDirection: { x: 0, y: -1 } };\n\treturn { projectileDirection: { x: x / len, y: y / len } };\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a projectile plugin for ECSpresso.\n *\n * Provides homing and linear projectile movement systems, plus\n * automatic collision-to-damage integration.\n *\n * @example\n * ```typescript\n * // Spawn a homing projectile:\n * ecs.spawn({\n * ...createProjectile(10, 400, turretId),\n * ...createProjectileTarget(enemyId),\n * ...createLocalTransform(x, y),\n * ...createCircleCollider(4),\n * ...collisionLayers.turretProjectile(),\n * sprite: bulletSprite,\n * renderLayer: 'projectiles',\n * });\n * ```\n */\nexport function createProjectilePlugin<G extends string = 'combat'>(\n\toptions?: ProjectilePluginOptions<G>,\n) {\n\tconst {\n\t\tsystemGroup = 'combat',\n\t\tpriority = 300,\n\t\tphase = 'update',\n\t\tpublishDamage = true,\n\t} = options ?? {};\n\n\treturn definePlugin('projectile')\n\t\t.withComponentTypes<ProjectileComponentTypes>()\n\t\t.withEventTypes<ProjectileEventTypes>()\n\t\t.withLabels<'projectile-homing' | 'projectile-linear' | 'projectile-collision'>()\n\t\t.withGroups<G>()\n\t\t.requires<\n\t\t\tTransformWorldConfig &\n\t\t\tWorldConfigFrom<{}, CollisionEventTypes<string>>\n\t\t>()\n\t\t.install((world) => {\n\t\t\t// Homing projectiles — track target position each frame\n\t\t\tworld\n\t\t\t\t.addSystem('projectile-homing')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('homing', {\n\t\t\t\t\twith: ['projectile', 'projectileTarget', 'localTransform'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs, dt }) => {\n\t\t\t\t\tfor (const entity of queries.homing) {\n\t\t\t\t\t\tconst { projectile, projectileTarget, localTransform } = entity.components;\n\n\t\t\t\t\t\t// Target no longer exists — remove projectile\n\t\t\t\t\t\tif (!ecs.getEntity(projectileTarget.entityId)) {\n\t\t\t\t\t\t\tecs.commands.removeEntity(entity.id);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst targetTransform = ecs.getComponent(projectileTarget.entityId, 'worldTransform');\n\t\t\t\t\t\tif (!targetTransform) {\n\t\t\t\t\t\t\tecs.commands.removeEntity(entity.id);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst dx = targetTransform.x - localTransform.x;\n\t\t\t\t\t\tconst dy = targetTransform.y - localTransform.y;\n\t\t\t\t\t\tconst distSq = dx * dx + dy * dy;\n\t\t\t\t\t\tconst step = projectile.speed * dt;\n\n\t\t\t\t\t\tif (distSq <= step * step) {\n\t\t\t\t\t\t\tlocalTransform.x = targetTransform.x;\n\t\t\t\t\t\t\tlocalTransform.y = targetTransform.y;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst dist = Math.sqrt(distSq);\n\t\t\t\t\t\t\tlocalTransform.x += (dx / dist) * step;\n\t\t\t\t\t\t\tlocalTransform.y += (dy / dist) * step;\n\t\t\t\t\t\t\tlocalTransform.rotation = Math.atan2(dy, dx);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// Linear projectiles — move in fixed direction\n\t\t\tworld\n\t\t\t\t.addSystem('projectile-linear')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('linear', {\n\t\t\t\t\twith: ['projectile', 'projectileDirection', 'localTransform'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, dt }) => {\n\t\t\t\t\tfor (const entity of queries.linear) {\n\t\t\t\t\t\tconst { projectile, projectileDirection, localTransform } = entity.components;\n\t\t\t\t\t\tconst step = projectile.speed * dt;\n\t\t\t\t\t\tlocalTransform.x += projectileDirection.x * step;\n\t\t\t\t\t\tlocalTransform.y += projectileDirection.y * step;\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// Collision integration — route collision events to projectileHit + damage\n\t\t\tworld\n\t\t\t\t.addSystem('projectile-collision')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setEventHandlers({\n\t\t\t\t\tcollision({ data, ecs }) {\n\t\t\t\t\t\tif (!ecs.getEntity(data.entityA) || !ecs.getEntity(data.entityB)) return;\n\n\t\t\t\t\t\tconst projectileA = ecs.getComponent(data.entityA, 'projectile');\n\t\t\t\t\t\tconst projectileB = ecs.getComponent(data.entityB, 'projectile');\n\n\t\t\t\t\t\tconst isAProjectile = projectileA !== undefined;\n\t\t\t\t\t\tconst projectileData = isAProjectile ? projectileA : projectileB;\n\t\t\t\t\t\tif (!projectileData) return;\n\n\t\t\t\t\t\tconst projectileId = isAProjectile ? data.entityA : data.entityB;\n\t\t\t\t\t\tconst targetId = isAProjectile ? data.entityB : data.entityA;\n\n\t\t\t\t\t\t// Don't hit the entity that fired this projectile\n\t\t\t\t\t\tif (targetId === projectileData.sourceId) return;\n\n\t\t\t\t\t\tecs.eventBus.publish('projectileHit', {\n\t\t\t\t\t\t\tprojectileId,\n\t\t\t\t\t\t\ttargetId,\n\t\t\t\t\t\t\tdamage: projectileData.damage,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tif (publishDamage) {\n\t\t\t\t\t\t\tecs.eventBus.publish('damage', {\n\t\t\t\t\t\t\t\tentityId: targetId,\n\t\t\t\t\t\t\t\tamount: projectileData.damage,\n\t\t\t\t\t\t\t\tsourceId: projectileData.sourceId,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tecs.commands.removeEntity(projectileId);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t});\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "2FD63C0EC856D97064756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)},
|
|
1
|
+
var $=Object.defineProperty;var A=(j)=>j;function G(j,k){this[j]=A.bind(null,k)}var Y=(j,k)=>{for(var q in k)$(j,q,{get:k[q],enumerable:!0,configurable:!0,set:G.bind(k,q)})};var _=(j,k)=>()=>(j&&(k=j(j=0)),k);var D=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(k,q)=>(typeof require<"u"?require:k)[q]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as L}from"ecspresso";function M(j){let k=new Float64Array(j),q=0,z=0;return{push(Q){if(k[q]=Q,q=(q+1)%j,z<j)z++},computeFps(){if(z<2)return 0;let Q=k[(q-1+j)%j]??0,U=k[(q-z+j)%j]??0,F=Q-U;if(F<=0)return 0;return(z-1)/F*1000},computeAverageFrameTime(){if(z<2)return 0;let Q=k[(q-1+j)%j]??0,U=k[(q-z+j)%j]??0,F=Q-U;if(F<=0)return 0;return F/(z-1)},get size(){return z}}}function R(j){let{systemGroup:k="diagnostics",enableTimingOnInit:q=!0,fpsSampleCount:z=60}=j??{},Q={fps:0,entityCount:0,systemTimings:new Map,phaseTimings:{preUpdate:0,fixedUpdate:0,update:0,postUpdate:0,render:0},averageFrameTime:0},U=M(z);return L("diagnostics").withResourceTypes().withLabels().withGroups().install((F)=>{F.addResource("diagnostics",Q),F.addSystem("diagnostics-collect").setPriority(-999999).inPhase("render").inGroup(k).setOnInitialize((J)=>{if(q)J.enableDiagnostics(!0)}).setOnDetach((J)=>{J.enableDiagnostics(!1)}).setProcess(({ecs:J})=>{let V=performance.now();U.push(V);let K=J.getResource("diagnostics"),H={fps:U.computeFps(),entityCount:J.entityCount,systemTimings:J.systemTimings,phaseTimings:J.phaseTimings,averageFrameTime:U.computeAverageFrameTime()};K.fps=H.fps,K.entityCount=H.entityCount,K.systemTimings=H.systemTimings,K.phaseTimings=H.phaseTimings,K.averageFrameTime=H.averageFrameTime})})}var N={"top-left":"top:8px;left:8px","top-right":"top:8px;right:8px","bottom-left":"bottom:8px;left:8px","bottom-right":"bottom:8px;right:8px"};function B(j,k){let{position:q="top-left",updateInterval:z=200,showSystemTimings:Q=!0,maxSystemsShown:U=10}=k??{},F=document.createElement("div");F.style.cssText=`position:fixed;${N[q]};z-index:999999;background:rgba(0,0,0,0.8);color:#0f0;font:12px/1.4 monospace;padding:8px 12px;border-radius:4px;pointer-events:none;white-space:pre`,document.body.appendChild(F);let J=setInterval(()=>{let V=j.getResource("diagnostics"),K=[`FPS: ${V.fps.toFixed(0)}`,`Frame: ${V.averageFrameTime.toFixed(2)}ms`,`Entities: ${V.entityCount}`],H=V.phaseTimings;if(K.push(`Phases: pre=${H.preUpdate.toFixed(2)} fix=${H.fixedUpdate.toFixed(2)} upd=${H.update.toFixed(2)} post=${H.postUpdate.toFixed(2)} ren=${H.render.toFixed(2)}`),Q&&V.systemTimings.size>0){K.push("--- Systems ---");let Z=[...V.systemTimings.entries()].sort((W,X)=>X[1]-W[1]).slice(0,U);for(let[W,X]of Z)K.push(` ${W}: ${X.toFixed(3)}ms`)}F.textContent=K.join(`
|
|
2
|
+
`)},z);return()=>{clearInterval(J),F.remove()}}export{R as createDiagnosticsPlugin,B as createDiagnosticsOverlay};
|
|
3
3
|
|
|
4
|
-
//# debugId=
|
|
4
|
+
//# debugId=2396ABCE6E466B2664756E2164756E21
|
|
5
5
|
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * Diagnostics Plugin for ECSpresso\n *\n * Runtime diagnostics: FPS, entity count, per-system timing, per-phase timing,\n * and an optional DOM overlay for visual debugging.\n */\n\nimport { definePlugin } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\n\n// ==================== Types ====================\n\nexport interface DiagnosticsData {\n\tfps: number;\n\tentityCount: number;\n\tsystemTimings: ReadonlyMap<string, number>;\n\tphaseTimings: Readonly<Record<SystemPhase, number>>;\n\taverageFrameTime: number;\n}\n\nexport interface DiagnosticsResourceTypes {\n\tdiagnostics: DiagnosticsData;\n}\n\nexport interface DiagnosticsPluginOptions<G extends string = 'diagnostics'> {\n\t/** System group name (default: 'diagnostics') */\n\tsystemGroup?: G;\n\t/** Enable timing collection on initialize (default: true) */\n\tenableTimingOnInit?: boolean;\n\t/** Number of frames to sample for FPS average (default: 60) */\n\tfpsSampleCount?: number;\n}\n\nexport interface DiagnosticsOverlayOptions {\n\t/** Corner position (default: 'top-left') */\n\tposition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\t/** Milliseconds between DOM updates (default: 200) */\n\tupdateInterval?: number;\n\t/** Show per-system timings (default: true) */\n\tshowSystemTimings?: boolean;\n\t/** Maximum systems to show in overlay (default: 10) */\n\tmaxSystemsShown?: number;\n}\n\n// ==================== Ring Buffer ====================\n\n/**\n * Fixed-size circular buffer for frame timestamps.\n * Avoids Array.shift() allocation on every frame.\n */\nfunction createRingBuffer(capacity: number) {\n\tconst buffer = new Float64Array(capacity);\n\tlet writeIndex = 0;\n\tlet count = 0;\n\n\treturn {\n\t\tpush(value: number): void {\n\t\t\tbuffer[writeIndex] = value;\n\t\t\twriteIndex = (writeIndex + 1) % capacity;\n\t\t\tif (count < capacity) count++;\n\t\t},\n\n\t\t/** Compute FPS from stored timestamps */\n\t\tcomputeFps(): number {\n\t\t\tif (count < 2) return 0;\n\t\t\tconst newest = buffer[(writeIndex - 1 + capacity) % capacity] ?? 0;\n\t\t\tconst oldest = buffer[(writeIndex - count + capacity) % capacity] ?? 0;\n\t\t\tconst elapsed = newest - oldest;\n\t\t\tif (elapsed <= 0) return 0;\n\t\t\treturn ((count - 1) / elapsed) * 1000;\n\t\t},\n\n\t\t/** Compute average frame time in ms */\n\t\tcomputeAverageFrameTime(): number {\n\t\t\tif (count < 2) return 0;\n\t\t\tconst newest = buffer[(writeIndex - 1 + capacity) % capacity] ?? 0;\n\t\t\tconst oldest = buffer[(writeIndex - count + capacity) % capacity] ?? 0;\n\t\t\tconst elapsed = newest - oldest;\n\t\t\tif (elapsed <= 0) return 0;\n\t\t\treturn elapsed / (count - 1);\n\t\t},\n\n\t\tget size(): number {\n\t\t\treturn count;\n\t\t},\n\t};\n}\n\n// ==================== Plugin Factory ====================\n\nexport function createDiagnosticsPlugin<G extends string = 'diagnostics'>(\n\toptions?: DiagnosticsPluginOptions<G>,\n) {\n\tconst {\n\t\tsystemGroup = 'diagnostics',\n\t\tenableTimingOnInit = true,\n\t\tfpsSampleCount = 60,\n\t} = options ?? {};\n\n\tconst initialData: DiagnosticsData = {\n\t\tfps: 0,\n\t\tentityCount: 0,\n\t\tsystemTimings: new Map(),\n\t\tphaseTimings: { preUpdate: 0, fixedUpdate: 0, update: 0, postUpdate: 0, render: 0 },\n\t\taverageFrameTime: 0,\n\t};\n\n\tconst ringBuffer = createRingBuffer(fpsSampleCount);\n\n\treturn definePlugin('diagnostics')\n\t\t.withResourceTypes<DiagnosticsResourceTypes>()\n\t\t.withLabels<'diagnostics-collect'>()\n\t\t.withGroups<G>()\n\t\t.install((world) => {\n\t\t\tworld.addResource('diagnostics', initialData);\n\n\t\t\tworld\n\t\t\t\t.addSystem('diagnostics-collect')\n\t\t\t\t.setPriority(-999999)\n\t\t\t\t.inPhase('render')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize((ecs) => {\n\t\t\t\t\tif (enableTimingOnInit) {\n\t\t\t\t\t\tecs.enableDiagnostics(true);\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.setOnDetach((ecs) => {\n\t\t\t\t\tecs.enableDiagnostics(false);\n\t\t\t\t})\n\t\t\t\t.setProcess(({ ecs }) => {\n\t\t\t\t\tconst now = performance.now();\n\t\t\t\t\tringBuffer.push(now);\n\n\t\t\t\t\tconst resource = ecs.getResource('diagnostics');\n\t\t\t\t\tconst updated: DiagnosticsData = {\n\t\t\t\t\t\tfps: ringBuffer.computeFps(),\n\t\t\t\t\t\tentityCount: ecs.entityCount,\n\t\t\t\t\t\tsystemTimings: ecs.systemTimings,\n\t\t\t\t\t\tphaseTimings: ecs.phaseTimings,\n\t\t\t\t\t\taverageFrameTime: ringBuffer.computeAverageFrameTime(),\n\t\t\t\t\t};\n\n\t\t\t\t\t// Mutate fields on the existing resource object to avoid allocation\n\t\t\t\t\t(resource as { -readonly [K in keyof DiagnosticsData]: DiagnosticsData[K] }).fps = updated.fps;\n\t\t\t\t\t(resource as { -readonly [K in keyof DiagnosticsData]: DiagnosticsData[K] }).entityCount = updated.entityCount;\n\t\t\t\t\t(resource as { -readonly [K in keyof DiagnosticsData]: DiagnosticsData[K] }).systemTimings = updated.systemTimings;\n\t\t\t\t\t(resource as { -readonly [K in keyof DiagnosticsData]: DiagnosticsData[K] }).phaseTimings = updated.phaseTimings;\n\t\t\t\t\t(resource as { -readonly [K in keyof DiagnosticsData]: DiagnosticsData[K] }).averageFrameTime = updated.averageFrameTime;\n\t\t\t\t});\n\t\t});\n}\n\n// ==================== Overlay Helper ====================\n\nconst POSITION_STYLES: Record<NonNullable<DiagnosticsOverlayOptions['position']>, string> = {\n\t'top-left': 'top:8px;left:8px',\n\t'top-right': 'top:8px;right:8px',\n\t'bottom-left': 'bottom:8px;left:8px',\n\t'bottom-right': 'bottom:8px;right:8px',\n} as const;\n\n/**\n * Create a DOM overlay that displays diagnostics data.\n * Returns a cleanup function that removes the element and clears the interval.\n *\n * @param ecs An ECSpresso instance with the diagnostics resource\n * @param options Overlay configuration\n * @returns Cleanup function\n */\nexport function createDiagnosticsOverlay<\n\tR extends DiagnosticsResourceTypes,\n>(\n\tecs: { getResource<K extends keyof R>(key: K): R[K] },\n\toptions?: DiagnosticsOverlayOptions,\n): () => void {\n\tconst {\n\t\tposition = 'top-left',\n\t\tupdateInterval = 200,\n\t\tshowSystemTimings = true,\n\t\tmaxSystemsShown = 10,\n\t} = options ?? {};\n\n\tconst el = document.createElement('div');\n\tel.style.cssText = `position:fixed;${POSITION_STYLES[position]};z-index:999999;background:rgba(0,0,0,0.8);color:#0f0;font:12px/1.4 monospace;padding:8px 12px;border-radius:4px;pointer-events:none;white-space:pre`;\n\tdocument.body.appendChild(el);\n\n\tconst intervalId = setInterval(() => {\n\t\tconst d = ecs.getResource('diagnostics' as keyof R) as DiagnosticsData;\n\n\t\tconst lines: string[] = [\n\t\t\t`FPS: ${d.fps.toFixed(0)}`,\n\t\t\t`Frame: ${d.averageFrameTime.toFixed(2)}ms`,\n\t\t\t`Entities: ${d.entityCount}`,\n\t\t];\n\n\t\tconst phases = d.phaseTimings;\n\t\tlines.push(\n\t\t\t`Phases: pre=${phases.preUpdate.toFixed(2)} fix=${phases.fixedUpdate.toFixed(2)} upd=${phases.update.toFixed(2)} post=${phases.postUpdate.toFixed(2)} ren=${phases.render.toFixed(2)}`,\n\t\t);\n\n\t\tif (showSystemTimings && d.systemTimings.size > 0) {\n\t\t\tlines.push('--- Systems ---');\n\t\t\tconst sorted = [...d.systemTimings.entries()]\n\t\t\t\t.sort((a, b) => b[1] - a[1])\n\t\t\t\t.slice(0, maxSystemsShown);\n\t\t\tfor (const [label, ms] of sorted) {\n\t\t\t\tlines.push(` ${label}: ${ms.toFixed(3)}ms`);\n\t\t\t}\n\t\t}\n\n\t\tel.textContent = lines.join('\\n');\n\t}, updateInterval);\n\n\treturn () => {\n\t\tclearInterval(intervalId);\n\t\tel.remove();\n\t};\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "2396ABCE6E466B2664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var A=Object.defineProperty;var D=(q)=>q;function R(q,O){this[q]=D.bind(null,O)}var u=(q,O)=>{for(var Q in O)A(q,Q,{get:O[Q],enumerable:!0,configurable:!0,set:R.bind(O,Q)})};var l=(q,O)=>()=>(q&&(O=q(q=0)),O);var y=((q)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(q,{get:(O,Q)=>(typeof require<"u"?require:O)[Q]}):q)(function(q){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+q+'" is not supported')});import{definePlugin as v}from"ecspresso";function d(q){return q}function P(){return{keysDown:new Set,keysPressed:[],keysReleased:[],buttonsDown:new Set,buttonsPressed:[],buttonsReleased:[],pointerX:0,pointerY:0,pointerDeltaX:0,pointerDeltaY:0,lastPointerX:0,lastPointerY:0,pointerMoved:!1}}var B=new Set,F=new Set;function S(){return{keysDown:B,keysPressed:B,keysReleased:B,buttonsDown:F,buttonsPressed:F,buttonsReleased:F,pointerX:0,pointerY:0,pointerDeltaX:0,pointerDeltaY:0,actionsActive:B,prevActionsActive:B}}function _(q,O,Q){let V=new Set;for(let[$,W]of Object.entries(q)){let Z=W.keys?.some((H)=>O.has(H))??!1,J=W.buttons?.some((H)=>Q.has(H))??!1;if(Z||J)V.add($)}return V}function b(q,O,Q){let V=new Set(q.keysDown),$=new Set(q.keysPressed),W=new Set(q.keysReleased),Z=new Set(q.buttonsDown),J=new Set(q.buttonsPressed),H=new Set(q.buttonsReleased),j=q.pointerMoved?q.pointerX-q.lastPointerX:0,X=q.pointerMoved?q.pointerY-q.lastPointerY:0,x=_(Q,V,Z),K={keysDown:V,keysPressed:$,keysReleased:W,buttonsDown:Z,buttonsPressed:J,buttonsReleased:H,pointerX:q.pointerX,pointerY:q.pointerY,pointerDeltaX:j,pointerDeltaY:X,actionsActive:x,prevActionsActive:O};return q.keysPressed=[],q.keysReleased=[],q.buttonsPressed=[],q.buttonsReleased=[],q.lastPointerX=q.pointerX,q.lastPointerY=q.pointerY,q.pointerMoved=!1,K}function c(q){let{systemGroup:O="input",priority:Q=100,phase:V="preUpdate",actions:$={},target:W=globalThis,coordinateTransform:Z}=q??{},J=P(),H=S(),j={...$},X=[],x={x:0,y:0},K={x:0,y:0},L={keyboard:{isDown:(z)=>H.keysDown.has(z),justPressed:(z)=>H.keysPressed.has(z),justReleased:(z)=>H.keysReleased.has(z)},pointer:{position:x,delta:K,isDown:(z)=>H.buttonsDown.has(z),justPressed:(z)=>H.buttonsPressed.has(z),justReleased:(z)=>H.buttonsReleased.has(z)},actions:{isActive:(z)=>H.actionsActive.has(z),justActivated:(z)=>H.actionsActive.has(z)&&!H.prevActionsActive.has(z),justDeactivated:(z)=>!H.actionsActive.has(z)&&H.prevActionsActive.has(z)},setActionMap(z){j={...z}},getActionMap(){return{...j}}};function m(z){let C=z;if(C.repeat)return;J.keysDown.add(C.key),J.keysPressed.push(C.key)}function G(z){let C=z;J.keysDown.delete(C.key),J.keysReleased.push(C.key)}function I(z){let C=z;J.buttonsDown.add(C.button),J.buttonsPressed.push(C.button)}function U(z){let C=z;if(Z){let{x:N,y:Y}=Z(C.clientX,C.clientY);J.pointerX=N,J.pointerY=Y}else J.pointerX=C.clientX,J.pointerY=C.clientY;J.pointerMoved=!0}function g(z){let C=z;J.buttonsDown.delete(C.button),J.buttonsReleased.push(C.button)}function f(z,C){W.addEventListener(z,C),X.push(()=>{W.removeEventListener(z,C)})}return v("input").withResourceTypes().withLabels().withGroups().install((z)=>{z.addResource("inputState",L),z.addSystem("input-state").setPriority(Q).inPhase(V).inGroup(O).setOnInitialize(()=>{f("keydown",m),f("keyup",G),f("pointerdown",I),f("pointermove",U),f("pointerup",g)}).setOnDetach(()=>{for(let C of X)C();X.length=0}).setProcess(()=>{let C=H.actionsActive;H=b(J,C,j),x.x=H.pointerX,x.y=H.pointerY,K.x=H.pointerDeltaX,K.y=H.pointerDeltaY})})}export{c as createInputPlugin,d as createActionBinding};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=D1AADEB620C63DED64756E2164756E21
|
|
4
4
|
//# sourceMappingURL=input.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * Input Plugin for ECSpresso\n *\n * Provides frame-accurate keyboard, pointer (mouse + touch via PointerEvent),\n * and action mapping input. Resource-only plugin — input is polled via the\n * `inputState` resource. No ECS components or events.\n *\n * DOM events are accumulated between frames and snapshotted once per frame\n * in the system's process step, so all systems see consistent state.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\n\n// ==================== Public Types ====================\n\nexport interface Vec2 {\n\tx: number;\n\ty: number;\n}\n\n// Key codes per the UI Events spec (KeyboardEvent.key values)\n// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values\n\ntype LowercaseLetter =\n\t| 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm'\n\t| 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';\n\ntype Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';\n\ntype Punctuation =\n\t| '`' | '~' | '!' | '@' | '#' | '$' | '%' | '^' | '&' | '*' | '(' | ')'\n\t| '-' | '_' | '=' | '+' | '[' | '{' | ']' | '}' | '\\\\' | '|'\n\t| ';' | ':' | \"'\" | '\"' | ',' | '<' | '.' | '>' | '/' | '?';\n\ntype ModifierKey =\n\t| 'Alt' | 'AltGraph' | 'CapsLock' | 'Control' | 'Fn' | 'FnLock'\n\t| 'Hyper' | 'Meta' | 'NumLock' | 'ScrollLock' | 'Shift'\n\t| 'Super' | 'Symbol' | 'SymbolLock';\n\ntype WhitespaceKey = 'Enter' | 'Tab' | ' ';\n\ntype NavigationKey =\n\t| `Arrow${'Down' | 'Left' | 'Right' | 'Up'}`\n\t| 'End' | 'Home' | 'PageDown' | 'PageUp';\n\ntype EditingKey =\n\t| 'Backspace' | 'Clear' | 'Copy' | 'CrSel' | 'Cut' | 'Delete'\n\t| 'EraseEof' | 'ExSel' | 'Insert' | 'Paste' | 'Redo' | 'Undo';\n\ntype UIKey =\n\t| 'Accept' | 'Again' | 'Attn' | 'Cancel' | 'ContextMenu' | 'Escape'\n\t| 'Execute' | 'Find' | 'Finish' | 'Help' | 'Pause' | 'Play'\n\t| 'Props' | 'Select' | 'ZoomIn' | 'ZoomOut';\n\ntype DeviceKey =\n\t| 'BrightnessDown' | 'BrightnessUp' | 'Eject' | 'Hibernate'\n\t| 'LogOff' | 'Power' | 'PowerOff' | 'PrintScreen' | 'Standby' | 'WakeUp';\n\ntype IMEKey =\n\t| 'AllCandidates' | 'Alphanumeric' | 'CodeInput' | 'Compose' | 'Convert'\n\t| 'FinalMode' | 'GroupFirst' | 'GroupLast' | 'GroupNext' | 'GroupPrevious'\n\t| 'ModeChange' | 'NextCandidate' | 'NonConvert' | 'PreviousCandidate'\n\t| 'Process' | 'SingleCandidate'\n\t| 'HangulMode' | 'HanjaMode' | 'JunjaMode'\n\t| 'Eisu' | 'Hankaku' | 'Hiragana' | 'HiraganaKatakana' | 'KanaMode'\n\t| 'KanjiMode' | 'Katakana' | 'Romaji' | 'Zenkaku' | 'ZenkakuHankaku';\n\ntype FunctionKey =\n\t| `F${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24}`\n\t| 'Soft1' | 'Soft2' | 'Soft3' | 'Soft4';\n\ntype PhoneKey =\n\t| 'AppSwitch' | 'Call' | 'Camera' | 'CameraFocus' | 'EndCall'\n\t| 'GoBack' | 'GoHome' | 'HeadsetHook' | 'LastNumberRedial'\n\t| 'Notification' | 'MannerMode' | 'VoiceDial';\n\ntype MultimediaKey =\n\t| 'ChannelDown' | 'ChannelUp'\n\t| `Media${\n\t\t'FastForward' | 'Pause' | 'Play' | 'PlayPause'\n\t\t| 'Record' | 'Rewind' | 'Stop' | 'TrackNext' | 'TrackPrevious'\n\t}`;\n\ntype AudioKey =\n\t| `Audio${\n\t\t'BalanceLeft' | 'BalanceRight' | 'BassDown' | 'BassBoostDown'\n\t\t| 'BassBoostToggle' | 'BassBoostUp' | 'BassUp' | 'FaderFront' | 'FaderRear'\n\t\t| 'SurroundModeNext' | 'TrebleDown' | 'TrebleUp'\n\t\t| 'VolumeDown' | 'VolumeMute' | 'VolumeUp'\n\t}`\n\t| `Microphone${'Toggle' | 'VolumeDown' | 'VolumeMute' | 'VolumeUp'}`;\n\ntype TVKey =\n\t| 'TV'\n\t| `TV${\n\t\t'3DMode' | 'AntennaCable' | 'AudioDescription' | 'AudioDescriptionMixDown'\n\t\t| 'AudioDescriptionMixUp' | 'ContentsMenu' | 'DataService' | 'Input'\n\t\t| 'InputComponent1' | 'InputComponent2' | 'InputComposite1' | 'InputComposite2'\n\t\t| 'InputHDMI1' | 'InputHDMI2' | 'InputHDMI3' | 'InputHDMI4' | 'InputVGA1'\n\t\t| 'MediaContext' | 'Network' | 'NumberEntry' | 'Power' | 'RadioService'\n\t\t| 'Satellite' | 'SatelliteBS' | 'SatelliteCS' | 'SatelliteToggle'\n\t\t| 'TerrestrialAnalog' | 'TerrestrialDigital' | 'Timer'\n\t}`;\n\ntype MediaControllerKey =\n\t| 'AVRInput' | 'AVRPower'\n\t| `Color${'F0Red' | 'F1Green' | 'F2Yellow' | 'F3Blue' | 'F4Grey' | 'F5Brown'}`\n\t| 'ClosedCaptionToggle' | 'Dimmer' | 'DisplaySwap' | 'DVR' | 'Exit'\n\t| `Favorite${'Clear' | 'Recall' | 'Store'}${0 | 1 | 2 | 3}`\n\t| 'Guide' | 'GuideNextDay' | 'GuidePreviousDay' | 'Info' | 'InstantReplay'\n\t| 'Link' | 'ListProgram' | 'LiveContent' | 'Lock'\n\t| `Media${\n\t\t'Apps' | 'AudioTrack' | 'Last' | 'SkipBackward'\n\t\t| 'SkipForward' | 'StepBackward' | 'StepForward' | 'TopMenu'\n\t}`\n\t| `Navigate${'In' | 'Next' | 'Out' | 'Previous'}`\n\t| 'NextFavoriteChannel' | 'NextUserProfile' | 'OnDemand' | 'Pairing'\n\t| `PinP${'Down' | 'Move' | 'Toggle' | 'Up'}`\n\t| `PlaySpeed${'Down' | 'Reset' | 'Up'}`\n\t| 'RandomToggle' | 'RcLowBattery' | 'RecordSpeedNext' | 'RfBypass'\n\t| 'ScanChannelsToggle' | 'ScreenModeNext' | 'Settings' | 'SplitScreenToggle'\n\t| 'STBInput' | 'STBPower' | 'Subtitle' | 'Teletext'\n\t| 'VideoModeNext' | 'Wink' | 'ZoomToggle';\n\ntype SpeechKey = 'SpeechCorrectionList' | 'SpeechInputToggle';\n\ntype DocumentKey =\n\t| 'Close' | 'New' | 'Open' | 'Print' | 'Save' | 'SpellCheck'\n\t| 'MailForward' | 'MailReply' | 'MailSend';\n\ntype LaunchKey = `Launch${\n\t| 'Calculator' | 'Calendar' | 'Contacts' | 'Mail' | 'MediaPlayer'\n\t| 'MusicPlayer' | 'MyComputer' | 'Phone' | 'ScreenSaver' | 'Spreadsheet'\n\t| 'WebBrowser' | 'WebCam' | 'WordProcessor'\n\t| `Application${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16}`\n}`;\n\ntype BrowserKey = `Browser${'Back' | 'Favorites' | 'Forward' | 'Home' | 'Refresh' | 'Search' | 'Stop'}`;\n\ntype NumpadKey = 'Decimal' | 'Key11' | 'Key12' | 'Multiply' | 'Add' | 'Divide' | 'Subtract' | 'Separator';\n\nexport type KeyCode =\n\t| LowercaseLetter | Uppercase<LowercaseLetter> | Digit | Punctuation\n\t| ModifierKey | WhitespaceKey | NavigationKey | EditingKey | UIKey | DeviceKey\n\t| IMEKey | FunctionKey | PhoneKey | MultimediaKey | AudioKey | TVKey\n\t| MediaControllerKey | SpeechKey | DocumentKey | LaunchKey | BrowserKey | NumpadKey\n\t| 'Unidentified' | 'Dead';\n\nexport interface KeyboardState {\n\tisDown(key: KeyCode): boolean;\n\tjustPressed(key: KeyCode): boolean;\n\tjustReleased(key: KeyCode): boolean;\n}\n\nexport interface PointerState {\n\treadonly position: Readonly<Vec2>;\n\treadonly delta: Readonly<Vec2>;\n\tisDown(button: number): boolean;\n\tjustPressed(button: number): boolean;\n\tjustReleased(button: number): boolean;\n}\n\nexport interface ActionState<A extends string = string> {\n\tisActive(action: A): boolean;\n\tjustActivated(action: A): boolean;\n\tjustDeactivated(action: A): boolean;\n}\n\nexport interface InputState<A extends string = string> {\n\treadonly keyboard: KeyboardState;\n\treadonly pointer: PointerState;\n\treadonly actions: ActionState<A>;\n\tsetActionMap(actions: ActionMap<A>): void;\n\tgetActionMap(): Readonly<ActionMap<A>>;\n}\n\nexport interface ActionBinding {\n\tkeys?: KeyCode[];\n\tbuttons?: number[];\n}\n\nexport type ActionMap<A extends string = string> = Record<A, ActionBinding>;\n\nexport interface InputResourceTypes<A extends string = string> {\n\tinputState: InputState<A>;\n}\n\nexport interface InputPluginOptions<A extends string = string, G extends string = 'input'> extends BasePluginOptions<G> {\n\t/** Initial action mappings */\n\tactions?: ActionMap<A>;\n\t/** EventTarget to attach listeners to (default: globalThis). Pass a custom target for testability. */\n\ttarget?: EventTarget;\n\t/**\n\t * Optional conversion from raw DOM client coordinates to the space `inputState.pointer.position` should report.\n\t * Renderer-agnostic: wire to `clientToLogical(...)` from renderer2D when using `screenScale`, or to a renderer-specific helper.\n\t * When omitted, pointer coords remain raw `clientX`/`clientY` (not canvas-relative).\n\t */\n\tcoordinateTransform?: (clientX: number, clientY: number) => { x: number; y: number };\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a single action binding.\n *\n * @param binding The binding configuration\n * @returns The same binding object\n */\nexport function createActionBinding(binding: ActionBinding): ActionBinding {\n\treturn binding;\n}\n\n// ==================== Internal Types ====================\n\ninterface RawInputState {\n\tkeysDown: Set<string>;\n\tkeysPressed: string[];\n\tkeysReleased: string[];\n\tbuttonsDown: Set<number>;\n\tbuttonsPressed: number[];\n\tbuttonsReleased: number[];\n\tpointerX: number;\n\tpointerY: number;\n\tpointerDeltaX: number;\n\tpointerDeltaY: number;\n\tlastPointerX: number;\n\tlastPointerY: number;\n\tpointerMoved: boolean;\n}\n\ninterface FrameSnapshot {\n\tkeysDown: ReadonlySet<string>;\n\tkeysPressed: ReadonlySet<string>;\n\tkeysReleased: ReadonlySet<string>;\n\tbuttonsDown: ReadonlySet<number>;\n\tbuttonsPressed: ReadonlySet<number>;\n\tbuttonsReleased: ReadonlySet<number>;\n\tpointerX: number;\n\tpointerY: number;\n\tpointerDeltaX: number;\n\tpointerDeltaY: number;\n\tactionsActive: ReadonlySet<string>;\n\tprevActionsActive: ReadonlySet<string>;\n}\n\n// ==================== Plugin Factory ====================\n\nfunction createRawInputState(): RawInputState {\n\treturn {\n\t\tkeysDown: new Set(),\n\t\tkeysPressed: [],\n\t\tkeysReleased: [],\n\t\tbuttonsDown: new Set(),\n\t\tbuttonsPressed: [],\n\t\tbuttonsReleased: [],\n\t\tpointerX: 0,\n\t\tpointerY: 0,\n\t\tpointerDeltaX: 0,\n\t\tpointerDeltaY: 0,\n\t\tlastPointerX: 0,\n\t\tlastPointerY: 0,\n\t\tpointerMoved: false,\n\t};\n}\n\nconst EMPTY_SET_STRING: ReadonlySet<string> = new Set<string>();\nconst EMPTY_SET_NUMBER: ReadonlySet<number> = new Set<number>();\n\nfunction createEmptySnapshot(): FrameSnapshot {\n\treturn {\n\t\tkeysDown: EMPTY_SET_STRING,\n\t\tkeysPressed: EMPTY_SET_STRING,\n\t\tkeysReleased: EMPTY_SET_STRING,\n\t\tbuttonsDown: EMPTY_SET_NUMBER,\n\t\tbuttonsPressed: EMPTY_SET_NUMBER,\n\t\tbuttonsReleased: EMPTY_SET_NUMBER,\n\t\tpointerX: 0,\n\t\tpointerY: 0,\n\t\tpointerDeltaX: 0,\n\t\tpointerDeltaY: 0,\n\t\tactionsActive: EMPTY_SET_STRING,\n\t\tprevActionsActive: EMPTY_SET_STRING,\n\t};\n}\n\nfunction computeActiveActions(\n\tactionMap: ActionMap,\n\tkeysDown: ReadonlySet<string>,\n\tbuttonsDown: ReadonlySet<number>,\n): Set<string> {\n\tconst active = new Set<string>();\n\tfor (const [name, binding] of Object.entries(actionMap)) {\n\t\tconst keyActive = binding.keys?.some((k) => keysDown.has(k)) ?? false;\n\t\tconst buttonActive = binding.buttons?.some((b) => buttonsDown.has(b)) ?? false;\n\t\tif (keyActive || buttonActive) {\n\t\t\tactive.add(name);\n\t\t}\n\t}\n\treturn active;\n}\n\nfunction snapshotRaw(raw: RawInputState, prevActionsActive: ReadonlySet<string>, actionMap: ActionMap): FrameSnapshot {\n\tconst keysDown = new Set(raw.keysDown);\n\tconst keysPressed = new Set(raw.keysPressed);\n\tconst keysReleased = new Set(raw.keysReleased);\n\tconst buttonsDown = new Set(raw.buttonsDown);\n\tconst buttonsPressed = new Set(raw.buttonsPressed);\n\tconst buttonsReleased = new Set(raw.buttonsReleased);\n\n\tconst pointerDeltaX = raw.pointerMoved ? raw.pointerX - raw.lastPointerX : 0;\n\tconst pointerDeltaY = raw.pointerMoved ? raw.pointerY - raw.lastPointerY : 0;\n\n\tconst actionsActive = computeActiveActions(actionMap, keysDown, buttonsDown);\n\n\tconst snapshot: FrameSnapshot = {\n\t\tkeysDown,\n\t\tkeysPressed,\n\t\tkeysReleased,\n\t\tbuttonsDown,\n\t\tbuttonsPressed,\n\t\tbuttonsReleased,\n\t\tpointerX: raw.pointerX,\n\t\tpointerY: raw.pointerY,\n\t\tpointerDeltaX,\n\t\tpointerDeltaY,\n\t\tactionsActive,\n\t\tprevActionsActive,\n\t};\n\n\t// Clear accumulation buffers\n\traw.keysPressed = [];\n\traw.keysReleased = [];\n\traw.buttonsPressed = [];\n\traw.buttonsReleased = [];\n\traw.lastPointerX = raw.pointerX;\n\traw.lastPointerY = raw.pointerY;\n\traw.pointerMoved = false;\n\n\treturn snapshot;\n}\n\n/**\n * Create an input plugin for ECSpresso.\n *\n * This plugin provides:\n * - Frame-accurate keyboard state (isDown, justPressed, justReleased)\n * - Pointer position/delta and button state (mouse + touch via PointerEvent)\n * - Named action mapping with runtime remapping\n * - Automatic listener cleanup on detach\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createInputPlugin({\n * actions: {\n * jump: { keys: [' ', 'ArrowUp'] },\n * shoot: { keys: ['z'], buttons: [0] },\n * },\n * }))\n * .build();\n *\n * // In a system:\n * const input = ecs.getResource('inputState');\n * if (input.actions.justActivated('jump')) { ... }\n * if (input.keyboard.isDown('ArrowRight')) { ... }\n * ```\n */\nexport function createInputPlugin<A extends string = string, G extends string = 'input'>(\n\toptions?: InputPluginOptions<A, G>\n) {\n\tconst {\n\t\tsystemGroup = 'input',\n\t\tpriority = 100,\n\t\tphase = 'preUpdate',\n\t\tactions: initialActions = {},\n\t\ttarget = globalThis,\n\t\tcoordinateTransform,\n\t} = options ?? {};\n\n\t// Closure state\n\tconst raw = createRawInputState();\n\tlet snapshot = createEmptySnapshot();\n\tlet actionMap: ActionMap = { ...initialActions };\n\tconst cleanupFns: Array<() => void> = [];\n\n\t// The position/delta objects exposed via the resource.\n\t// Updated in-place each frame to avoid allocations.\n\tconst position: Vec2 = { x: 0, y: 0 };\n\tconst delta: Vec2 = { x: 0, y: 0 };\n\n\t// Build the InputState resource that closes over snapshot\n\tconst keyboard: KeyboardState = {\n\t\tisDown: (key) => snapshot.keysDown.has(key),\n\t\tjustPressed: (key) => snapshot.keysPressed.has(key),\n\t\tjustReleased: (key) => snapshot.keysReleased.has(key),\n\t};\n\n\tconst pointer: PointerState = {\n\t\tposition,\n\t\tdelta,\n\t\tisDown: (button) => snapshot.buttonsDown.has(button),\n\t\tjustPressed: (button) => snapshot.buttonsPressed.has(button),\n\t\tjustReleased: (button) => snapshot.buttonsReleased.has(button),\n\t};\n\n\tconst actionState: ActionState<A> = {\n\t\tisActive: (action) => snapshot.actionsActive.has(action),\n\t\tjustActivated: (action) =>\n\t\t\tsnapshot.actionsActive.has(action) && !snapshot.prevActionsActive.has(action),\n\t\tjustDeactivated: (action) =>\n\t\t\t!snapshot.actionsActive.has(action) && snapshot.prevActionsActive.has(action),\n\t};\n\n\tconst inputState: InputState<A> = {\n\t\tkeyboard,\n\t\tpointer,\n\t\tactions: actionState,\n\t\tsetActionMap(newMap) {\n\t\t\tactionMap = { ...newMap };\n\t\t},\n\t\tgetActionMap() {\n\t\t\treturn { ...actionMap } as ActionMap<A>;\n\t\t},\n\t};\n\n\t// DOM event handlers\n\tfunction onKeyDown(e: Event) {\n\t\tconst ke = e as KeyboardEvent;\n\t\tif (ke.repeat) return;\n\t\traw.keysDown.add(ke.key);\n\t\traw.keysPressed.push(ke.key);\n\t}\n\n\tfunction onKeyUp(e: Event) {\n\t\tconst ke = e as KeyboardEvent;\n\t\traw.keysDown.delete(ke.key);\n\t\traw.keysReleased.push(ke.key);\n\t}\n\n\tfunction onPointerDown(e: Event) {\n\t\tconst pe = e as PointerEvent;\n\t\traw.buttonsDown.add(pe.button);\n\t\traw.buttonsPressed.push(pe.button);\n\t}\n\n\tfunction onPointerMove(e: Event) {\n\t\tconst pe = e as PointerEvent;\n\t\tif (coordinateTransform) {\n\t\t\tconst { x, y } = coordinateTransform(pe.clientX, pe.clientY);\n\t\t\traw.pointerX = x;\n\t\t\traw.pointerY = y;\n\t\t} else {\n\t\t\traw.pointerX = pe.clientX;\n\t\t\traw.pointerY = pe.clientY;\n\t\t}\n\t\traw.pointerMoved = true;\n\t}\n\n\tfunction onPointerUp(e: Event) {\n\t\tconst pe = e as PointerEvent;\n\t\traw.buttonsDown.delete(pe.button);\n\t\traw.buttonsReleased.push(pe.button);\n\t}\n\n\tfunction addListener(type: string, handler: (e: Event) => void) {\n\t\ttarget.addEventListener(type, handler);\n\t\tcleanupFns.push(() => { target.removeEventListener(type, handler); });\n\t}\n\n\treturn definePlugin('input')\n\t\t.withResourceTypes<InputResourceTypes<A>>()\n\t\t.withLabels<'input-state'>()\n\t\t.withGroups<G>()\n\t\t.install((world) => {\n\t\t\tworld.addResource('inputState', inputState);\n\n\t\t\tworld\n\t\t\t\t.addSystem('input-state')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize(() => {\n\t\t\t\t\taddListener('keydown', onKeyDown);\n\t\t\t\t\taddListener('keyup', onKeyUp);\n\t\t\t\t\taddListener('pointerdown', onPointerDown);\n\t\t\t\t\taddListener('pointermove', onPointerMove);\n\t\t\t\t\taddListener('pointerup', onPointerUp);\n\t\t\t\t})\n\t\t\t\t.setOnDetach(() => {\n\t\t\t\t\tfor (const cleanup of cleanupFns) {\n\t\t\t\t\t\tcleanup();\n\t\t\t\t\t}\n\t\t\t\t\tcleanupFns.length = 0;\n\t\t\t\t})\n\t\t\t\t.setProcess(() => {\n\t\t\t\t\tconst prevActionsActive = snapshot.actionsActive;\n\t\t\t\t\tsnapshot = snapshotRaw(raw, prevActionsActive, actionMap);\n\n\t\t\t\t\t// Update the exposed position/delta objects in-place\n\t\t\t\t\tposition.x = snapshot.pointerX;\n\t\t\t\t\tposition.y = snapshot.pointerY;\n\t\t\t\t\tdelta.x = snapshot.pointerDeltaX;\n\t\t\t\t\tdelta.y = snapshot.pointerDeltaY;\n\t\t\t\t});\n\t\t});\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "D1AADEB620C63DED64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var e=((P)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(P,{get:(M,K)=>(typeof require<"u"?require:M)[K]}):P)(function(P){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+P+'" is not supported')});import{definePlugin as r}from"ecspresso";var w={traumaDecay:1,maxOffsetX:10,maxOffsetY:10,maxRotation:0.05},u={smoothing:5,deadzoneX:0,deadzoneY:0,offsetX:0,offsetY:0};function QJ(P,M,K){let U=P-(K.x+K.shakeOffsetX),O=M-(K.y+K.shakeOffsetY),W=-(K.rotation+K.shakeRotation),E=Math.cos(W),A=Math.sin(W),y=U*E-O*A,q=U*A+O*E;return{x:y*K.zoom+K.viewportWidth/2,y:q*K.zoom+K.viewportHeight/2}}function p(P,M,K){let U=(P-K.viewportWidth/2)/K.zoom,O=(M-K.viewportHeight/2)/K.zoom,W=K.rotation+K.shakeRotation,E=Math.cos(W),A=Math.sin(W),y=U*E-O*A,q=U*A+O*E;return{x:y+K.x+K.shakeOffsetX,y:q+K.y+K.shakeOffsetY}}function o(P){return typeof P==="number"?P:P.id}function m(P){let M=P===!0?{}:P;return{trauma:0,traumaDecay:M.traumaDecay??w.traumaDecay,maxOffsetX:M.maxOffsetX??w.maxOffsetX,maxOffsetY:M.maxOffsetY??w.maxOffsetY,maxRotation:M.maxRotation??w.maxRotation}}function s(P){if(Array.isArray(P))return{minX:P[0],minY:P[1],maxX:P[2],maxY:P[3]};return{...P}}function i(P){return{smoothing:P?.smoothing??u.smoothing,deadzoneX:P?.deadzoneX??u.deadzoneX,deadzoneY:P?.deadzoneY??u.deadzoneY,offsetX:P?.offsetX??u.offsetX,offsetY:P?.offsetY??u.offsetY}}function $J(P){let{viewportWidth:M=800,viewportHeight:K=600,initial:U,follow:O,shake:W,bounds:E,zoom:A,pan:y,systemGroup:q="camera",phase:g="postUpdate",randomFn:f=Math.random}=P??{};return r("camera").withComponentTypes().withResourceTypes().withLabels().withGroups().requires().install((k)=>{let J={x:U?.x??0,y:U?.y??0,zoom:U?.zoom??1,rotation:U?.rotation??0,shakeOffsetX:0,shakeOffsetY:0,shakeRotation:0,viewportWidth:M,viewportHeight:K,entityId:-1,follow:()=>{},unfollow:()=>{},setPosition:()=>{},setZoom:()=>{},setRotation:()=>{},setBounds:()=>{},clearBounds:()=>{},addTrauma:()=>{}};if(k.addResource("cameraState",J),k.addSystem("camera-init").inGroup(q).setOnInitialize((R)=>{let V=R.spawn({camera:{x:U?.x??0,y:U?.y??0,zoom:U?.zoom??1,rotation:U?.rotation??0}});if(O)R.addComponent(V.id,"cameraFollow",{target:-1,...i(O)});if(W)R.addComponent(V.id,"cameraShake",m(W));if(E)R.addComponent(V.id,"cameraBounds",s(E));J.entityId=V.id,J.follow=(N,$)=>{let j={target:o(N),...i($)},Q=R.getComponent(J.entityId,"cameraFollow");if(Q)Q.target=j.target,Q.smoothing=j.smoothing,Q.deadzoneX=j.deadzoneX,Q.deadzoneY=j.deadzoneY,Q.offsetX=j.offsetX,Q.offsetY=j.offsetY;else R.addComponent(J.entityId,"cameraFollow",j)},J.unfollow=()=>{if(R.getComponent(J.entityId,"cameraFollow"))R.removeComponent(J.entityId,"cameraFollow")},J.setPosition=(N,$)=>{let Z=R.getComponent(J.entityId,"camera");if(!Z)return;Z.x=N,Z.y=$},J.setZoom=(N)=>{let $=R.getComponent(J.entityId,"camera");if(!$)return;$.zoom=N},J.setRotation=(N)=>{let $=R.getComponent(J.entityId,"camera");if(!$)return;$.rotation=N},J.setBounds=(N,$,Z,j)=>{let Q=R.getComponent(J.entityId,"cameraBounds");if(Q)Q.minX=N,Q.minY=$,Q.maxX=Z,Q.maxY=j;else R.addComponent(J.entityId,"cameraBounds",{minX:N,minY:$,maxX:Z,maxY:j})},J.clearBounds=()=>{if(R.getComponent(J.entityId,"cameraBounds"))R.removeComponent(J.entityId,"cameraBounds")},J.addTrauma=(N)=>{let $=R.getComponent(J.entityId,"cameraShake");if($)$.trauma=Math.min(1,Math.max(0,$.trauma+N));else R.addComponent(J.entityId,"cameraShake",{...m(!0),trauma:Math.min(1,Math.max(0,N))})}}),k.addSystem("camera-follow").setPriority(400).inPhase(g).inGroup(q).addQuery("cameras",{with:["camera","cameraFollow"]}).setProcess(({queries:R,dt:V,ecs:N})=>{let $=Math.min(1,V);for(let Z of R.cameras){let{camera:j,cameraFollow:Q}=Z.components;if(Q.target<0)continue;let _;try{_=N.getComponent(Q.target,"worldTransform")}catch{continue}if(!_)continue;let z=_.x+Q.offsetX,G=_.y+Q.offsetY,B=z-j.x,b=G-j.y;if(Math.abs(B)>Q.deadzoneX){let H=B>0?1:-1,D=B-H*Q.deadzoneX,F=Math.min(1,Q.smoothing*$);j.x+=D*F}if(Math.abs(b)>Q.deadzoneY){let H=b>0?1:-1,D=b-H*Q.deadzoneY,F=Math.min(1,Q.smoothing*$);j.y+=D*F}}}),k.addSystem("camera-shake-update").setPriority(390).inPhase(g).inGroup(q).addQuery("shakeCameras",{with:["camera","cameraShake"]}).setProcess(({queries:R,dt:V})=>{for(let N of R.shakeCameras){let{cameraShake:$}=N.components;$.trauma=Math.max(0,$.trauma-$.traumaDecay*V)}}),k.addSystem("camera-bounds").setPriority(380).inPhase(g).inGroup(q).addQuery("boundedCameras",{with:["camera","cameraBounds"]}).setProcess(({queries:R})=>{for(let V of R.boundedCameras){let{camera:N,cameraBounds:$}=V.components,Z=J.viewportWidth/(2*N.zoom),j=J.viewportHeight/(2*N.zoom),Q=$.minX+Z,_=$.maxX-Z,z=$.minY+j,G=$.maxY-j;if(Q>_)N.x=($.minX+$.maxX)/2;else N.x=Math.max(Q,Math.min(_,N.x));if(z>G)N.y=($.minY+$.maxY)/2;else N.y=Math.max(z,Math.min(G,N.y))}}),k.addSystem("camera-state-sync").setPriority(370).inPhase(g).inGroup(q).setProcess(({ecs:R})=>{let V=R.getComponent(J.entityId,"camera");if(!V){J.x=0,J.y=0,J.zoom=1,J.rotation=0,J.shakeOffsetX=0,J.shakeOffsetY=0,J.shakeRotation=0;return}J.x=V.x,J.y=V.y,J.zoom=V.zoom,J.rotation=V.rotation;let N=R.getComponent(J.entityId,"cameraShake");if(N&&N.trauma>0){let $=N.trauma*N.trauma;J.shakeOffsetX=N.maxOffsetX*$*(f()*2-1),J.shakeOffsetY=N.maxOffsetY*$*(f()*2-1),J.shakeRotation=N.maxRotation*$*(f()*2-1)}else J.shakeOffsetX=0,J.shakeOffsetY=0,J.shakeRotation=0}),A){let _=function(z){z.preventDefault(),$+=Math.sign(z.deltaY)},{zoomStep:R=0.1,minZoom:V=0.1,maxZoom:N=10}=A,$=0,Z=!1,j,Q;k.addSystem("camera-zoom").setPriority(410).inPhase("preUpdate").inGroup(q).addQuery("cameras",{with:["camera"]}).setOnInitialize((z)=>{let G=z.tryGetResource("inputState"),B=z.tryGetResource("pixiApp");if(!G||!B){console.error("[camera] zoom requires the input plugin and renderer2D plugin. Zoom will be disabled.");return}j=B.canvas,j.addEventListener("wheel",_,{passive:!1}),Q=z.tryGetResource("isoProjection"),Z=!0}).setOnDetach(()=>{if(!Z||!j)return;j.removeEventListener("wheel",_)}).setProcess(({queries:z,ecs:G})=>{if(!Z||$===0)return;let B=$;$=0;let[b]=z.cameras;if(!b)return;let H=b.components.camera,D=G.tryGetResource("inputState");if(!D)return;let F=B>0?1-R:1+R,C=Math.max(V,Math.min(N,H.zoom*Math.pow(F,Math.abs(B))));if(Q&&j){let I=j.getBoundingClientRect(),L=D.pointer.position.x-(I.left+I.width/2),v=D.pointer.position.y-(I.top+I.height/2),X=Q.tileWidth/2,Y=Q.tileHeight/2,h=(H.x-H.y)*X+Q.originX,T=(H.x+H.y)*Y+Q.originY,x=h+L/H.zoom,S=T+v/H.zoom;H.zoom=C;let c=x-L/C,n=S-v/C,d=c-Q.originX,l=n-Q.originY;H.x=d/Q.tileWidth+l/Q.tileHeight,H.y=-d/Q.tileWidth+l/Q.tileHeight}else{let I=p(D.pointer.position.x,D.pointer.position.y,J);H.zoom=C,H.x=I.x-(D.pointer.position.x-J.viewportWidth/2)/C,H.y=I.y-(D.pointer.position.y-J.viewportHeight/2)/C}})}if(y){let{speed:R,actions:V}=y,N=V?.up??"panUp",$=V?.down??"panDown",Z=V?.left??"panLeft",j=V?.right??"panRight",Q=!1;k.addSystem("camera-pan").setPriority(420).inPhase("preUpdate").inGroup(q).setOnInitialize((_)=>{if(!_.tryGetResource("inputState")){console.error("[camera] pan requires the input plugin. Pan will be disabled.");return}Q=!0}).setProcess(({ecs:_,dt:z})=>{if(!Q)return;let G=_.tryGetResource("inputState");if(!G)return;let B=R/J.zoom*z,b=(G.actions.isActive(j)?1:0)-(G.actions.isActive(Z)?1:0),H=(G.actions.isActive($)?1:0)-(G.actions.isActive(N)?1:0);if(b!==0||H!==0)J.setPosition(J.x+b*B,J.y+H*B)})}})}import{Graphics as a}from"pixi.js";import{definePlugin as t}from"ecspresso";function KJ(){return{selectable:!0}}function IJ(P){let{systemGroup:M="selection",priority:K=100,phase:U="preUpdate",clickThreshold:O=5,boxFillColor:W=65280,boxFillAlpha:E=0.15,boxStrokeColor:A=65280,boxStrokeAlpha:y=0.8,selectedTint:q=4521796,renderLayer:g}=P??{},f={color:W,alpha:E},k={color:A,width:1.5,alpha:y};return t("selection").withComponentTypes().withResourceTypes().withLabels().withGroups().requires().install((J)=>{J.addResource("selectionState",{dragStart:{x:0,y:0},boxEntityId:null});let R=null;J.addSystem("selection-input").setPriority(K).inPhase(U).inGroup(M).addQuery("selectables",{with:["selectable","worldTransform"]}).addQuery("currentlySelected",{with:["selected"]}).withResources(["inputState","selectionState","pixiApp"]).setOnInitialize((V)=>{let N=V.getResource("pixiApp");R=($)=>$.preventDefault(),N.canvas.addEventListener("contextmenu",R)}).setOnDetach((V)=>{if(!R)return;V.getResource("pixiApp").canvas.removeEventListener("contextmenu",R),R=null}).setProcess(({queries:V,ecs:N,resources:$})=>{let{inputState:Z,selectionState:j}=$,Q=Z.pointer;if(Q.justPressed(0)){if(j.boxEntityId!==null)N.commands.removeEntity(j.boxEntityId);j.dragStart.x=Q.position.x,j.dragStart.y=Q.position.y;let I=N.spawn({graphics:new a});if(g)N.addComponent(I.id,"renderLayer",g);j.boxEntityId=I.id}if(Q.isDown(0)&&j.boxEntityId!==null){let I=N.getComponent(j.boxEntityId,"graphics");if(!I)return;let L=j.dragStart.x,v=j.dragStart.y,X=Q.position.x,Y=Q.position.y,h=Math.min(L,X),T=Math.min(v,Y),x=Math.abs(X-L),S=Math.abs(Y-v);I.clear(),I.rect(h,T,x,S),I.fill(f),I.stroke(k)}if(!Q.justReleased(0)||j.boxEntityId===null)return;let _=j.dragStart.x,z=j.dragStart.y,G=Q.position.x,B=Q.position.y,b=Math.abs(G-_),H=Math.abs(B-z);for(let I of V.currentlySelected)N.removeComponent(I.id,"selected");let D=b<O&&H<O,F=N.tryGetResource("cameraState"),C=F?p(G,B,F):{x:G,y:B};if(D){let L=null,v=1/0;for(let X of V.selectables){let{worldTransform:Y}=X.components,h=Y.x-C.x,T=Y.y-C.y,x=h*h+T*T;if(x<400&&x<v)v=x,L=X.id}if(L!==null)N.addComponent(L,"selected",!0)}else{let I=F?p(_,z,F):{x:_,y:z},L=Math.min(I.x,C.x),v=Math.max(I.x,C.x),X=Math.min(I.y,C.y),Y=Math.max(I.y,C.y);for(let h of V.selectables){let{worldTransform:T}=h.components;if(T.x>=L&&T.x<=v&&T.y>=X&&T.y<=Y)N.addComponent(h.id,"selected",!0)}}N.commands.removeEntity(j.boxEntityId),j.boxEntityId=null}),J.addSystem("selection-visual").setPriority(K).inPhase("render").inGroup(M).addQuery("selectedUnits",{with:["selected","sprite"]}).setOnEntityEnter("selectedUnits",({entity:V})=>{V.components.sprite.tint=q}).addQuery("deselectedUnits",{with:["selectable","sprite"],without:["selected"]}).setOnEntityEnter("deselectedUnits",({entity:V})=>{V.components.sprite.tint=16777215})})}export{IJ as createSelectionPlugin,KJ as createSelectable};
|
|
1
|
+
var r=Object.defineProperty;var o=(j)=>j;function s(j,I){this[j]=o.bind(null,I)}var QJ=(j,I)=>{for(var K in I)r(j,K,{get:I[K],enumerable:!0,configurable:!0,set:s.bind(I,K)})};var $J=(j,I)=>()=>(j&&(I=j(j=0)),I);var jJ=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(I,K)=>(typeof require<"u"?require:I)[K]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as a}from"ecspresso";var w={traumaDecay:1,maxOffsetX:10,maxOffsetY:10,maxRotation:0.05},u={smoothing:5,deadzoneX:0,deadzoneY:0,offsetX:0,offsetY:0};function PJ(j,I,K){let B=j-(K.x+K.shakeOffsetX),O=I-(K.y+K.shakeOffsetY),W=-(K.rotation+K.shakeRotation),E=Math.cos(W),A=Math.sin(W),y=B*E-O*A,q=B*A+O*E;return{x:y*K.zoom+K.viewportWidth/2,y:q*K.zoom+K.viewportHeight/2}}function p(j,I,K){let B=(j-K.viewportWidth/2)/K.zoom,O=(I-K.viewportHeight/2)/K.zoom,W=K.rotation+K.shakeRotation,E=Math.cos(W),A=Math.sin(W),y=B*E-O*A,q=B*A+O*E;return{x:y+K.x+K.shakeOffsetX,y:q+K.y+K.shakeOffsetY}}function t(j){return typeof j==="number"?j:j.id}function m(j){let I=j===!0?{}:j;return{trauma:0,traumaDecay:I.traumaDecay??w.traumaDecay,maxOffsetX:I.maxOffsetX??w.maxOffsetX,maxOffsetY:I.maxOffsetY??w.maxOffsetY,maxRotation:I.maxRotation??w.maxRotation}}function e(j){if(Array.isArray(j))return{minX:j[0],minY:j[1],maxX:j[2],maxY:j[3]};return{...j}}function i(j){return{smoothing:j?.smoothing??u.smoothing,deadzoneX:j?.deadzoneX??u.deadzoneX,deadzoneY:j?.deadzoneY??u.deadzoneY,offsetX:j?.offsetX??u.offsetX,offsetY:j?.offsetY??u.offsetY}}function KJ(j){let{viewportWidth:I=800,viewportHeight:K=600,initial:B,follow:O,shake:W,bounds:E,zoom:A,pan:y,systemGroup:q="camera",phase:g="postUpdate",randomFn:f=Math.random}=j??{};return a("camera").withComponentTypes().withResourceTypes().withLabels().withGroups().requires().install((k)=>{let J={x:B?.x??0,y:B?.y??0,zoom:B?.zoom??1,rotation:B?.rotation??0,shakeOffsetX:0,shakeOffsetY:0,shakeRotation:0,viewportWidth:I,viewportHeight:K,entityId:-1,follow:()=>{},unfollow:()=>{},setPosition:()=>{},setZoom:()=>{},setRotation:()=>{},setBounds:()=>{},clearBounds:()=>{},addTrauma:()=>{}};if(k.addResource("cameraState",J),k.addSystem("camera-init").inGroup(q).setOnInitialize((V)=>{let P=V.spawn({camera:{x:B?.x??0,y:B?.y??0,zoom:B?.zoom??1,rotation:B?.rotation??0}});if(O)V.addComponent(P.id,"cameraFollow",{target:-1,...i(O)});if(W)V.addComponent(P.id,"cameraShake",m(W));if(E)V.addComponent(P.id,"cameraBounds",e(E));J.entityId=P.id,J.follow=(N,$)=>{let R={target:t(N),...i($)},Q=V.getComponent(J.entityId,"cameraFollow");if(Q)Q.target=R.target,Q.smoothing=R.smoothing,Q.deadzoneX=R.deadzoneX,Q.deadzoneY=R.deadzoneY,Q.offsetX=R.offsetX,Q.offsetY=R.offsetY;else V.addComponent(J.entityId,"cameraFollow",R)},J.unfollow=()=>{if(V.getComponent(J.entityId,"cameraFollow"))V.removeComponent(J.entityId,"cameraFollow")},J.setPosition=(N,$)=>{let z=V.getComponent(J.entityId,"camera");if(!z)return;z.x=N,z.y=$},J.setZoom=(N)=>{let $=V.getComponent(J.entityId,"camera");if(!$)return;$.zoom=N},J.setRotation=(N)=>{let $=V.getComponent(J.entityId,"camera");if(!$)return;$.rotation=N},J.setBounds=(N,$,z,R)=>{let Q=V.getComponent(J.entityId,"cameraBounds");if(Q)Q.minX=N,Q.minY=$,Q.maxX=z,Q.maxY=R;else V.addComponent(J.entityId,"cameraBounds",{minX:N,minY:$,maxX:z,maxY:R})},J.clearBounds=()=>{if(V.getComponent(J.entityId,"cameraBounds"))V.removeComponent(J.entityId,"cameraBounds")},J.addTrauma=(N)=>{let $=V.getComponent(J.entityId,"cameraShake");if($)$.trauma=Math.min(1,Math.max(0,$.trauma+N));else V.addComponent(J.entityId,"cameraShake",{...m(!0),trauma:Math.min(1,Math.max(0,N))})}}),k.addSystem("camera-follow").setPriority(400).inPhase(g).inGroup(q).addQuery("cameras",{with:["camera","cameraFollow"]}).setProcess(({queries:V,dt:P,ecs:N})=>{let $=Math.min(1,P);for(let z of V.cameras){let{camera:R,cameraFollow:Q}=z.components;if(Q.target<0)continue;let G;try{G=N.getComponent(Q.target,"worldTransform")}catch{continue}if(!G)continue;let _=G.x+Q.offsetX,U=G.y+Q.offsetY,M=_-R.x,b=U-R.y;if(Math.abs(M)>Q.deadzoneX){let Z=M>0?1:-1,D=M-Z*Q.deadzoneX,F=Math.min(1,Q.smoothing*$);R.x+=D*F}if(Math.abs(b)>Q.deadzoneY){let Z=b>0?1:-1,D=b-Z*Q.deadzoneY,F=Math.min(1,Q.smoothing*$);R.y+=D*F}}}),k.addSystem("camera-shake-update").setPriority(390).inPhase(g).inGroup(q).addQuery("shakeCameras",{with:["camera","cameraShake"]}).setProcess(({queries:V,dt:P})=>{for(let N of V.shakeCameras){let{cameraShake:$}=N.components;$.trauma=Math.max(0,$.trauma-$.traumaDecay*P)}}),k.addSystem("camera-bounds").setPriority(380).inPhase(g).inGroup(q).addQuery("boundedCameras",{with:["camera","cameraBounds"]}).setProcess(({queries:V})=>{for(let P of V.boundedCameras){let{camera:N,cameraBounds:$}=P.components,z=J.viewportWidth/(2*N.zoom),R=J.viewportHeight/(2*N.zoom),Q=$.minX+z,G=$.maxX-z,_=$.minY+R,U=$.maxY-R;if(Q>G)N.x=($.minX+$.maxX)/2;else N.x=Math.max(Q,Math.min(G,N.x));if(_>U)N.y=($.minY+$.maxY)/2;else N.y=Math.max(_,Math.min(U,N.y))}}),k.addSystem("camera-state-sync").setPriority(370).inPhase(g).inGroup(q).setProcess(({ecs:V})=>{let P=V.getComponent(J.entityId,"camera");if(!P){J.x=0,J.y=0,J.zoom=1,J.rotation=0,J.shakeOffsetX=0,J.shakeOffsetY=0,J.shakeRotation=0;return}J.x=P.x,J.y=P.y,J.zoom=P.zoom,J.rotation=P.rotation;let N=V.getComponent(J.entityId,"cameraShake");if(N&&N.trauma>0){let $=N.trauma*N.trauma;J.shakeOffsetX=N.maxOffsetX*$*(f()*2-1),J.shakeOffsetY=N.maxOffsetY*$*(f()*2-1),J.shakeRotation=N.maxRotation*$*(f()*2-1)}else J.shakeOffsetX=0,J.shakeOffsetY=0,J.shakeRotation=0}),A){let G=function(_){_.preventDefault(),$+=Math.sign(_.deltaY)},{zoomStep:V=0.1,minZoom:P=0.1,maxZoom:N=10}=A,$=0,z=!1,R,Q;k.addSystem("camera-zoom").setPriority(410).inPhase("preUpdate").inGroup(q).addQuery("cameras",{with:["camera"]}).setOnInitialize((_)=>{let U=_.tryGetResource("inputState"),M=_.tryGetResource("pixiApp");if(!U||!M){console.error("[camera] zoom requires the input plugin and renderer2D plugin. Zoom will be disabled.");return}R=M.canvas,R.addEventListener("wheel",G,{passive:!1}),Q=_.tryGetResource("isoProjection"),z=!0}).setOnDetach(()=>{if(!z||!R)return;R.removeEventListener("wheel",G)}).setProcess(({queries:_,ecs:U})=>{if(!z||$===0)return;let M=$;$=0;let[b]=_.cameras;if(!b)return;let Z=b.components.camera,D=U.tryGetResource("inputState");if(!D)return;let F=M>0?1-V:1+V,C=Math.max(P,Math.min(N,Z.zoom*Math.pow(F,Math.abs(M))));if(Q&&R){let H=R.getBoundingClientRect(),L=D.pointer.position.x-(H.left+H.width/2),v=D.pointer.position.y-(H.top+H.height/2),X=Q.tileWidth/2,Y=Q.tileHeight/2,h=(Z.x-Z.y)*X+Q.originX,T=(Z.x+Z.y)*Y+Q.originY,x=h+L/Z.zoom,S=T+v/Z.zoom;Z.zoom=C;let c=x-L/C,n=S-v/C,d=c-Q.originX,l=n-Q.originY;Z.x=d/Q.tileWidth+l/Q.tileHeight,Z.y=-d/Q.tileWidth+l/Q.tileHeight}else{let H=p(D.pointer.position.x,D.pointer.position.y,J);Z.zoom=C,Z.x=H.x-(D.pointer.position.x-J.viewportWidth/2)/C,Z.y=H.y-(D.pointer.position.y-J.viewportHeight/2)/C}})}if(y){let{speed:V,actions:P}=y,N=P?.up??"panUp",$=P?.down??"panDown",z=P?.left??"panLeft",R=P?.right??"panRight",Q=!1;k.addSystem("camera-pan").setPriority(420).inPhase("preUpdate").inGroup(q).setOnInitialize((G)=>{if(!G.tryGetResource("inputState")){console.error("[camera] pan requires the input plugin. Pan will be disabled.");return}Q=!0}).setProcess(({ecs:G,dt:_})=>{if(!Q)return;let U=G.tryGetResource("inputState");if(!U)return;let M=V/J.zoom*_,b=(U.actions.isActive(R)?1:0)-(U.actions.isActive(z)?1:0),Z=(U.actions.isActive($)?1:0)-(U.actions.isActive(N)?1:0);if(b!==0||Z!==0)J.setPosition(J.x+b*M,J.y+Z*M)})}})}import{Graphics as JJ}from"pixi.js";import{definePlugin as NJ}from"ecspresso";function _J(){return{selectable:!0}}function GJ(j){let{systemGroup:I="selection",priority:K=100,phase:B="preUpdate",clickThreshold:O=5,boxFillColor:W=65280,boxFillAlpha:E=0.15,boxStrokeColor:A=65280,boxStrokeAlpha:y=0.8,selectedTint:q=4521796,renderLayer:g}=j??{},f={color:W,alpha:E},k={color:A,width:1.5,alpha:y};return NJ("selection").withComponentTypes().withResourceTypes().withLabels().withGroups().requires().install((J)=>{J.addResource("selectionState",{dragStart:{x:0,y:0},boxEntityId:null});let V=null;J.addSystem("selection-input").setPriority(K).inPhase(B).inGroup(I).addQuery("selectables",{with:["selectable","worldTransform"]}).addQuery("currentlySelected",{with:["selected"]}).withResources(["inputState","selectionState","pixiApp"]).setOnInitialize((P)=>{let N=P.getResource("pixiApp");V=($)=>$.preventDefault(),N.canvas.addEventListener("contextmenu",V)}).setOnDetach((P)=>{if(!V)return;P.getResource("pixiApp").canvas.removeEventListener("contextmenu",V),V=null}).setProcess(({queries:P,ecs:N,resources:$})=>{let{inputState:z,selectionState:R}=$,Q=z.pointer;if(Q.justPressed(0)){if(R.boxEntityId!==null)N.commands.removeEntity(R.boxEntityId);R.dragStart.x=Q.position.x,R.dragStart.y=Q.position.y;let H=N.spawn({graphics:new JJ});if(g)N.addComponent(H.id,"renderLayer",g);R.boxEntityId=H.id}if(Q.isDown(0)&&R.boxEntityId!==null){let H=N.getComponent(R.boxEntityId,"graphics");if(!H)return;let L=R.dragStart.x,v=R.dragStart.y,X=Q.position.x,Y=Q.position.y,h=Math.min(L,X),T=Math.min(v,Y),x=Math.abs(X-L),S=Math.abs(Y-v);H.clear(),H.rect(h,T,x,S),H.fill(f),H.stroke(k)}if(!Q.justReleased(0)||R.boxEntityId===null)return;let G=R.dragStart.x,_=R.dragStart.y,U=Q.position.x,M=Q.position.y,b=Math.abs(U-G),Z=Math.abs(M-_);for(let H of P.currentlySelected)N.removeComponent(H.id,"selected");let D=b<O&&Z<O,F=N.tryGetResource("cameraState"),C=F?p(U,M,F):{x:U,y:M};if(D){let L=null,v=1/0;for(let X of P.selectables){let{worldTransform:Y}=X.components,h=Y.x-C.x,T=Y.y-C.y,x=h*h+T*T;if(x<400&&x<v)v=x,L=X.id}if(L!==null)N.addComponent(L,"selected",!0)}else{let H=F?p(G,_,F):{x:G,y:_},L=Math.min(H.x,C.x),v=Math.max(H.x,C.x),X=Math.min(H.y,C.y),Y=Math.max(H.y,C.y);for(let h of P.selectables){let{worldTransform:T}=h.components;if(T.x>=L&&T.x<=v&&T.y>=X&&T.y<=Y)N.addComponent(h.id,"selected",!0)}}N.commands.removeEntity(R.boxEntityId),R.boxEntityId=null}),J.addSystem("selection-visual").setPriority(K).inPhase("render").inGroup(I).addQuery("selectedUnits",{with:["selected","sprite"]}).setOnEntityEnter("selectedUnits",({entity:P})=>{P.components.sprite.tint=q}).addQuery("deselectedUnits",{with:["selectable","sprite"],without:["selected"]}).setOnEntityEnter("deselectedUnits",({entity:P})=>{P.components.sprite.tint=16777215})})}export{GJ as createSelectionPlugin,_J as createSelectable};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=E108E241311BC76E64756E2164756E21
|
|
4
4
|
//# sourceMappingURL=selection.js.map
|