ecspresso 0.14.5 → 0.14.7
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.js +2 -2
- package/dist/plugins/ai/behavior-tree.js.map +2 -2
- 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/ai/pathfinding.js +2 -2
- package/dist/plugins/ai/pathfinding.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 +3 -3
- 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.js +2 -2
- package/dist/plugins/physics/collision3D.js.map +2 -2
- package/dist/plugins/physics/physics2D.js +2 -2
- package/dist/plugins/physics/physics2D.js.map +2 -2
- package/dist/plugins/physics/physics3D.js +2 -2
- package/dist/plugins/physics/physics3D.js.map +2 -2
- 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 +2 -2
- package/dist/plugins/rendering/renderer3D.js +2 -4105
- package/dist/plugins/rendering/renderer3D.js.map +3 -5
- package/dist/plugins/rendering/sprite-animation.js +2 -2
- package/dist/plugins/rendering/sprite-animation.js.map +2 -2
- package/dist/plugins/rendering/tilemap.js +2 -2
- package/dist/plugins/rendering/tilemap.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 +3 -3
- package/dist/plugins/spatial/camera3D.js +2 -2
- package/dist/plugins/spatial/camera3D.js.map +3 -3
- package/dist/plugins/spatial/spatial-index.js +2 -2
- package/dist/plugins/spatial/spatial-index.js.map +2 -2
- package/dist/plugins/spatial/spatial-index3D.js +2 -2
- package/dist/plugins/spatial/spatial-index3D.js.map +2 -2
- package/dist/plugins/spatial/transform.js +2 -2
- package/dist/plugins/spatial/transform.js.map +2 -2
- package/dist/plugins/spatial/transform3D.js +2 -2
- package/dist/plugins/spatial/transform3D.js.map +2 -2
- package/dist/plugins/ui/ui.d.ts +40 -8
- package/dist/plugins/ui/ui.js +2 -2
- package/dist/plugins/ui/ui.js.map +3 -3
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var K=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(k,A)=>(typeof require<"u"?require:k)[A]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as J}from"ecspresso";function N(j){return{health:{current:j,max:j}}}function O(j,k){return{health:{current:j,max:k}}}function Q(j){let{systemGroup:k="combat"}=j??{};return J("health").withComponentTypes().withEventTypes().withLabels().withGroups().install((A)=>{A.addSystem("health-damage").inGroup(k).setEventHandlers({damage({data:q,ecs:F}){let z=F.getComponent(q.entityId,"health");if(!z)return;if(z.current<=0)return;if(z.current=Math.max(0,z.current-q.amount),F.markChanged(q.entityId,"health"),z.current<=0)F.eventBus.publish("entityDied",{entityId:q.entityId,killerId:q.sourceId})}})})}export{O as createHealthWith,Q as createHealthPlugin,N as createHealth};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=F64127187EB3461D64756E2164756E21
|
|
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": "2PAUA,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": "F64127187EB3461D64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Z=((v)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(v,{get:(E,H)=>(typeof require<"u"?require:E)[H]}):v)(function(v){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+v+'" is not supported')});import{definePlugin as Y}from"ecspresso";function b(v,E,H){return{projectile:{damage:v,speed:E,sourceId:H}}}function B(v){return{projectileTarget:{entityId:v}}}function S(v,E){let H=Math.sqrt(v*v+E*E);if(H===0)return{projectileDirection:{x:0,y:-1}};return{projectileDirection:{x:v/H,y:E/H}}}function G(v){let{systemGroup:E="combat",priority:H=300,phase:U="update",publishDamage:X=!0}=v??{};return Y("projectile").withComponentTypes().withEventTypes().withLabels().withGroups().requires().install((R)=>{R.addSystem("projectile-homing").setPriority(H).inPhase(U).inGroup(E).addQuery("homing",{with:["projectile","projectileTarget","localTransform"]}).setProcess(({queries:C,ecs:k,dt:M})=>{for(let K of C.homing){let{projectile:L,projectileTarget:F,localTransform:z}=K.components;if(!k.getEntity(F.entityId)){k.commands.removeEntity(K.id);continue}let J=k.getComponent(F.entityId,"worldTransform");if(!J){k.commands.removeEntity(K.id);continue}let N=J.x-z.x,O=J.y-z.y,V=N*N+O*O,Q=L.speed*M;if(V<=Q*Q)z.x=J.x,z.y=J.y;else{let W=Math.sqrt(V);z.x+=N/W*Q,z.y+=O/W*Q,z.rotation=Math.atan2(O,N)}k.markChanged(K.id,"localTransform")}}),R.addSystem("projectile-linear").setPriority(H).inPhase(U).inGroup(E).addQuery("linear",{with:["projectile","projectileDirection","localTransform"]}).setProcess(({queries:C,dt:k})=>{for(let M of C.linear){let{projectile:K,projectileDirection:L,localTransform:F}=M.components,z=K.speed*k;F.x+=L.x*z,F.y+=L.y*z}}),R.addSystem("projectile-collision").inGroup(E).setEventHandlers({collision({data:C,ecs:k}){if(!k.getEntity(C.entityA)||!k.getEntity(C.entityB))return;let M=k.getComponent(C.entityA,"projectile"),K=k.getComponent(C.entityB,"projectile"),L=M!==void 0,F=L?M:K;if(!F)return;let z=L?C.entityA:C.entityB,J=L?C.entityB:C.entityA;if(J===F.sourceId)return;if(k.eventBus.publish("projectileHit",{projectileId:z,targetId:J,damage:F.damage}),X)k.eventBus.publish("damage",{entityId:J,amount:F.damage,sourceId:F.sourceId});k.commands.removeEntity(z)}})})}export{B as createProjectileTarget,G as createProjectilePlugin,S as createProjectileDirection,b as createProjectile};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=D0756C0A27AA8CAC64756E2164756E21
|
|
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": "D0756C0A27AA8CAC64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)},
|
|
1
|
+
var L=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(F,k)=>(typeof require<"u"?require:F)[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 $}from"ecspresso";function A(j){let F=new Float64Array(j),k=0,q=0;return{push(Q){if(F[k]=Q,k=(k+1)%j,q<j)q++},computeFps(){if(q<2)return 0;let Q=F[(k-1+j)%j]??0,U=F[(k-q+j)%j]??0,z=Q-U;if(z<=0)return 0;return(q-1)/z*1000},computeAverageFrameTime(){if(q<2)return 0;let Q=F[(k-1+j)%j]??0,U=F[(k-q+j)%j]??0,z=Q-U;if(z<=0)return 0;return z/(q-1)},get size(){return q}}}function Y(j){let{systemGroup:F="diagnostics",enableTimingOnInit:k=!0,fpsSampleCount:q=60}=j??{},Q={fps:0,entityCount:0,systemTimings:new Map,phaseTimings:{preUpdate:0,fixedUpdate:0,update:0,postUpdate:0,render:0},averageFrameTime:0},U=A(q);return $("diagnostics").withResourceTypes().withLabels().withGroups().install((z)=>{z.addResource("diagnostics",Q),z.addSystem("diagnostics-collect").setPriority(-999999).inPhase("render").inGroup(F).setOnInitialize((J)=>{if(k)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 G={"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 _(j,F){let{position:k="top-left",updateInterval:q=200,showSystemTimings:Q=!0,maxSystemsShown:U=10}=F??{},z=document.createElement("div");z.style.cssText=`position:fixed;${G[k]};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(z);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`)}z.textContent=K.join(`
|
|
2
|
+
`)},q);return()=>{clearInterval(J),z.remove()}}export{Y as createDiagnosticsPlugin,_ as createDiagnosticsOverlay};
|
|
3
3
|
|
|
4
|
-
//# debugId=
|
|
4
|
+
//# debugId=D7FE41862EA92B5B64756E2164756E21
|
|
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": "D7FE41862EA92B5B64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Jj=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(q,W)=>(typeof require<"u"?require:q)[W]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as i}from"ecspresso";function Wj(j){return j}function Yj(j,...q){return q.map((W)=>({pad:j,button:W}))}function $j(j,q,W,G){return G===void 0?{pad:j,axis:q,direction:W}:{pad:j,axis:q,direction:W,threshold:G}}var s=0.5,o=0.15,H=4;function n(){return{keysDown:new Set,keysPressed:[],keysReleased:[],pointerButtonsDown:new Set,pointerButtonsPressed:[],pointerButtonsReleased:[],pointerX:0,pointerY:0,lastPointerX:0,lastPointerY:0,pointerMoved:!1}}function d(){return{keysDown:new Set,keysPressed:new Set,keysReleased:new Set,pointerButtonsDown:new Set,pointerButtonsPressed:new Set,pointerButtonsReleased:new Set,pointerX:0,pointerY:0,pointerDeltaX:0,pointerDeltaY:0}}function r(){return{connected:!1,id:null,buttonsDown:new Set,buttonsPrev:new Set,buttonsPressed:new Set,buttonsReleased:new Set,buttonValues:[],axes:[],rawAxes:[]}}function h(){return{active:new Set,prev:new Set}}function B(j,q){j.clear();for(let W of q)j.add(W)}function t(j,q){B(j.keysDown,q.keysDown),B(j.keysPressed,q.keysPressed),B(j.keysReleased,q.keysReleased),B(j.pointerButtonsDown,q.pointerButtonsDown),B(j.pointerButtonsPressed,q.pointerButtonsPressed),B(j.pointerButtonsReleased,q.pointerButtonsReleased),j.pointerDeltaX=q.pointerMoved?q.pointerX-q.lastPointerX:0,j.pointerDeltaY=q.pointerMoved?q.pointerY-q.lastPointerY:0,j.pointerX=q.pointerX,j.pointerY=q.pointerY,q.keysPressed.length=0,q.keysReleased.length=0,q.pointerButtonsPressed.length=0,q.pointerButtonsReleased.length=0,q.lastPointerX=q.pointerX,q.lastPointerY=q.pointerY,q.pointerMoved=!1}function a(j){return()=>{if(typeof navigator>"u"||typeof navigator.getGamepads!=="function"){for(let W=0;W<j.length;W++)j[W]=null;return j}let q=navigator.getGamepads();for(let W=0;W<j.length;W++)j[W]=q[W]??null;return j}}function D(j,q,W,G,Z){let $=Math.sqrt(j*j+q*q);if($<W){G[Z]=0,G[Z+1]=0;return}let V=Math.min(($-W)/(1-W),1);G[Z]=j/$*V,G[Z+1]=q/$*V}function e(j,q,W){if(q.length=j.length,j.length>=2)D(j[0]??0,j[1]??0,W,q,0);if(j.length>=4)D(j[2]??0,j[3]??0,W,q,2);for(let G=4;G<j.length;G++)q[G]=j[G]??0}function jj(j,q,W){let G=q();for(let Z=0;Z<H;Z++){let $=G[Z]??null,V=j[Z];if(!V)continue;let R=V.buttonsPrev;if(B(R,V.buttonsDown),V.buttonsDown.clear(),V.buttonsPressed.clear(),V.buttonsReleased.clear(),!$||!$.connected){if(V.connected){for(let Y of R)V.buttonsReleased.add(Y);V.connected=!1,V.id=null,V.buttonValues.length=0,V.axes.length=0,V.rawAxes.length=0}continue}V.connected=!0,V.id=$.id,V.buttonValues.length=$.buttons.length;for(let Y=0;Y<$.buttons.length;Y++){let E=$.buttons[Y];if(!E){V.buttonValues[Y]=0;continue}if(V.buttonValues[Y]=E.value,E.pressed)V.buttonsDown.add(Y)}for(let Y of V.buttonsDown)if(!R.has(Y))V.buttonsPressed.add(Y);for(let Y of R)if(!V.buttonsDown.has(Y))V.buttonsReleased.add(Y);V.rawAxes.length=$.axes.length;for(let Y=0;Y<$.axes.length;Y++)V.rawAxes[Y]=$.axes[Y]??0;e(V.rawAxes,V.axes,W)}}function qj(j,q,W,G){if(j.keys?.some((Z)=>q.has(Z)))return!0;if(j.pointerButtons?.some((Z)=>W.has(Z)))return!0;if(j.gamepadButtons?.some(({pad:Z,button:$})=>G[Z]?.buttonsDown.has($)??!1))return!0;if(j.gamepadAxes?.some(({pad:Z,axis:$,direction:V,threshold:R=s})=>{let Y=G[Z]?.axes[$]??0;return V>0?Y>R:Y<-R}))return!0;return!1}function k(j,q,W,G,Z){let $=j.prev;j.prev=j.active,j.active=$,$.clear();for(let[V,R]of Object.entries(q))if(qj(R,W,G,Z))$.add(V)}function g(j){return{isActive:(q)=>j.active.has(q),justActivated:(q)=>j.active.has(q)&&!j.prev.has(q),justDeactivated:(q)=>!j.active.has(q)&&j.prev.has(q)}}function Xj(j){let{systemGroup:q="input",priority:W=100,phase:G="preUpdate",target:Z=globalThis,gamepad:$={},coordinateTransform:V}=j??{},R={...j?.actions??{}},Y=new Map(Object.entries(j?.players??{})),E=$.deadzone??o,S=$.poll??a(Array(H).fill(null)),C=n(),K=d(),L=Array.from({length:H},r),z=h(),O=new Map,N=new Map,T=[],I={x:0,y:0},F={x:0,y:0},U=R,A={isDown:(J)=>K.keysDown.has(J),justPressed:(J)=>K.keysPressed.has(J),justReleased:(J)=>K.keysReleased.has(J)},y={position:I,delta:F,isDown:(J)=>K.pointerButtonsDown.has(J),justPressed:(J)=>K.pointerButtonsPressed.has(J),justReleased:(J)=>K.pointerButtonsReleased.has(J)};function b(J){let Q=L[J];if(!Q)throw Error(`Invalid gamepad index: ${J}`);return{get connected(){return Q.connected},get id(){return Q.id},isDown:(X)=>Q.buttonsDown.has(X),justPressed:(X)=>Q.buttonsPressed.has(X),justReleased:(X)=>Q.buttonsReleased.has(X),buttonValue:(X)=>Q.buttonValues[X]??0,axis:(X)=>Q.axes[X]??0,rawAxis:(X)=>Q.rawAxes[X]??0}}let c=Array.from({length:H},(J,Q)=>b(Q)),f=g(z);function M(J){let Q=O.get(J);if(Q)return Q;let X=h();return O.set(J,X),X}function P(J){let Q=M(J);return{actions:g(Q),setActionMap:(X)=>{if(!Y.has(J))throw Error(`Player '${J}' was removed`);Y.set(J,{...X})},getActionMap:()=>{let X=Y.get(J);if(!X)throw Error(`Player '${J}' was removed`);return{...X}}}}for(let J of Y.keys())N.set(J,P(J));let x={keyboard:A,pointer:y,gamepads:c,actions:f,setActionMap(J){U={...J}},getActionMap(){return{...U}},definePlayer(J,Q){if(Y.set(J,{...Q}),!N.has(J))N.set(J,P(J))},removePlayer(J){let Q=Y.delete(J);return N.delete(J),O.delete(J),Q},player(J){return N.get(J)},playerIds(){return Array.from(Y.keys())}};function m(J){let Q=J;if(Q.repeat)return;C.keysDown.add(Q.key),C.keysPressed.push(Q.key)}function u(J){let Q=J;C.keysDown.delete(Q.key),C.keysReleased.push(Q.key)}function w(J){let Q=J;C.pointerButtonsDown.add(Q.button),C.pointerButtonsPressed.push(Q.button)}function l(J){let Q=J;if(V){let{x:X,y:v}=V(Q.clientX,Q.clientY);C.pointerX=X,C.pointerY=v}else C.pointerX=Q.clientX,C.pointerY=Q.clientY;C.pointerMoved=!0}function p(J){let Q=J;C.pointerButtonsDown.delete(Q.button),C.pointerButtonsReleased.push(Q.button)}function _(J,Q){Z.addEventListener(J,Q),T.push(()=>{Z.removeEventListener(J,Q)})}return i("input").withResourceTypes().withLabels().withGroups().install((J)=>{J.addResource("inputState",x),J.addSystem("input-state").setPriority(W).inPhase(G).inGroup(q).setOnInitialize(()=>{_("keydown",m),_("keyup",u),_("pointerdown",w),_("pointermove",l),_("pointerup",p)}).setOnDetach(()=>{for(let Q of T)Q();T.length=0}).setProcess(()=>{jj(L,S,E),t(K,C),I.x=K.pointerX,I.y=K.pointerY,F.x=K.pointerDeltaX,F.y=K.pointerDeltaY,k(z,U,K.keysDown,K.pointerButtonsDown,L);for(let[Q,X]of Y){let v=M(Q);k(v,X,K.keysDown,K.pointerButtonsDown,L)}})})}export{Yj as gamepadButtonsOn,$j as gamepadAxisOn,Xj as createInputPlugin,Wj as createActionBinding};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=A6B7FF490EDEEA4864756E2164756E21
|
|
4
4
|
//# sourceMappingURL=input.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * Input Plugin for ECSpresso\n *\n * Resource-only plugin — input is polled via the `inputState` resource. Provides\n * frame-accurate keyboard, pointer (mouse + touch via PointerEvent), up to 4\n * gamepads, and unified + per-player action maps.\n *\n * Mutation model: DOM events accumulate into `raw` between frames and are\n * flattened once per frame into a stable `frame` object whose Sets are cleared\n * and refilled in place (no per-frame allocations). Gamepads are polled once\n * per frame via `navigator.getGamepads()` (or an injected poll function).\n * Unified and per-player action states ping-pong two Sets (`active` / `prev`)\n * so edge detection costs nothing beyond one `.add()` per active action.\n */\n\nimport { definePlugin, type BasePluginOptions, type Vector2D } from 'ecspresso';\n\n// ==================== Public Types ====================\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<Vector2D>;\n\treadonly delta: Readonly<Vector2D>;\n\tisDown(button: number): boolean;\n\tjustPressed(button: number): boolean;\n\tjustReleased(button: number): boolean;\n}\n\nexport interface GamepadState {\n\treadonly connected: boolean;\n\treadonly id: string | null;\n\tisDown(button: number): boolean;\n\tjustPressed(button: number): boolean;\n\tjustReleased(button: number): boolean;\n\t/** Analog button value in [0, 1]. Useful for triggers. Returns 0 when disconnected or out of range. */\n\tbuttonValue(button: number): number;\n\t/** Deadzone-applied axis value in [-1, 1]. Sticks use radial deadzone on axis pairs (0,1) and (2,3). */\n\taxis(index: number): number;\n\t/** Raw axis value in [-1, 1] with no deadzone applied. */\n\trawAxis(index: number): number;\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 PlayerInput<A extends string = string> {\n\treadonly actions: ActionState<A>;\n\tsetActionMap(map: ActionMap<A>): void;\n\tgetActionMap(): Readonly<ActionMap<A>>;\n}\n\nexport interface InputState<A extends string = string> {\n\treadonly keyboard: KeyboardState;\n\treadonly pointer: PointerState;\n\t/** Always length 4 (standard web gamepad slot count). Disconnected slots return `connected: false`. */\n\treadonly gamepads: ReadonlyArray<GamepadState>;\n\t/** Unified action state — fires when any bound input (keyboard, pointer, any pad) is active. Intended for menu/shared input. */\n\treadonly actions: ActionState<A>;\n\tsetActionMap(actions: ActionMap<A>): void;\n\tgetActionMap(): Readonly<ActionMap<A>>;\n\t/** Register or replace a player's action map. Per-player states are isolated from the unified `actions`. */\n\tdefinePlayer(id: string, map: ActionMap<A>): void;\n\t/** Returns true if the player existed and was removed. */\n\tremovePlayer(id: string): boolean;\n\t/** Returns a handle to a registered player's input, or undefined if no such player. */\n\tplayer(id: string): PlayerInput<A> | undefined;\n\tplayerIds(): readonly string[];\n}\n\nexport interface GamepadButtonRef {\n\tpad: number;\n\tbutton: number;\n}\n\nexport interface GamepadAxisRef {\n\tpad: number;\n\taxis: number;\n\t/** Which half of the axis counts as \"active\". */\n\tdirection: 1 | -1;\n\t/** Magnitude at which the axis triggers the action. Applied to the deadzone-adjusted axis value. Default: 0.5. */\n\tthreshold?: number;\n}\n\nexport interface ActionBinding {\n\tkeys?: KeyCode[];\n\t/** Pointer (mouse/touch) button indices — 0 = primary, 1 = auxiliary, 2 = secondary, etc. */\n\tpointerButtons?: number[];\n\tgamepadButtons?: GamepadButtonRef[];\n\tgamepadAxes?: GamepadAxisRef[];\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\n/**\n * Minimal gamepad shape required by the injectable poll function. A structural\n * subset of the browser `Gamepad` interface — `navigator.getGamepads()` satisfies\n * it directly, and test doubles can supply just these fields.\n */\nexport interface GamepadLike {\n\tid: string;\n\tconnected: boolean;\n\tbuttons: ReadonlyArray<{ pressed: boolean; value: number }>;\n\taxes: ReadonlyArray<number>;\n}\n\nexport interface GamepadOptions {\n\t/** Radial deadzone applied to stick pairs (axes 0,1 and 2,3). Value in [0, 1]. Default: 0.15. */\n\tdeadzone?: number;\n\t/**\n\t * Custom poll function returning up to 4 gamepad slots. Defaults to `navigator.getGamepads()`.\n\t * Primarily an injection point for tests; in the browser the default is correct.\n\t */\n\tpoll?: () => ReadonlyArray<GamepadLike | null>;\n}\n\nexport interface InputPluginOptions<A extends string = string, G extends string = 'input'> extends BasePluginOptions<G> {\n\t/** Initial unified action map. */\n\tactions?: ActionMap<A>;\n\t/** Initial per-player action maps, keyed by player id. */\n\tplayers?: Record<string, ActionMap<A>>;\n\t/** EventTarget to attach listeners to (default: globalThis). Pass a custom target for testability. */\n\ttarget?: EventTarget;\n\t/** Gamepad polling and deadzone configuration. */\n\tgamepad?: GamepadOptions;\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/** Create a single action binding. Identity function that provides type inference for inline literals. */\nexport function createActionBinding(binding: ActionBinding): ActionBinding {\n\treturn binding;\n}\n\n/** Build an array of gamepad button refs scoped to one pad — `gamepadButtonsOn(0, 0, 1, 9)` = pad 0's buttons 0, 1, 9. */\nexport function gamepadButtonsOn(pad: number, ...buttons: number[]): GamepadButtonRef[] {\n\treturn buttons.map((button) => ({ pad, button }));\n}\n\n/** Build a gamepad axis ref. `threshold` defaults to 0.5 at activation time. */\nexport function gamepadAxisOn(pad: number, axis: number, direction: 1 | -1, threshold?: number): GamepadAxisRef {\n\treturn threshold === undefined ? { pad, axis, direction } : { pad, axis, direction, threshold };\n}\n\n// ==================== Internal Types ====================\n\ninterface RawKeyPointerState {\n\tkeysDown: Set<string>;\n\tkeysPressed: string[];\n\tkeysReleased: string[];\n\tpointerButtonsDown: Set<number>;\n\tpointerButtonsPressed: number[];\n\tpointerButtonsReleased: number[];\n\tpointerX: number;\n\tpointerY: number;\n\tlastPointerX: number;\n\tlastPointerY: number;\n\tpointerMoved: boolean;\n}\n\n/**\n * Stable per-frame view of keyboard + pointer input. Sets are mutated in place\n * each frame (cleared and refilled from raw), so closures over this object see\n * consistent state throughout a frame without per-frame Set allocation.\n */\ninterface FrameState {\n\tkeysDown: Set<string>;\n\tkeysPressed: Set<string>;\n\tkeysReleased: Set<string>;\n\tpointerButtonsDown: Set<number>;\n\tpointerButtonsPressed: Set<number>;\n\tpointerButtonsReleased: Set<number>;\n\tpointerX: number;\n\tpointerY: number;\n\tpointerDeltaX: number;\n\tpointerDeltaY: number;\n}\n\ninterface PadRuntime {\n\tconnected: boolean;\n\tid: string | null;\n\tbuttonsDown: Set<number>;\n\tbuttonsPrev: Set<number>;\n\tbuttonsPressed: Set<number>;\n\tbuttonsReleased: Set<number>;\n\tbuttonValues: number[];\n\taxes: number[];\n\trawAxes: number[];\n}\n\n/** Two ping-ponged Sets backing an ActionState. Each frame we swap `active` ↔ `prev`, clear the new `active`, and refill. */\ninterface ActionSlot {\n\tactive: Set<string>;\n\tprev: Set<string>;\n}\n\n// ==================== Helpers ====================\n\nconst DEFAULT_AXIS_THRESHOLD = 0.5;\nconst DEFAULT_DEADZONE = 0.15;\nconst PAD_COUNT = 4;\n\nfunction createRawKeyPointerState(): RawKeyPointerState {\n\treturn {\n\t\tkeysDown: new Set(),\n\t\tkeysPressed: [],\n\t\tkeysReleased: [],\n\t\tpointerButtonsDown: new Set(),\n\t\tpointerButtonsPressed: [],\n\t\tpointerButtonsReleased: [],\n\t\tpointerX: 0,\n\t\tpointerY: 0,\n\t\tlastPointerX: 0,\n\t\tlastPointerY: 0,\n\t\tpointerMoved: false,\n\t};\n}\n\nfunction createFrameState(): FrameState {\n\treturn {\n\t\tkeysDown: new Set(),\n\t\tkeysPressed: new Set(),\n\t\tkeysReleased: new Set(),\n\t\tpointerButtonsDown: new Set(),\n\t\tpointerButtonsPressed: new Set(),\n\t\tpointerButtonsReleased: new Set(),\n\t\tpointerX: 0,\n\t\tpointerY: 0,\n\t\tpointerDeltaX: 0,\n\t\tpointerDeltaY: 0,\n\t};\n}\n\nfunction createPadRuntime(): PadRuntime {\n\treturn {\n\t\tconnected: false,\n\t\tid: null,\n\t\tbuttonsDown: new Set(),\n\t\tbuttonsPrev: new Set(),\n\t\tbuttonsPressed: new Set(),\n\t\tbuttonsReleased: new Set(),\n\t\tbuttonValues: [],\n\t\taxes: [],\n\t\trawAxes: [],\n\t};\n}\n\nfunction createActionSlot(): ActionSlot {\n\treturn { active: new Set(), prev: new Set() };\n}\n\nfunction refillSet<T>(dest: Set<T>, source: Iterable<T>): void {\n\tdest.clear();\n\tfor (const item of source) dest.add(item);\n}\n\nfunction updateFrameStateFromRaw(frame: FrameState, raw: RawKeyPointerState): void {\n\trefillSet(frame.keysDown, raw.keysDown);\n\trefillSet(frame.keysPressed, raw.keysPressed);\n\trefillSet(frame.keysReleased, raw.keysReleased);\n\trefillSet(frame.pointerButtonsDown, raw.pointerButtonsDown);\n\trefillSet(frame.pointerButtonsPressed, raw.pointerButtonsPressed);\n\trefillSet(frame.pointerButtonsReleased, raw.pointerButtonsReleased);\n\n\tframe.pointerDeltaX = raw.pointerMoved ? raw.pointerX - raw.lastPointerX : 0;\n\tframe.pointerDeltaY = raw.pointerMoved ? raw.pointerY - raw.lastPointerY : 0;\n\tframe.pointerX = raw.pointerX;\n\tframe.pointerY = raw.pointerY;\n\n\traw.keysPressed.length = 0;\n\traw.keysReleased.length = 0;\n\traw.pointerButtonsPressed.length = 0;\n\traw.pointerButtonsReleased.length = 0;\n\traw.lastPointerX = raw.pointerX;\n\traw.lastPointerY = raw.pointerY;\n\traw.pointerMoved = false;\n}\n\nfunction defaultPoll(out: Array<GamepadLike | null>): () => ReadonlyArray<GamepadLike | null> {\n\treturn () => {\n\t\tif (typeof navigator === 'undefined' || typeof navigator.getGamepads !== 'function') {\n\t\t\tfor (let i = 0; i < out.length; i++) out[i] = null;\n\t\t\treturn out;\n\t\t}\n\t\tconst pads = navigator.getGamepads();\n\t\tfor (let i = 0; i < out.length; i++) out[i] = pads[i] ?? null;\n\t\treturn out;\n\t};\n}\n\nfunction applyStickDeadzone(x: number, y: number, deadzone: number, out: number[], baseIndex: number): void {\n\tconst mag = Math.sqrt(x * x + y * y);\n\tif (mag < deadzone) {\n\t\tout[baseIndex] = 0;\n\t\tout[baseIndex + 1] = 0;\n\t\treturn;\n\t}\n\tconst scaled = Math.min((mag - deadzone) / (1 - deadzone), 1);\n\tout[baseIndex] = (x / mag) * scaled;\n\tout[baseIndex + 1] = (y / mag) * scaled;\n}\n\nfunction applyAxisDeadzoning(rawAxes: number[], axes: number[], deadzone: number): void {\n\taxes.length = rawAxes.length;\n\tif (rawAxes.length >= 2) {\n\t\tapplyStickDeadzone(rawAxes[0] ?? 0, rawAxes[1] ?? 0, deadzone, axes, 0);\n\t}\n\tif (rawAxes.length >= 4) {\n\t\tapplyStickDeadzone(rawAxes[2] ?? 0, rawAxes[3] ?? 0, deadzone, axes, 2);\n\t}\n\t// Axes beyond the two standard sticks pass through with no deadzone (triggers, dpad-as-axis, etc.)\n\tfor (let i = 4; i < rawAxes.length; i++) {\n\t\taxes[i] = rawAxes[i] ?? 0;\n\t}\n}\n\nfunction pollGamepadsInto(pads: PadRuntime[], pollFn: () => ReadonlyArray<GamepadLike | null>, deadzone: number): void {\n\tconst polled = pollFn();\n\tfor (let i = 0; i < PAD_COUNT; i++) {\n\t\tconst pad = polled[i] ?? null;\n\t\tconst state = pads[i];\n\t\tif (!state) continue;\n\n\t\t// Rotate button sets using the existing `prev` set as scratch, then clear what we'll refill.\n\t\tconst reusedPrev = state.buttonsPrev;\n\t\trefillSet(reusedPrev, state.buttonsDown);\n\t\tstate.buttonsDown.clear();\n\t\tstate.buttonsPressed.clear();\n\t\tstate.buttonsReleased.clear();\n\n\t\tif (!pad || !pad.connected) {\n\t\t\tif (state.connected) {\n\t\t\t\t// Newly disconnected: synthesize justReleased for anything that was held, then clear values.\n\t\t\t\tfor (const b of reusedPrev) state.buttonsReleased.add(b);\n\t\t\t\tstate.connected = false;\n\t\t\t\tstate.id = null;\n\t\t\t\tstate.buttonValues.length = 0;\n\t\t\t\tstate.axes.length = 0;\n\t\t\t\tstate.rawAxes.length = 0;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tstate.connected = true;\n\t\tstate.id = pad.id;\n\n\t\tstate.buttonValues.length = pad.buttons.length;\n\t\tfor (let b = 0; b < pad.buttons.length; b++) {\n\t\t\tconst info = pad.buttons[b];\n\t\t\tif (!info) {\n\t\t\t\tstate.buttonValues[b] = 0;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tstate.buttonValues[b] = info.value;\n\t\t\tif (info.pressed) state.buttonsDown.add(b);\n\t\t}\n\n\t\tfor (const b of state.buttonsDown) {\n\t\t\tif (!reusedPrev.has(b)) state.buttonsPressed.add(b);\n\t\t}\n\t\tfor (const b of reusedPrev) {\n\t\t\tif (!state.buttonsDown.has(b)) state.buttonsReleased.add(b);\n\t\t}\n\n\t\tstate.rawAxes.length = pad.axes.length;\n\t\tfor (let a = 0; a < pad.axes.length; a++) {\n\t\t\tstate.rawAxes[a] = pad.axes[a] ?? 0;\n\t\t}\n\t\tapplyAxisDeadzoning(state.rawAxes, state.axes, deadzone);\n\t}\n}\n\nfunction isBindingActive(\n\tbinding: ActionBinding,\n\tkeysDown: ReadonlySet<string>,\n\tpointerButtonsDown: ReadonlySet<number>,\n\tpads: ReadonlyArray<PadRuntime>,\n): boolean {\n\tif (binding.keys?.some((k) => keysDown.has(k))) return true;\n\tif (binding.pointerButtons?.some((b) => pointerButtonsDown.has(b))) return true;\n\tif (binding.gamepadButtons?.some(({ pad, button }) => pads[pad]?.buttonsDown.has(button) ?? false)) return true;\n\tif (binding.gamepadAxes?.some(({ pad, axis, direction, threshold = DEFAULT_AXIS_THRESHOLD }) => {\n\t\tconst value = pads[pad]?.axes[axis] ?? 0;\n\t\treturn direction > 0 ? value > threshold : value < -threshold;\n\t})) return true;\n\treturn false;\n}\n\n/**\n * Recompute the slot's `active` set in place from `map` against current input sources.\n * Rotates `active` ↔ `prev` (reusing Set instances) so edge detection works with no allocations.\n */\nfunction advanceActionSlot(\n\tslot: ActionSlot,\n\tmap: ActionMap,\n\tkeysDown: ReadonlySet<string>,\n\tpointerButtonsDown: ReadonlySet<number>,\n\tpads: ReadonlyArray<PadRuntime>,\n): void {\n\tconst nextActive = slot.prev;\n\tslot.prev = slot.active;\n\tslot.active = nextActive;\n\tnextActive.clear();\n\n\tfor (const [name, binding] of Object.entries(map)) {\n\t\tif (isBindingActive(binding, keysDown, pointerButtonsDown, pads)) nextActive.add(name);\n\t}\n}\n\nfunction makeActionState<A extends string>(slot: ActionSlot): ActionState<A> {\n\treturn {\n\t\tisActive: (action) => slot.active.has(action),\n\t\tjustActivated: (action) => slot.active.has(action) && !slot.prev.has(action),\n\t\tjustDeactivated: (action) => !slot.active.has(action) && slot.prev.has(action),\n\t};\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create an input plugin for ECSpresso.\n *\n * Provides:\n * - Frame-accurate keyboard state (isDown, justPressed, justReleased)\n * - Pointer position/delta and button state (mouse + touch via PointerEvent)\n * - Up to 4 gamepads polled per frame, with radial deadzone on sticks and analog button values\n * - Unified action mapping (keyboard + pointer + any pad)\n * - Per-player action maps for local co-op (`definePlayer`, `player(id)`)\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'], gamepadButtons: [{ pad: 0, button: 0 }] },\n * shoot: { keys: ['z'], pointerButtons: [0] },\n * },\n * players: {\n * p1: { jump: { keys: [' '] }, shoot: { keys: ['z'] } },\n * p2: {\n * jump: { gamepadButtons: gamepadButtonsOn(0, 0) },\n * shoot: { gamepadButtons: gamepadButtonsOn(0, 2) },\n * },\n * },\n * }))\n * .build();\n *\n * const input = ecs.getResource('inputState');\n * if (input.actions.justActivated('jump')) { ... } // any source\n * if (input.player('p1')?.actions.isActive('jump')) { ... } // just player 1\n * if (input.gamepads[0].isDown(0)) { ... } // raw pad 0 A-button\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\ttarget = globalThis,\n\t\tgamepad: gamepadOpts = {},\n\t\tcoordinateTransform,\n\t} = options ?? {};\n\n\t// Construction-time casts: option defaults of `{}` don't structurally satisfy a narrow `ActionMap<A>`,\n\t// but at this boundary we know the user either supplied a valid map or is using A = string.\n\tconst unifiedActionMap = { ...(options?.actions ?? {}) } as ActionMap<A>;\n\tconst playerMaps = new Map<string, ActionMap<A>>(\n\t\tObject.entries(options?.players ?? {}) as Array<[string, ActionMap<A>]>,\n\t);\n\n\tconst deadzone = gamepadOpts.deadzone ?? DEFAULT_DEADZONE;\n\tconst pollFn = gamepadOpts.poll ?? defaultPoll(new Array<GamepadLike | null>(PAD_COUNT).fill(null));\n\n\tconst raw = createRawKeyPointerState();\n\tconst frame = createFrameState();\n\tconst pads: PadRuntime[] = Array.from({ length: PAD_COUNT }, createPadRuntime);\n\tconst unifiedSlot = createActionSlot();\n\tconst playerSlots = new Map<string, ActionSlot>();\n\tconst playerHandles = new Map<string, PlayerInput<A>>();\n\tconst cleanupFns: Array<() => void> = [];\n\n\t// Vector2Ds exposed via the resource — updated in place each frame.\n\tconst position: Vector2D = { x: 0, y: 0 };\n\tconst delta: Vector2D = { x: 0, y: 0 };\n\n\tlet currentUnifiedMap = unifiedActionMap;\n\n\tconst keyboard: KeyboardState = {\n\t\tisDown: (key) => frame.keysDown.has(key),\n\t\tjustPressed: (key) => frame.keysPressed.has(key),\n\t\tjustReleased: (key) => frame.keysReleased.has(key),\n\t};\n\n\tconst pointer: PointerState = {\n\t\tposition,\n\t\tdelta,\n\t\tisDown: (button) => frame.pointerButtonsDown.has(button),\n\t\tjustPressed: (button) => frame.pointerButtonsPressed.has(button),\n\t\tjustReleased: (button) => frame.pointerButtonsReleased.has(button),\n\t};\n\n\tfunction makeGamepadState(index: number): GamepadState {\n\t\tconst state = pads[index];\n\t\tif (!state) throw new Error(`Invalid gamepad index: ${index}`);\n\t\treturn {\n\t\t\tget connected() { return state.connected; },\n\t\t\tget id() { return state.id; },\n\t\t\tisDown: (button) => state.buttonsDown.has(button),\n\t\t\tjustPressed: (button) => state.buttonsPressed.has(button),\n\t\t\tjustReleased: (button) => state.buttonsReleased.has(button),\n\t\t\tbuttonValue: (button) => state.buttonValues[button] ?? 0,\n\t\t\taxis: (index) => state.axes[index] ?? 0,\n\t\t\trawAxis: (index) => state.rawAxes[index] ?? 0,\n\t\t};\n\t}\n\n\tconst gamepadStates: ReadonlyArray<GamepadState> = Array.from({ length: PAD_COUNT }, (_, i) => makeGamepadState(i));\n\n\tconst unifiedActions = makeActionState<A>(unifiedSlot);\n\n\tfunction ensurePlayerSlot(id: string): ActionSlot {\n\t\tconst existing = playerSlots.get(id);\n\t\tif (existing) return existing;\n\t\tconst slot = createActionSlot();\n\t\tplayerSlots.set(id, slot);\n\t\treturn slot;\n\t}\n\n\tfunction createPlayerHandle(id: string): PlayerInput<A> {\n\t\tconst slot = ensurePlayerSlot(id);\n\t\treturn {\n\t\t\tactions: makeActionState<A>(slot),\n\t\t\tsetActionMap: (map) => {\n\t\t\t\tif (!playerMaps.has(id)) throw new Error(`Player '${id}' was removed`);\n\t\t\t\tplayerMaps.set(id, { ...map });\n\t\t\t},\n\t\t\tgetActionMap: () => {\n\t\t\t\tconst map = playerMaps.get(id);\n\t\t\t\tif (!map) throw new Error(`Player '${id}' was removed`);\n\t\t\t\treturn { ...map };\n\t\t\t},\n\t\t};\n\t}\n\n\tfor (const id of playerMaps.keys()) {\n\t\tplayerHandles.set(id, createPlayerHandle(id));\n\t}\n\n\tconst inputState: InputState<A> = {\n\t\tkeyboard,\n\t\tpointer,\n\t\tgamepads: gamepadStates,\n\t\tactions: unifiedActions,\n\t\tsetActionMap(newMap) {\n\t\t\tcurrentUnifiedMap = { ...newMap };\n\t\t},\n\t\tgetActionMap() {\n\t\t\treturn { ...currentUnifiedMap };\n\t\t},\n\t\tdefinePlayer(id, map) {\n\t\t\tplayerMaps.set(id, { ...map });\n\t\t\tif (!playerHandles.has(id)) playerHandles.set(id, createPlayerHandle(id));\n\t\t},\n\t\tremovePlayer(id) {\n\t\t\tconst existed = playerMaps.delete(id);\n\t\t\tplayerHandles.delete(id);\n\t\t\tplayerSlots.delete(id);\n\t\t\treturn existed;\n\t\t},\n\t\tplayer(id) {\n\t\t\treturn playerHandles.get(id);\n\t\t},\n\t\tplayerIds() {\n\t\t\treturn Array.from(playerMaps.keys());\n\t\t},\n\t};\n\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.pointerButtonsDown.add(pe.button);\n\t\traw.pointerButtonsPressed.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.pointerButtonsDown.delete(pe.button);\n\t\traw.pointerButtonsReleased.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) cleanup();\n\t\t\t\t\tcleanupFns.length = 0;\n\t\t\t\t})\n\t\t\t\t.setProcess(() => {\n\t\t\t\t\t// Pads must be polled before action computation so a single frame reflects\n\t\t\t\t\t// both DOM-driven (keyboard/pointer) and polled (gamepad) sources consistently.\n\t\t\t\t\tpollGamepadsInto(pads, pollFn, deadzone);\n\t\t\t\t\tupdateFrameStateFromRaw(frame, raw);\n\n\t\t\t\t\tposition.x = frame.pointerX;\n\t\t\t\t\tposition.y = frame.pointerY;\n\t\t\t\t\tdelta.x = frame.pointerDeltaX;\n\t\t\t\t\tdelta.y = frame.pointerDeltaY;\n\n\t\t\t\t\tadvanceActionSlot(unifiedSlot, currentUnifiedMap, frame.keysDown, frame.pointerButtonsDown, pads);\n\t\t\t\t\tfor (const [id, map] of playerMaps) {\n\t\t\t\t\t\tconst slot = ensurePlayerSlot(id);\n\t\t\t\t\t\tadvanceActionSlot(slot, map, frame.keysDown, frame.pointerButtonsDown, pads);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "4PAeA,uBAAS,kBAoQF,SAAS,EAAmB,CAAC,EAAuC,CAC1E,OAAO,EAID,SAAS,EAAgB,CAAC,KAAgB,EAAuC,CACvF,OAAO,EAAQ,IAAI,CAAC,KAAY,CAAE,MAAK,QAAO,EAAE,EAI1C,SAAS,EAAa,CAAC,EAAa,EAAc,EAAmB,EAAoC,CAC/G,OAAO,IAAc,OAAY,CAAE,MAAK,OAAM,WAAU,EAAI,CAAE,MAAK,OAAM,YAAW,WAAU,EAyD/F,IAAM,EAAyB,IACzB,EAAmB,KACnB,EAAY,EAElB,SAAS,CAAwB,EAAuB,CACvD,MAAO,CACN,SAAU,IAAI,IACd,YAAa,CAAC,EACd,aAAc,CAAC,EACf,mBAAoB,IAAI,IACxB,sBAAuB,CAAC,EACxB,uBAAwB,CAAC,EACzB,SAAU,EACV,SAAU,EACV,aAAc,EACd,aAAc,EACd,aAAc,EACf,EAGD,SAAS,CAAgB,EAAe,CACvC,MAAO,CACN,SAAU,IAAI,IACd,YAAa,IAAI,IACjB,aAAc,IAAI,IAClB,mBAAoB,IAAI,IACxB,sBAAuB,IAAI,IAC3B,uBAAwB,IAAI,IAC5B,SAAU,EACV,SAAU,EACV,cAAe,EACf,cAAe,CAChB,EAGD,SAAS,CAAgB,EAAe,CACvC,MAAO,CACN,UAAW,GACX,GAAI,KACJ,YAAa,IAAI,IACjB,YAAa,IAAI,IACjB,eAAgB,IAAI,IACpB,gBAAiB,IAAI,IACrB,aAAc,CAAC,EACf,KAAM,CAAC,EACP,QAAS,CAAC,CACX,EAGD,SAAS,CAAgB,EAAe,CACvC,MAAO,CAAE,OAAQ,IAAI,IAAO,KAAM,IAAI,GAAM,EAG7C,SAAS,CAAY,CAAC,EAAc,EAA2B,CAC9D,EAAK,MAAM,EACX,QAAW,KAAQ,EAAQ,EAAK,IAAI,CAAI,EAGzC,SAAS,CAAuB,CAAC,EAAmB,EAA+B,CAClF,EAAU,EAAM,SAAU,EAAI,QAAQ,EACtC,EAAU,EAAM,YAAa,EAAI,WAAW,EAC5C,EAAU,EAAM,aAAc,EAAI,YAAY,EAC9C,EAAU,EAAM,mBAAoB,EAAI,kBAAkB,EAC1D,EAAU,EAAM,sBAAuB,EAAI,qBAAqB,EAChE,EAAU,EAAM,uBAAwB,EAAI,sBAAsB,EAElE,EAAM,cAAgB,EAAI,aAAe,EAAI,SAAW,EAAI,aAAe,EAC3E,EAAM,cAAgB,EAAI,aAAe,EAAI,SAAW,EAAI,aAAe,EAC3E,EAAM,SAAW,EAAI,SACrB,EAAM,SAAW,EAAI,SAErB,EAAI,YAAY,OAAS,EACzB,EAAI,aAAa,OAAS,EAC1B,EAAI,sBAAsB,OAAS,EACnC,EAAI,uBAAuB,OAAS,EACpC,EAAI,aAAe,EAAI,SACvB,EAAI,aAAe,EAAI,SACvB,EAAI,aAAe,GAGpB,SAAS,CAAW,CAAC,EAAyE,CAC7F,MAAO,IAAM,CACZ,GAAI,OAAO,UAAc,KAAe,OAAO,UAAU,cAAgB,WAAY,CACpF,QAAS,EAAI,EAAG,EAAI,EAAI,OAAQ,IAAK,EAAI,GAAK,KAC9C,OAAO,EAER,IAAM,EAAO,UAAU,YAAY,EACnC,QAAS,EAAI,EAAG,EAAI,EAAI,OAAQ,IAAK,EAAI,GAAK,EAAK,IAAM,KACzD,OAAO,GAIT,SAAS,CAAkB,CAAC,EAAW,EAAW,EAAkB,EAAe,EAAyB,CAC3G,IAAM,EAAM,KAAK,KAAK,EAAI,EAAI,EAAI,CAAC,EACnC,GAAI,EAAM,EAAU,CACnB,EAAI,GAAa,EACjB,EAAI,EAAY,GAAK,EACrB,OAED,IAAM,EAAS,KAAK,KAAK,EAAM,IAAa,EAAI,GAAW,CAAC,EAC5D,EAAI,GAAc,EAAI,EAAO,EAC7B,EAAI,EAAY,GAAM,EAAI,EAAO,EAGlC,SAAS,CAAmB,CAAC,EAAmB,EAAgB,EAAwB,CAEvF,GADA,EAAK,OAAS,EAAQ,OAClB,EAAQ,QAAU,EACrB,EAAmB,EAAQ,IAAM,EAAG,EAAQ,IAAM,EAAG,EAAU,EAAM,CAAC,EAEvE,GAAI,EAAQ,QAAU,EACrB,EAAmB,EAAQ,IAAM,EAAG,EAAQ,IAAM,EAAG,EAAU,EAAM,CAAC,EAGvE,QAAS,EAAI,EAAG,EAAI,EAAQ,OAAQ,IACnC,EAAK,GAAK,EAAQ,IAAM,EAI1B,SAAS,EAAgB,CAAC,EAAoB,EAAiD,EAAwB,CACtH,IAAM,EAAS,EAAO,EACtB,QAAS,EAAI,EAAG,EAAI,EAAW,IAAK,CACnC,IAAM,EAAM,EAAO,IAAM,KACnB,EAAQ,EAAK,GACnB,GAAI,CAAC,EAAO,SAGZ,IAAM,EAAa,EAAM,YAMzB,GALA,EAAU,EAAY,EAAM,WAAW,EACvC,EAAM,YAAY,MAAM,EACxB,EAAM,eAAe,MAAM,EAC3B,EAAM,gBAAgB,MAAM,EAExB,CAAC,GAAO,CAAC,EAAI,UAAW,CAC3B,GAAI,EAAM,UAAW,CAEpB,QAAW,KAAK,EAAY,EAAM,gBAAgB,IAAI,CAAC,EACvD,EAAM,UAAY,GAClB,EAAM,GAAK,KACX,EAAM,aAAa,OAAS,EAC5B,EAAM,KAAK,OAAS,EACpB,EAAM,QAAQ,OAAS,EAExB,SAGD,EAAM,UAAY,GAClB,EAAM,GAAK,EAAI,GAEf,EAAM,aAAa,OAAS,EAAI,QAAQ,OACxC,QAAS,EAAI,EAAG,EAAI,EAAI,QAAQ,OAAQ,IAAK,CAC5C,IAAM,EAAO,EAAI,QAAQ,GACzB,GAAI,CAAC,EAAM,CACV,EAAM,aAAa,GAAK,EACxB,SAGD,GADA,EAAM,aAAa,GAAK,EAAK,MACzB,EAAK,QAAS,EAAM,YAAY,IAAI,CAAC,EAG1C,QAAW,KAAK,EAAM,YACrB,GAAI,CAAC,EAAW,IAAI,CAAC,EAAG,EAAM,eAAe,IAAI,CAAC,EAEnD,QAAW,KAAK,EACf,GAAI,CAAC,EAAM,YAAY,IAAI,CAAC,EAAG,EAAM,gBAAgB,IAAI,CAAC,EAG3D,EAAM,QAAQ,OAAS,EAAI,KAAK,OAChC,QAAS,EAAI,EAAG,EAAI,EAAI,KAAK,OAAQ,IACpC,EAAM,QAAQ,GAAK,EAAI,KAAK,IAAM,EAEnC,EAAoB,EAAM,QAAS,EAAM,KAAM,CAAQ,GAIzD,SAAS,EAAe,CACvB,EACA,EACA,EACA,EACU,CACV,GAAI,EAAQ,MAAM,KAAK,CAAC,IAAM,EAAS,IAAI,CAAC,CAAC,EAAG,MAAO,GACvD,GAAI,EAAQ,gBAAgB,KAAK,CAAC,IAAM,EAAmB,IAAI,CAAC,CAAC,EAAG,MAAO,GAC3E,GAAI,EAAQ,gBAAgB,KAAK,EAAG,MAAK,YAAa,EAAK,IAAM,YAAY,IAAI,CAAM,GAAK,EAAK,EAAG,MAAO,GAC3G,GAAI,EAAQ,aAAa,KAAK,EAAG,MAAK,OAAM,YAAW,YAAY,KAA6B,CAC/F,IAAM,EAAQ,EAAK,IAAM,KAAK,IAAS,EACvC,OAAO,EAAY,EAAI,EAAQ,EAAY,EAAQ,CAAC,EACpD,EAAG,MAAO,GACX,MAAO,GAOR,SAAS,CAAiB,CACzB,EACA,EACA,EACA,EACA,EACO,CACP,IAAM,EAAa,EAAK,KACxB,EAAK,KAAO,EAAK,OACjB,EAAK,OAAS,EACd,EAAW,MAAM,EAEjB,QAAY,EAAM,KAAY,OAAO,QAAQ,CAAG,EAC/C,GAAI,GAAgB,EAAS,EAAU,EAAoB,CAAI,EAAG,EAAW,IAAI,CAAI,EAIvF,SAAS,CAAiC,CAAC,EAAkC,CAC5E,MAAO,CACN,SAAU,CAAC,IAAW,EAAK,OAAO,IAAI,CAAM,EAC5C,cAAe,CAAC,IAAW,EAAK,OAAO,IAAI,CAAM,GAAK,CAAC,EAAK,KAAK,IAAI,CAAM,EAC3E,gBAAiB,CAAC,IAAW,CAAC,EAAK,OAAO,IAAI,CAAM,GAAK,EAAK,KAAK,IAAI,CAAM,CAC9E,EAwCM,SAAS,EAAwE,CACvF,EACC,CACD,IACC,cAAc,QACd,WAAW,IACX,QAAQ,YACR,SAAS,WACT,QAAS,EAAc,CAAC,EACxB,uBACG,GAAW,CAAC,EAIV,EAAmB,IAAM,GAAS,SAAW,CAAC,CAAG,EACjD,EAAa,IAAI,IACtB,OAAO,QAAQ,GAAS,SAAW,CAAC,CAAC,CACtC,EAEM,EAAW,EAAY,UAAY,EACnC,EAAS,EAAY,MAAQ,EAAgB,MAA0B,CAAS,EAAE,KAAK,IAAI,CAAC,EAE5F,EAAM,EAAyB,EAC/B,EAAQ,EAAiB,EACzB,EAAqB,MAAM,KAAK,CAAE,OAAQ,CAAU,EAAG,CAAgB,EACvE,EAAc,EAAiB,EAC/B,EAAc,IAAI,IAClB,EAAgB,IAAI,IACpB,EAAgC,CAAC,EAGjC,EAAqB,CAAE,EAAG,EAAG,EAAG,CAAE,EAClC,EAAkB,CAAE,EAAG,EAAG,EAAG,CAAE,EAEjC,EAAoB,EAElB,EAA0B,CAC/B,OAAQ,CAAC,IAAQ,EAAM,SAAS,IAAI,CAAG,EACvC,YAAa,CAAC,IAAQ,EAAM,YAAY,IAAI,CAAG,EAC/C,aAAc,CAAC,IAAQ,EAAM,aAAa,IAAI,CAAG,CAClD,EAEM,EAAwB,CAC7B,WACA,QACA,OAAQ,CAAC,IAAW,EAAM,mBAAmB,IAAI,CAAM,EACvD,YAAa,CAAC,IAAW,EAAM,sBAAsB,IAAI,CAAM,EAC/D,aAAc,CAAC,IAAW,EAAM,uBAAuB,IAAI,CAAM,CAClE,EAEA,SAAS,CAAgB,CAAC,EAA6B,CACtD,IAAM,EAAQ,EAAK,GACnB,GAAI,CAAC,EAAO,MAAU,MAAM,0BAA0B,GAAO,EAC7D,MAAO,IACF,UAAS,EAAG,CAAE,OAAO,EAAM,cAC3B,GAAE,EAAG,CAAE,OAAO,EAAM,IACxB,OAAQ,CAAC,IAAW,EAAM,YAAY,IAAI,CAAM,EAChD,YAAa,CAAC,IAAW,EAAM,eAAe,IAAI,CAAM,EACxD,aAAc,CAAC,IAAW,EAAM,gBAAgB,IAAI,CAAM,EAC1D,YAAa,CAAC,IAAW,EAAM,aAAa,IAAW,EACvD,KAAM,CAAC,IAAU,EAAM,KAAK,IAAU,EACtC,QAAS,CAAC,IAAU,EAAM,QAAQ,IAAU,CAC7C,EAGD,IAAM,EAA6C,MAAM,KAAK,CAAE,OAAQ,CAAU,EAAG,CAAC,EAAG,IAAM,EAAiB,CAAC,CAAC,EAE5G,EAAiB,EAAmB,CAAW,EAErD,SAAS,CAAgB,CAAC,EAAwB,CACjD,IAAM,EAAW,EAAY,IAAI,CAAE,EACnC,GAAI,EAAU,OAAO,EACrB,IAAM,EAAO,EAAiB,EAE9B,OADA,EAAY,IAAI,EAAI,CAAI,EACjB,EAGR,SAAS,CAAkB,CAAC,EAA4B,CACvD,IAAM,EAAO,EAAiB,CAAE,EAChC,MAAO,CACN,QAAS,EAAmB,CAAI,EAChC,aAAc,CAAC,IAAQ,CACtB,GAAI,CAAC,EAAW,IAAI,CAAE,EAAG,MAAU,MAAM,WAAW,gBAAiB,EACrE,EAAW,IAAI,EAAI,IAAK,CAAI,CAAC,GAE9B,aAAc,IAAM,CACnB,IAAM,EAAM,EAAW,IAAI,CAAE,EAC7B,GAAI,CAAC,EAAK,MAAU,MAAM,WAAW,gBAAiB,EACtD,MAAO,IAAK,CAAI,EAElB,EAGD,QAAW,KAAM,EAAW,KAAK,EAChC,EAAc,IAAI,EAAI,EAAmB,CAAE,CAAC,EAG7C,IAAM,EAA4B,CACjC,WACA,UACA,SAAU,EACV,QAAS,EACT,YAAY,CAAC,EAAQ,CACpB,EAAoB,IAAK,CAAO,GAEjC,YAAY,EAAG,CACd,MAAO,IAAK,CAAkB,GAE/B,YAAY,CAAC,EAAI,EAAK,CAErB,GADA,EAAW,IAAI,EAAI,IAAK,CAAI,CAAC,EACzB,CAAC,EAAc,IAAI,CAAE,EAAG,EAAc,IAAI,EAAI,EAAmB,CAAE,CAAC,GAEzE,YAAY,CAAC,EAAI,CAChB,IAAM,EAAU,EAAW,OAAO,CAAE,EAGpC,OAFA,EAAc,OAAO,CAAE,EACvB,EAAY,OAAO,CAAE,EACd,GAER,MAAM,CAAC,EAAI,CACV,OAAO,EAAc,IAAI,CAAE,GAE5B,SAAS,EAAG,CACX,OAAO,MAAM,KAAK,EAAW,KAAK,CAAC,EAErC,EAEA,SAAS,CAAS,CAAC,EAAU,CAC5B,IAAM,EAAK,EACX,GAAI,EAAG,OAAQ,OACf,EAAI,SAAS,IAAI,EAAG,GAAG,EACvB,EAAI,YAAY,KAAK,EAAG,GAAG,EAG5B,SAAS,CAAO,CAAC,EAAU,CAC1B,IAAM,EAAK,EACX,EAAI,SAAS,OAAO,EAAG,GAAG,EAC1B,EAAI,aAAa,KAAK,EAAG,GAAG,EAG7B,SAAS,CAAa,CAAC,EAAU,CAChC,IAAM,EAAK,EACX,EAAI,mBAAmB,IAAI,EAAG,MAAM,EACpC,EAAI,sBAAsB,KAAK,EAAG,MAAM,EAGzC,SAAS,CAAa,CAAC,EAAU,CAChC,IAAM,EAAK,EACX,GAAI,EAAqB,CACxB,IAAQ,IAAG,KAAM,EAAoB,EAAG,QAAS,EAAG,OAAO,EAC3D,EAAI,SAAW,EACf,EAAI,SAAW,EAEf,OAAI,SAAW,EAAG,QAClB,EAAI,SAAW,EAAG,QAEnB,EAAI,aAAe,GAGpB,SAAS,CAAW,CAAC,EAAU,CAC9B,IAAM,EAAK,EACX,EAAI,mBAAmB,OAAO,EAAG,MAAM,EACvC,EAAI,uBAAuB,KAAK,EAAG,MAAM,EAG1C,SAAS,CAAW,CAAC,EAAc,EAA6B,CAC/D,EAAO,iBAAiB,EAAM,CAAO,EACrC,EAAW,KAAK,IAAM,CAAE,EAAO,oBAAoB,EAAM,CAAO,EAAI,EAGrE,OAAO,EAAa,OAAO,EACzB,kBAAyC,EACzC,WAA0B,EAC1B,WAAc,EACd,QAAQ,CAAC,IAAU,CACnB,EAAM,YAAY,aAAc,CAAU,EAE1C,EACE,UAAU,aAAa,EACvB,YAAY,CAAQ,EACpB,QAAQ,CAAK,EACb,QAAQ,CAAW,EACnB,gBAAgB,IAAM,CACtB,EAAY,UAAW,CAAS,EAChC,EAAY,QAAS,CAAO,EAC5B,EAAY,cAAe,CAAa,EACxC,EAAY,cAAe,CAAa,EACxC,EAAY,YAAa,CAAW,EACpC,EACA,YAAY,IAAM,CAClB,QAAW,KAAW,EAAY,EAAQ,EAC1C,EAAW,OAAS,EACpB,EACA,WAAW,IAAM,CAGjB,GAAiB,EAAM,EAAQ,CAAQ,EACvC,EAAwB,EAAO,CAAG,EAElC,EAAS,EAAI,EAAM,SACnB,EAAS,EAAI,EAAM,SACnB,EAAM,EAAI,EAAM,cAChB,EAAM,EAAI,EAAM,cAEhB,EAAkB,EAAa,EAAmB,EAAM,SAAU,EAAM,mBAAoB,CAAI,EAChG,QAAY,EAAI,KAAQ,EAAY,CACnC,IAAM,EAAO,EAAiB,CAAE,EAChC,EAAkB,EAAM,EAAK,EAAM,SAAU,EAAM,mBAAoB,CAAI,GAE5E,EACF",
|
|
8
|
+
"debugId": "A6B7FF490EDEEA4864756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
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};
|
|
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),T=-(K.rotation+K.shakeRotation),E=Math.cos(T),A=Math.sin(T),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,T=K.rotation+K.shakeRotation,E=Math.cos(T),A=Math.sin(T),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:T,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(T)R.addComponent(V.id,"cameraShake",m(T));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 G=N.getComponent(Q.target,"worldTransform");if(!G)continue;let z=G.x+Q.offsetX,_=G.y+Q.offsetY,B=z-j.x,W=_-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(W)>Q.deadzoneY){let H=W>0?1:-1,D=W-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,G=$.maxX-Z,z=$.minY+j,_=$.maxY-j;if(Q>G)N.x=($.minX+$.maxX)/2;else N.x=Math.max(Q,Math.min(G,N.x));if(z>_)N.y=($.minY+$.maxY)/2;else N.y=Math.max(z,Math.min(_,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 G=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 _=z.tryGetResource("inputState"),B=z.tryGetResource("pixiApp");if(!_||!B){console.error("[camera] zoom requires the input plugin and renderer2D plugin. Zoom will be disabled.");return}j=B.canvas,j.addEventListener("wheel",G,{passive:!1}),Q=z.tryGetResource("isoProjection"),Z=!0}).setOnDetach(()=>{if(!Z||!j)return;j.removeEventListener("wheel",G)}).setProcess(({queries:z,ecs:_})=>{if(!Z||$===0)return;let B=$;$=0;let[W]=z.cameras;if(!W)return;let H=W.components.camera,D=_.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(),b=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,L=(H.x+H.y)*Y+Q.originY,x=h+b/H.zoom,S=L+v/H.zoom;H.zoom=C;let c=x-b/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((G)=>{if(!G.tryGetResource("inputState")){console.error("[camera] pan requires the input plugin. Pan will be disabled.");return}Q=!0}).setProcess(({ecs:G,dt:z})=>{if(!Q)return;let _=G.tryGetResource("inputState");if(!_)return;let B=R/J.zoom*z,W=(_.actions.isActive(j)?1:0)-(_.actions.isActive(Z)?1:0),H=(_.actions.isActive($)?1:0)-(_.actions.isActive(N)?1:0);if(W!==0||H!==0)J.setPosition(J.x+W*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:T=65280,boxFillAlpha:E=0.15,boxStrokeColor:A=65280,boxStrokeAlpha:y=0.8,selectedTint:q=4521796,renderLayer:g}=P??{},f={color:T,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 b=j.dragStart.x,v=j.dragStart.y,X=Q.position.x,Y=Q.position.y,h=Math.min(b,X),L=Math.min(v,Y),x=Math.abs(X-b),S=Math.abs(Y-v);I.clear(),I.rect(h,L,x,S),I.fill(f),I.stroke(k)}if(!Q.justReleased(0)||j.boxEntityId===null)return;let G=j.dragStart.x,z=j.dragStart.y,_=Q.position.x,B=Q.position.y,W=Math.abs(_-G),H=Math.abs(B-z);for(let I of V.currentlySelected)N.removeComponent(I.id,"selected");let D=W<O&&H<O,F=N.tryGetResource("cameraState"),C=F?p(_,B,F):{x:_,y:B};if(D){let b=null,v=1/0;for(let X of V.selectables){let{worldTransform:Y}=X.components,h=Y.x-C.x,L=Y.y-C.y,x=h*h+L*L;if(x<400&&x<v)v=x,b=X.id}if(b!==null)N.addComponent(b,"selected",!0)}else{let I=F?p(G,z,F):{x:G,y:z},b=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:L}=h.components;if(L.x>=b&&L.x<=v&&L.y>=X&&L.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};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=A100A9606EA8CF3164756E2164756E21
|
|
4
4
|
//# sourceMappingURL=selection.js.map
|