1681-engine 0.5.8 → 0.5.9
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/CHANGELOG.md +53 -0
- package/dist/1681-engine.js +463 -409
- package/dist/ambient/ColorGrade.js +0 -1
- package/dist/ambient/DayClock.js +0 -1
- package/dist/ambient/WeatherLayer.js +0 -1
- package/dist/ambient/grade.js +0 -1
- package/dist/ambient/weather.js +0 -1
- package/dist/anim/AnimStateMachine.js +0 -1
- package/dist/anim/Animator.js +0 -1
- package/dist/anim/animStateCompile.js +0 -1
- package/dist/anim/animStateSystem.js +0 -1
- package/dist/anim/animationSystem.js +0 -1
- package/dist/anim/asepriteSlices.js +0 -1
- package/dist/anim/asepriteTags.js +0 -1
- package/dist/anim/parseAseprite.js +0 -1
- package/dist/assets/AssetLoader.js +112 -14
- package/dist/assets/loadAseprite.js +0 -1
- package/dist/assets/takeCleanerWorker-DTcI_Jqr.js +0 -1
- package/dist/assets/textureAtlas.js +58 -0
- package/dist/audio/Footsteps.js +0 -1
- package/dist/audio/MusicPlayer.js +0 -1
- package/dist/audio/SoundEngine.js +13 -7
- package/dist/audio/cleanTake.js +0 -1
- package/dist/audio/music/ElementVoice.js +0 -1
- package/dist/audio/music/GraphVoice.js +0 -1
- package/dist/audio/music/MusicChannel.js +0 -1
- package/dist/audio/music/musicVoice.js +0 -1
- package/dist/audio/sampleBank.js +0 -1
- package/dist/audio/soundTable.js +0 -1
- package/dist/audio/synth.js +0 -1
- package/dist/core/BitmapLabel.js +0 -1
- package/dist/core/Camera.js +1 -2
- package/dist/core/Clock.js +31 -21
- package/dist/core/Graphics.js +0 -1
- package/dist/core/Group.js +0 -1
- package/dist/core/Image.js +0 -1
- package/dist/core/ObjectFactory.js +0 -1
- package/dist/core/Pointer.js +46 -24
- package/dist/core/Rectangle.js +0 -1
- package/dist/core/Stage.js +5 -6
- package/dist/core/Text.js +0 -1
- package/dist/core/TextureRegistry.js +54 -24
- package/dist/core/Timeline.js +0 -1
- package/dist/core/TimelineBuilder.js +0 -1
- package/dist/core/TimelineChild.js +0 -1
- package/dist/core/Tweener.js +0 -1
- package/dist/core/atlas.js +0 -1
- package/dist/core/atlasPlan.js +0 -1
- package/dist/core/blendLayer.js +11 -0
- package/dist/core/eases.js +0 -1
- package/dist/core/gameObject.js +22 -20
- package/dist/core/pixelZoom.js +43 -0
- package/dist/core/pixiPatches.js +0 -1
- package/dist/core/runDue.js +28 -0
- package/dist/core/silhouette.js +49 -0
- package/dist/core/timelinePlace.js +0 -1
- package/dist/core/tweenProps.js +0 -1
- package/dist/debug/DebugOverlay.js +0 -1
- package/dist/debug/DrawCallCounter.js +15 -12
- package/dist/debug/ErrorLog.js +0 -1
- package/dist/debug/FrameStats.js +0 -1
- package/dist/debug/inspectWorld.js +0 -1
- package/dist/diagnostics/ErrorDeduper.js +0 -1
- package/dist/diagnostics/FrameSampler.js +0 -1
- package/dist/diagnostics/bootWatch.js +0 -1
- package/dist/diagnostics/captureErrors.js +0 -1
- package/dist/diagnostics/contextLoss.js +0 -1
- package/dist/diagnostics/frameStats.js +0 -1
- package/dist/diagnostics/gpuInfo.js +0 -1
- package/dist/diagnostics/renderGuard.js +0 -1
- package/dist/ecs/SparseSet.js +0 -1
- package/dist/ecs/World.js +0 -1
- package/dist/ecs/fields.js +0 -1
- package/dist/engine/Engine.js +42 -18
- package/dist/engine/FixedLoop.js +0 -1
- package/dist/engine/HiddenPause.js +27 -0
- package/dist/engine/Scene.js +0 -1
- package/dist/engine/SceneManager.js +0 -1
- package/dist/events/EventBus.js +0 -1
- package/dist/fx/Afterimages.js +82 -0
- package/dist/fx/FloatingText.js +0 -1
- package/dist/fx/hitStop.js +0 -1
- package/dist/fx/viewCull.js +0 -1
- package/dist/i18n/Translator.js +0 -1
- package/dist/i18n/extractStrings.js +0 -1
- package/dist/i18n/formatMessage.js +0 -1
- package/dist/input/Actions.js +29 -21
- package/dist/input/AimGesture.js +0 -1
- package/dist/input/Keyboard.js +0 -1
- package/dist/input/Keymap.js +0 -1
- package/dist/input/TargetLock.js +0 -1
- package/dist/input/aimAxis.js +0 -1
- package/dist/input/keyChord.js +0 -1
- package/dist/input/longPress.js +0 -1
- package/dist/input/rumble.js +42 -0
- package/dist/input/stickAxis.js +0 -1
- package/dist/input/touchDetect.js +0 -1
- package/dist/light/LightLayer.js +0 -1
- package/dist/nav/MinHeap.js +0 -1
- package/dist/nav/findPath.js +0 -1
- package/dist/nav/navGrid.js +0 -1
- package/dist/nav/nearestOpen.js +0 -1
- package/dist/nav/straightenPath.js +0 -1
- package/dist/net/Connection.js +39 -16
- package/dist/net/InterpolationBuffer.js +0 -1
- package/dist/net/PredictedMover.js +0 -1
- package/dist/net/Room.js +0 -1
- package/dist/net/RoomHost.js +0 -1
- package/dist/net/cadence.js +0 -1
- package/dist/net/constants.js +0 -1
- package/dist/net/lobby/LobbyClient.js +0 -1
- package/dist/net/lobby/LobbyHost.js +0 -1
- package/dist/net/lobby/LobbyRooms.js +0 -1
- package/dist/net/lobby/Matchmaker.js +0 -1
- package/dist/net/movement.js +40 -39
- package/dist/net/protocol.js +0 -1
- package/dist/net/reconnect.js +10 -0
- package/dist/net/serverClock.js +0 -1
- package/dist/net/snapshotCodec.js +0 -1
- package/dist/parallax/Parallax.js +0 -1
- package/dist/parallax/ParallaxLayer.js +0 -1
- package/dist/parallax/parallaxMath.js +0 -1
- package/dist/particles/Emitter.js +0 -1
- package/dist/particles/ParticleBudget.js +0 -1
- package/dist/particles/ParticleSim.js +0 -1
- package/dist/particles/particleCurves.js +0 -1
- package/dist/particles/particleSampling.js +0 -1
- package/dist/physics/Body.js +0 -1
- package/dist/physics/BroadPhase.js +0 -1
- package/dist/physics/Contact.js +0 -1
- package/dist/physics/ContactSolver.js +0 -1
- package/dist/physics/PhysicsWorld.js +0 -1
- package/dist/physics/SpatialHash.js +0 -1
- package/dist/physics/collide.js +0 -1
- package/dist/physics/collidePolygon.js +0 -1
- package/dist/physics/contactReport.js +0 -1
- package/dist/physics/drawColliders.js +0 -1
- package/dist/physics/physicsSystem.js +0 -1
- package/dist/physics/polygon.js +0 -1
- package/dist/physics/raycast.js +0 -1
- package/dist/physics/syncToBody.js +0 -1
- package/dist/physics/tileBodies.js +0 -1
- package/dist/physics/types.js +0 -1
- package/dist/pixel/Darkness.js +50 -47
- package/dist/pixel/DitherVignette.js +0 -1
- package/dist/pixel/Evaporation.js +0 -1
- package/dist/pixel/NightShade.js +0 -1
- package/dist/pixel/PixelLayer.js +0 -1
- package/dist/pixel/PixelMarks.js +99 -0
- package/dist/pixel/SwayProp.js +0 -1
- package/dist/pixel/aimPixels.js +67 -0
- package/dist/pixel/lights.js +0 -1
- package/dist/pixel/nineSlice.js +0 -1
- package/dist/pixel/paintLayer.js +0 -1
- package/dist/pixel/palette.js +0 -1
- package/dist/pixel/pixel.js +0 -1
- package/dist/pixel/pixelPaint.js +27 -24
- package/dist/pixel/pixelParticles.js +35 -0
- package/dist/pixel/pixelTone.js +0 -1
- package/dist/pixel/propKit.js +0 -1
- package/dist/pixel/ramp.js +0 -1
- package/dist/pixel/selout.js +0 -1
- package/dist/pixel/spritePixels.js +0 -1
- package/dist/pixel/swayRig.js +0 -1
- package/dist/pixel/vignetteMask.js +0 -1
- package/dist/pixel/weatherArt.js +0 -1
- package/dist/pixel.js +71 -65
- package/dist/platform/appResume.js +18 -0
- package/dist/platform/fullscreen.js +52 -0
- package/dist/platform/orientation.js +26 -0
- package/dist/platform/pointerLock.js +37 -0
- package/dist/platform/reducedMotion.js +23 -0
- package/dist/platform/timeoutSignal.js +14 -0
- package/dist/platform/touch.js +0 -1
- package/dist/postfx/BloomFilter.js +0 -1
- package/dist/postfx/ChromaticAberrationFilter.js +0 -1
- package/dist/postfx/ColorAdjustFilter.js +0 -1
- package/dist/postfx/CrtFilter.js +0 -1
- package/dist/postfx/PixelateFilter.js +0 -1
- package/dist/postfx/PostFx.js +0 -1
- package/dist/postfx/ShockwaveFilter.js +0 -1
- package/dist/postfx/VignetteFilter.js +0 -1
- package/dist/postfx/fxAnimator.js +0 -1
- package/dist/postfx/fxShader.js +0 -1
- package/dist/postfx.js +0 -1
- package/dist/react/ChoiceDialog.js +0 -1
- package/dist/react/ConfirmDialog.js +0 -1
- package/dist/react/Glyph.js +0 -1
- package/dist/react/Modal.js +0 -1
- package/dist/react/Panel.js +0 -1
- package/dist/react/PixelImage.js +0 -1
- package/dist/react/Select.js +0 -1
- package/dist/react/Slider.js +0 -1
- package/dist/react/SplitPane.js +0 -1
- package/dist/react/TabStrip.js +0 -1
- package/dist/react/TitleTips.js +0 -1
- package/dist/react/Tooltip.js +0 -1
- package/dist/react/UiProvider.js +0 -1
- package/dist/react/baseCss.js +0 -1
- package/dist/react/choiceStore.js +0 -1
- package/dist/react/classNames.js +0 -1
- package/dist/react/confirmStore.js +0 -1
- package/dist/react/labels.js +0 -1
- package/dist/react/placement.js +0 -1
- package/dist/react/useFillSheet.js +0 -1
- package/dist/react/useFitToViewport.js +0 -1
- package/dist/react/usePicked.js +0 -1
- package/dist/react/useStore.js +0 -1
- package/dist/react/useViewport.js +0 -1
- package/dist/react.js +0 -1
- package/dist/rig/PosePlayer.js +0 -1
- package/dist/rig/RigView.js +0 -1
- package/dist/rig/Skeleton.js +0 -1
- package/dist/rig/humanoid.js +0 -1
- package/dist/rig/humanoid.json.js +0 -1
- package/dist/rig/humanoidPoses.js +0 -1
- package/dist/rig/poseMath.js +0 -1
- package/dist/rig/poses.json.js +0 -1
- package/dist/save/Autosaver.js +0 -1
- package/dist/save/MemoryBackend.js +0 -1
- package/dist/save/SaveStore.js +0 -1
- package/dist/save/WebStorageBackend.js +0 -1
- package/dist/save/checksum.js +0 -1
- package/dist/save/migrateSave.js +0 -1
- package/dist/settings.js +0 -1
- package/dist/tilemap/TilemapView.js +0 -1
- package/dist/tilemap/collision.js +0 -1
- package/dist/tilemap/tiled.js +0 -1
- package/dist/transitions/CrossfadeTransition.js +0 -1
- package/dist/transitions/DitherWipeTransition.js +0 -1
- package/dist/transitions/FadeTransition.js +0 -1
- package/dist/transitions/Letterbox.js +0 -1
- package/dist/transitions/ditherMask.js +0 -1
- package/dist/transitions/transitionTo.js +0 -1
- package/dist/transitions/tweenDone.js +0 -1
- package/dist/types/assets/AssetLoader.d.ts +63 -8
- package/dist/types/assets/AssetLoader.d.ts.map +1 -1
- package/dist/types/assets/textureAtlas.d.ts +35 -0
- package/dist/types/assets/textureAtlas.d.ts.map +1 -0
- package/dist/types/audio/SoundEngine.d.ts +5 -0
- package/dist/types/audio/SoundEngine.d.ts.map +1 -1
- package/dist/types/core/Camera.d.ts +1 -1
- package/dist/types/core/Camera.d.ts.map +1 -1
- package/dist/types/core/Clock.d.ts +14 -2
- package/dist/types/core/Clock.d.ts.map +1 -1
- package/dist/types/core/Pointer.d.ts +18 -0
- package/dist/types/core/Pointer.d.ts.map +1 -1
- package/dist/types/core/Stage.d.ts.map +1 -1
- package/dist/types/core/TextureRegistry.d.ts +39 -0
- package/dist/types/core/TextureRegistry.d.ts.map +1 -1
- package/dist/types/core/blendLayer.d.ts +23 -0
- package/dist/types/core/blendLayer.d.ts.map +1 -0
- package/dist/types/core/gameObject.d.ts.map +1 -1
- package/dist/types/core/pixelZoom.d.ts +45 -0
- package/dist/types/core/pixelZoom.d.ts.map +1 -0
- package/dist/types/core/runDue.d.ts +15 -0
- package/dist/types/core/runDue.d.ts.map +1 -0
- package/dist/types/core/silhouette.d.ts +16 -0
- package/dist/types/core/silhouette.d.ts.map +1 -0
- package/dist/types/debug/DrawCallCounter.d.ts.map +1 -1
- package/dist/types/engine/Engine.d.ts +16 -0
- package/dist/types/engine/Engine.d.ts.map +1 -1
- package/dist/types/engine/HiddenPause.d.ts +22 -0
- package/dist/types/engine/HiddenPause.d.ts.map +1 -0
- package/dist/types/fx/Afterimages.d.ts +62 -0
- package/dist/types/fx/Afterimages.d.ts.map +1 -0
- package/dist/types/index.d.ts +18 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/input/Actions.d.ts +7 -1
- package/dist/types/input/Actions.d.ts.map +1 -1
- package/dist/types/input/rumble.d.ts +41 -0
- package/dist/types/input/rumble.d.ts.map +1 -0
- package/dist/types/net/Connection.d.ts +22 -0
- package/dist/types/net/Connection.d.ts.map +1 -1
- package/dist/types/net/constants.d.ts +13 -4
- package/dist/types/net/constants.d.ts.map +1 -1
- package/dist/types/net/movement.d.ts.map +1 -1
- package/dist/types/net/reconnect.d.ts +14 -0
- package/dist/types/net/reconnect.d.ts.map +1 -0
- package/dist/types/pixel/Darkness.d.ts +17 -5
- package/dist/types/pixel/Darkness.d.ts.map +1 -1
- package/dist/types/pixel/PixelMarks.d.ts +68 -0
- package/dist/types/pixel/PixelMarks.d.ts.map +1 -0
- package/dist/types/pixel/aimPixels.d.ts +23 -0
- package/dist/types/pixel/aimPixels.d.ts.map +1 -0
- package/dist/types/pixel/index.d.ts +3 -1
- package/dist/types/pixel/index.d.ts.map +1 -1
- package/dist/types/pixel/pixelPaint.d.ts +1 -0
- package/dist/types/pixel/pixelPaint.d.ts.map +1 -1
- package/dist/types/pixel/pixelParticles.d.ts +21 -0
- package/dist/types/pixel/pixelParticles.d.ts.map +1 -0
- package/dist/types/platform/appResume.d.ts +25 -0
- package/dist/types/platform/appResume.d.ts.map +1 -0
- package/dist/types/platform/fullscreen.d.ts +37 -0
- package/dist/types/platform/fullscreen.d.ts.map +1 -0
- package/dist/types/platform/orientation.d.ts +19 -0
- package/dist/types/platform/orientation.d.ts.map +1 -0
- package/dist/types/platform/pointerLock.d.ts +26 -0
- package/dist/types/platform/pointerLock.d.ts.map +1 -0
- package/dist/types/platform/reducedMotion.d.ts +20 -0
- package/dist/types/platform/reducedMotion.d.ts.map +1 -0
- package/dist/types/platform/timeoutSignal.d.ts +6 -0
- package/dist/types/platform/timeoutSignal.d.ts.map +1 -0
- package/dist/types/ui/index.d.ts +4 -0
- package/dist/types/ui/index.d.ts.map +1 -1
- package/dist/types/ui/rich/RichText.d.ts +48 -0
- package/dist/types/ui/rich/RichText.d.ts.map +1 -0
- package/dist/types/ui/rich/richFonts.d.ts +70 -0
- package/dist/types/ui/rich/richFonts.d.ts.map +1 -0
- package/dist/types/ui/rich/richLayout.d.ts +76 -0
- package/dist/types/ui/rich/richLayout.d.ts.map +1 -0
- package/dist/types/ui/rich/richMarkup.d.ts +48 -0
- package/dist/types/ui/rich/richMarkup.d.ts.map +1 -0
- package/dist/ui/Bar.js +0 -1
- package/dist/ui/Button.js +0 -1
- package/dist/ui/Dialog.js +0 -1
- package/dist/ui/FocusManager.js +0 -1
- package/dist/ui/Label.js +0 -1
- package/dist/ui/List.js +0 -1
- package/dist/ui/Panel.js +0 -1
- package/dist/ui/ScrollView.js +0 -1
- package/dist/ui/Slider.js +0 -1
- package/dist/ui/Stack.js +0 -1
- package/dist/ui/Toggle.js +0 -1
- package/dist/ui/UiHost.js +0 -1
- package/dist/ui/UiPointerGate.js +0 -1
- package/dist/ui/UiRoot.js +0 -1
- package/dist/ui/Widget.js +0 -1
- package/dist/ui/dom/DomOverlay.js +0 -1
- package/dist/ui/dom/DomWorldAnchors.js +0 -1
- package/dist/ui/dom/InputShield.js +0 -1
- package/dist/ui/dom/createStore.js +0 -1
- package/dist/ui/dom/shieldDecision.js +0 -1
- package/dist/ui/dom/worldToScreen.js +0 -1
- package/dist/ui/layout/anchor.js +0 -1
- package/dist/ui/layout/barDrain.js +0 -1
- package/dist/ui/layout/fitToViewport.js +0 -1
- package/dist/ui/layout/focusNav.js +0 -1
- package/dist/ui/layout/geometry.js +0 -1
- package/dist/ui/layout/listWidth.js +0 -1
- package/dist/ui/layout/placeTooltip.js +0 -1
- package/dist/ui/layout/scroll.js +0 -1
- package/dist/ui/layout/slider.js +0 -1
- package/dist/ui/layout/stack.js +0 -1
- package/dist/ui/layout/wrapRow.js +0 -1
- package/dist/ui/rich/RichText.js +87 -0
- package/dist/ui/rich/richFonts.js +105 -0
- package/dist/ui/rich/richLayout.js +132 -0
- package/dist/ui/rich/richMarkup.js +67 -0
- package/dist/ui.js +75 -65
- package/package.json +1 -4
- package/dist/1681-engine.js.map +0 -1
- package/dist/ambient/ColorGrade.js.map +0 -1
- package/dist/ambient/DayClock.js.map +0 -1
- package/dist/ambient/WeatherLayer.js.map +0 -1
- package/dist/ambient/grade.js.map +0 -1
- package/dist/ambient/weather.js.map +0 -1
- package/dist/anim/AnimStateMachine.js.map +0 -1
- package/dist/anim/Animator.js.map +0 -1
- package/dist/anim/animStateCompile.js.map +0 -1
- package/dist/anim/animStateSystem.js.map +0 -1
- package/dist/anim/animationSystem.js.map +0 -1
- package/dist/anim/asepriteSlices.js.map +0 -1
- package/dist/anim/asepriteTags.js.map +0 -1
- package/dist/anim/parseAseprite.js.map +0 -1
- package/dist/assets/AssetLoader.js.map +0 -1
- package/dist/assets/loadAseprite.js.map +0 -1
- package/dist/assets/takeCleanerWorker-DTcI_Jqr.js.map +0 -1
- package/dist/audio/Footsteps.js.map +0 -1
- package/dist/audio/MusicPlayer.js.map +0 -1
- package/dist/audio/SoundEngine.js.map +0 -1
- package/dist/audio/cleanTake.js.map +0 -1
- package/dist/audio/music/ElementVoice.js.map +0 -1
- package/dist/audio/music/GraphVoice.js.map +0 -1
- package/dist/audio/music/MusicChannel.js.map +0 -1
- package/dist/audio/music/musicVoice.js.map +0 -1
- package/dist/audio/sampleBank.js.map +0 -1
- package/dist/audio/soundTable.js.map +0 -1
- package/dist/audio/synth.js.map +0 -1
- package/dist/core/BitmapLabel.js.map +0 -1
- package/dist/core/Camera.js.map +0 -1
- package/dist/core/Clock.js.map +0 -1
- package/dist/core/Graphics.js.map +0 -1
- package/dist/core/Group.js.map +0 -1
- package/dist/core/Image.js.map +0 -1
- package/dist/core/ObjectFactory.js.map +0 -1
- package/dist/core/Pointer.js.map +0 -1
- package/dist/core/Rectangle.js.map +0 -1
- package/dist/core/Stage.js.map +0 -1
- package/dist/core/Text.js.map +0 -1
- package/dist/core/TextureRegistry.js.map +0 -1
- package/dist/core/Timeline.js.map +0 -1
- package/dist/core/TimelineBuilder.js.map +0 -1
- package/dist/core/TimelineChild.js.map +0 -1
- package/dist/core/Tweener.js.map +0 -1
- package/dist/core/atlas.js.map +0 -1
- package/dist/core/atlasPlan.js.map +0 -1
- package/dist/core/eases.js.map +0 -1
- package/dist/core/gameObject.js.map +0 -1
- package/dist/core/pixiPatches.js.map +0 -1
- package/dist/core/timelinePlace.js.map +0 -1
- package/dist/core/tweenProps.js.map +0 -1
- package/dist/debug/DebugOverlay.js.map +0 -1
- package/dist/debug/DrawCallCounter.js.map +0 -1
- package/dist/debug/ErrorLog.js.map +0 -1
- package/dist/debug/FrameStats.js.map +0 -1
- package/dist/debug/inspectWorld.js.map +0 -1
- package/dist/diagnostics/ErrorDeduper.js.map +0 -1
- package/dist/diagnostics/FrameSampler.js.map +0 -1
- package/dist/diagnostics/bootWatch.js.map +0 -1
- package/dist/diagnostics/captureErrors.js.map +0 -1
- package/dist/diagnostics/contextLoss.js.map +0 -1
- package/dist/diagnostics/frameStats.js.map +0 -1
- package/dist/diagnostics/gpuInfo.js.map +0 -1
- package/dist/diagnostics/renderGuard.js.map +0 -1
- package/dist/ecs/SparseSet.js.map +0 -1
- package/dist/ecs/World.js.map +0 -1
- package/dist/ecs/fields.js.map +0 -1
- package/dist/engine/Engine.js.map +0 -1
- package/dist/engine/FixedLoop.js.map +0 -1
- package/dist/engine/Scene.js.map +0 -1
- package/dist/engine/SceneManager.js.map +0 -1
- package/dist/events/EventBus.js.map +0 -1
- package/dist/fx/FloatingText.js.map +0 -1
- package/dist/fx/hitStop.js.map +0 -1
- package/dist/fx/viewCull.js.map +0 -1
- package/dist/i18n/Translator.js.map +0 -1
- package/dist/i18n/extractStrings.js.map +0 -1
- package/dist/i18n/formatMessage.js.map +0 -1
- package/dist/input/Actions.js.map +0 -1
- package/dist/input/AimGesture.js.map +0 -1
- package/dist/input/Keyboard.js.map +0 -1
- package/dist/input/Keymap.js.map +0 -1
- package/dist/input/TargetLock.js.map +0 -1
- package/dist/input/aimAxis.js.map +0 -1
- package/dist/input/keyChord.js.map +0 -1
- package/dist/input/longPress.js.map +0 -1
- package/dist/input/stickAxis.js.map +0 -1
- package/dist/input/touchDetect.js.map +0 -1
- package/dist/light/LightLayer.js.map +0 -1
- package/dist/nav/MinHeap.js.map +0 -1
- package/dist/nav/findPath.js.map +0 -1
- package/dist/nav/navGrid.js.map +0 -1
- package/dist/nav/nearestOpen.js.map +0 -1
- package/dist/nav/straightenPath.js.map +0 -1
- package/dist/net/Connection.js.map +0 -1
- package/dist/net/InterpolationBuffer.js.map +0 -1
- package/dist/net/PredictedMover.js.map +0 -1
- package/dist/net/Room.js.map +0 -1
- package/dist/net/RoomHost.js.map +0 -1
- package/dist/net/cadence.js.map +0 -1
- package/dist/net/constants.js.map +0 -1
- package/dist/net/lobby/LobbyClient.js.map +0 -1
- package/dist/net/lobby/LobbyHost.js.map +0 -1
- package/dist/net/lobby/LobbyRooms.js.map +0 -1
- package/dist/net/lobby/Matchmaker.js.map +0 -1
- package/dist/net/movement.js.map +0 -1
- package/dist/net/protocol.js.map +0 -1
- package/dist/net/serverClock.js.map +0 -1
- package/dist/net/snapshotCodec.js.map +0 -1
- package/dist/parallax/Parallax.js.map +0 -1
- package/dist/parallax/ParallaxLayer.js.map +0 -1
- package/dist/parallax/parallaxMath.js.map +0 -1
- package/dist/particles/Emitter.js.map +0 -1
- package/dist/particles/ParticleBudget.js.map +0 -1
- package/dist/particles/ParticleSim.js.map +0 -1
- package/dist/particles/particleCurves.js.map +0 -1
- package/dist/particles/particleSampling.js.map +0 -1
- package/dist/physics/Body.js.map +0 -1
- package/dist/physics/BroadPhase.js.map +0 -1
- package/dist/physics/Contact.js.map +0 -1
- package/dist/physics/ContactSolver.js.map +0 -1
- package/dist/physics/PhysicsWorld.js.map +0 -1
- package/dist/physics/SpatialHash.js.map +0 -1
- package/dist/physics/collide.js.map +0 -1
- package/dist/physics/collidePolygon.js.map +0 -1
- package/dist/physics/contactReport.js.map +0 -1
- package/dist/physics/drawColliders.js.map +0 -1
- package/dist/physics/physicsSystem.js.map +0 -1
- package/dist/physics/polygon.js.map +0 -1
- package/dist/physics/raycast.js.map +0 -1
- package/dist/physics/syncToBody.js.map +0 -1
- package/dist/physics/tileBodies.js.map +0 -1
- package/dist/physics/types.js.map +0 -1
- package/dist/pixel/Darkness.js.map +0 -1
- package/dist/pixel/DitherVignette.js.map +0 -1
- package/dist/pixel/Evaporation.js.map +0 -1
- package/dist/pixel/NightShade.js.map +0 -1
- package/dist/pixel/PixelLayer.js.map +0 -1
- package/dist/pixel/SwayProp.js.map +0 -1
- package/dist/pixel/lights.js.map +0 -1
- package/dist/pixel/nineSlice.js.map +0 -1
- package/dist/pixel/paintLayer.js.map +0 -1
- package/dist/pixel/palette.js.map +0 -1
- package/dist/pixel/pixel.js.map +0 -1
- package/dist/pixel/pixelPaint.js.map +0 -1
- package/dist/pixel/pixelTone.js.map +0 -1
- package/dist/pixel/propKit.js.map +0 -1
- package/dist/pixel/ramp.js.map +0 -1
- package/dist/pixel/selout.js.map +0 -1
- package/dist/pixel/spritePixels.js.map +0 -1
- package/dist/pixel/swayRig.js.map +0 -1
- package/dist/pixel/vignetteMask.js.map +0 -1
- package/dist/pixel/weatherArt.js.map +0 -1
- package/dist/pixel.js.map +0 -1
- package/dist/platform/touch.js.map +0 -1
- package/dist/postfx/BloomFilter.js.map +0 -1
- package/dist/postfx/ChromaticAberrationFilter.js.map +0 -1
- package/dist/postfx/ColorAdjustFilter.js.map +0 -1
- package/dist/postfx/CrtFilter.js.map +0 -1
- package/dist/postfx/PixelateFilter.js.map +0 -1
- package/dist/postfx/PostFx.js.map +0 -1
- package/dist/postfx/ShockwaveFilter.js.map +0 -1
- package/dist/postfx/VignetteFilter.js.map +0 -1
- package/dist/postfx/fxAnimator.js.map +0 -1
- package/dist/postfx/fxShader.js.map +0 -1
- package/dist/postfx.js.map +0 -1
- package/dist/react/ChoiceDialog.js.map +0 -1
- package/dist/react/ConfirmDialog.js.map +0 -1
- package/dist/react/Glyph.js.map +0 -1
- package/dist/react/Modal.js.map +0 -1
- package/dist/react/Panel.js.map +0 -1
- package/dist/react/PixelImage.js.map +0 -1
- package/dist/react/Select.js.map +0 -1
- package/dist/react/Slider.js.map +0 -1
- package/dist/react/SplitPane.js.map +0 -1
- package/dist/react/TabStrip.js.map +0 -1
- package/dist/react/TitleTips.js.map +0 -1
- package/dist/react/Tooltip.js.map +0 -1
- package/dist/react/UiProvider.js.map +0 -1
- package/dist/react/baseCss.js.map +0 -1
- package/dist/react/choiceStore.js.map +0 -1
- package/dist/react/classNames.js.map +0 -1
- package/dist/react/confirmStore.js.map +0 -1
- package/dist/react/labels.js.map +0 -1
- package/dist/react/placement.js.map +0 -1
- package/dist/react/useFillSheet.js.map +0 -1
- package/dist/react/useFitToViewport.js.map +0 -1
- package/dist/react/usePicked.js.map +0 -1
- package/dist/react/useStore.js.map +0 -1
- package/dist/react/useViewport.js.map +0 -1
- package/dist/react.js.map +0 -1
- package/dist/rig/PosePlayer.js.map +0 -1
- package/dist/rig/RigView.js.map +0 -1
- package/dist/rig/Skeleton.js.map +0 -1
- package/dist/rig/humanoid.js.map +0 -1
- package/dist/rig/humanoid.json.js.map +0 -1
- package/dist/rig/humanoidPoses.js.map +0 -1
- package/dist/rig/poseMath.js.map +0 -1
- package/dist/rig/poses.json.js.map +0 -1
- package/dist/save/Autosaver.js.map +0 -1
- package/dist/save/MemoryBackend.js.map +0 -1
- package/dist/save/SaveStore.js.map +0 -1
- package/dist/save/WebStorageBackend.js.map +0 -1
- package/dist/save/checksum.js.map +0 -1
- package/dist/save/migrateSave.js.map +0 -1
- package/dist/settings.js.map +0 -1
- package/dist/tilemap/TilemapView.js.map +0 -1
- package/dist/tilemap/collision.js.map +0 -1
- package/dist/tilemap/tiled.js.map +0 -1
- package/dist/transitions/CrossfadeTransition.js.map +0 -1
- package/dist/transitions/DitherWipeTransition.js.map +0 -1
- package/dist/transitions/FadeTransition.js.map +0 -1
- package/dist/transitions/Letterbox.js.map +0 -1
- package/dist/transitions/ditherMask.js.map +0 -1
- package/dist/transitions/transitionTo.js.map +0 -1
- package/dist/transitions/tweenDone.js.map +0 -1
- package/dist/ui/Bar.js.map +0 -1
- package/dist/ui/Button.js.map +0 -1
- package/dist/ui/Dialog.js.map +0 -1
- package/dist/ui/FocusManager.js.map +0 -1
- package/dist/ui/Label.js.map +0 -1
- package/dist/ui/List.js.map +0 -1
- package/dist/ui/Panel.js.map +0 -1
- package/dist/ui/ScrollView.js.map +0 -1
- package/dist/ui/Slider.js.map +0 -1
- package/dist/ui/Stack.js.map +0 -1
- package/dist/ui/Toggle.js.map +0 -1
- package/dist/ui/UiHost.js.map +0 -1
- package/dist/ui/UiPointerGate.js.map +0 -1
- package/dist/ui/UiRoot.js.map +0 -1
- package/dist/ui/Widget.js.map +0 -1
- package/dist/ui/dom/DomOverlay.js.map +0 -1
- package/dist/ui/dom/DomWorldAnchors.js.map +0 -1
- package/dist/ui/dom/InputShield.js.map +0 -1
- package/dist/ui/dom/createStore.js.map +0 -1
- package/dist/ui/dom/shieldDecision.js.map +0 -1
- package/dist/ui/dom/worldToScreen.js.map +0 -1
- package/dist/ui/layout/anchor.js.map +0 -1
- package/dist/ui/layout/barDrain.js.map +0 -1
- package/dist/ui/layout/fitToViewport.js.map +0 -1
- package/dist/ui/layout/focusNav.js.map +0 -1
- package/dist/ui/layout/geometry.js.map +0 -1
- package/dist/ui/layout/listWidth.js.map +0 -1
- package/dist/ui/layout/placeTooltip.js.map +0 -1
- package/dist/ui/layout/scroll.js.map +0 -1
- package/dist/ui/layout/slider.js.map +0 -1
- package/dist/ui/layout/stack.js.map +0 -1
- package/dist/ui/layout/wrapRow.js.map +0 -1
- package/dist/ui.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"animStateCompile.js","sources":["../../src/anim/animStateCompile.ts"],"sourcesContent":["import type { AnimationClip } from './Animator';\nimport type { AnimCondition, AnimParams, AnimStateMachineOptions, CompareOp } from './animStateTypes';\n\n/*\n * The state machine's definition turned once, at construction, into flat typed arrays, so an update walks numbers and\n * allocates nothing: parameter values by index, every transition's conditions in one run of arrays, and each state's\n * candidate transitions (its own and the any-state ones) already sorted by priority.\n */\n\nexport const PARAM_BOOL = 0;\nexport const PARAM_NUMBER = 1;\nexport const PARAM_TRIGGER = 2;\n\nexport const OP_EQ = 0;\nexport const OP_NE = 1;\nexport const OP_GT = 2;\nexport const OP_GE = 3;\nexport const OP_LT = 4;\nexport const OP_LE = 5;\n\nconst OPS: Record<CompareOp, number> = { '==': OP_EQ, '!=': OP_NE, '>': OP_GT, '>=': OP_GE, '<': OP_LT, '<=': OP_LE };\n\nexport interface CompiledParams {\n names: string[];\n index: Map<string, number>;\n kinds: Uint8Array;\n values: Float64Array;\n initial: Float64Array;\n}\n\nexport interface CompiledStates {\n names: string[];\n index: Map<string, number>;\n clips: string[];\n speeds: Float64Array;\n /** Index of the speed parameter, or -1. */\n speedParams: Int32Array;\n /** Total length of each state's clip in ms, at speed 1. */\n lengths: Float64Array;\n frameCounts: Int32Array;\n /** Per state, per clip frame: the event names fired there (undefined: none). */\n markers: (readonly (readonly string[] | undefined)[])[];\n /** Per state: transition indices to try, best first. */\n candidates: Int32Array[];\n}\n\nexport interface CompiledTransitions {\n to: Int32Array;\n anyState: Uint8Array;\n self: Uint8Array;\n onEnd: Uint8Array;\n /** -1: none. */\n exitTime: Float64Array;\n condStart: Int32Array;\n condEnd: Int32Array;\n condParam: Int32Array;\n condOp: Uint8Array;\n condValue: Float64Array;\n}\n\n/** The length of a clip in ms, frame by frame exactly as `Animator` times it. */\nexport function clipLength(clip: AnimationClip): number {\n let ms = 0;\n for (let i = 0; i < clip.frames.length; i++) ms += Math.max(1, clip.durations?.[i] ?? 1000 / (clip.fps ?? 10));\n return ms;\n}\n\nexport function compileParams(decl: AnimParams): CompiledParams {\n const names = Object.keys(decl);\n const kinds = new Uint8Array(names.length);\n const initial = new Float64Array(names.length);\n names.forEach((n, i) => {\n const v = decl[n];\n if (v === 'trigger') kinds[i] = PARAM_TRIGGER;\n else if (typeof v === 'boolean') {\n kinds[i] = PARAM_BOOL;\n initial[i] = v ? 1 : 0;\n } else if (typeof v === 'number' && Number.isFinite(v)) {\n kinds[i] = PARAM_NUMBER;\n initial[i] = v;\n } else throw new Error(`AnimStateMachine: parameter \"${n}\" must start as a boolean, a number or 'trigger'`);\n });\n return { names, index: new Map(names.map((n, i) => [n, i])), kinds, values: initial.slice(), initial };\n}\n\nexport function compileStates(\n options: AnimStateMachineOptions<string, string, AnimParams>,\n clips: Record<string, AnimationClip>,\n params: CompiledParams,\n): CompiledStates {\n const names = Object.keys(options.states);\n if (!names.includes(options.initial)) throw new Error(`AnimStateMachine: no state \"${options.initial}\"`);\n const n = names.length;\n const out: CompiledStates = {\n names,\n index: new Map(names.map((s, i) => [s, i])),\n clips: [],\n speeds: new Float64Array(n),\n speedParams: new Int32Array(n),\n lengths: new Float64Array(n),\n frameCounts: new Int32Array(n),\n markers: [],\n candidates: [],\n };\n names.forEach((s, i) => {\n const def = options.states[s]!;\n const clip = clips[def.clip];\n if (!clip) throw new Error(`AnimStateMachine: state \"${s}\" plays clip \"${def.clip}\", which does not exist`);\n if (clip.frames.length === 0) throw new Error(`AnimStateMachine: clip \"${def.clip}\" has no frames`);\n out.clips.push(def.clip);\n out.speeds[i] = def.speed ?? 1;\n out.speedParams[i] = def.speedParam === undefined ? -1 : paramIndex(params, def.speedParam, PARAM_NUMBER, `state \"${s}\"`);\n out.lengths[i] = clipLength(clip);\n out.frameCounts[i] = clip.frames.length;\n const byFrame: (string[] | undefined)[] = new Array(clip.frames.length).fill(undefined);\n for (const m of def.markers ?? []) {\n if (!(m.frame >= 0 && m.frame < clip.frames.length)) throw new Error(`AnimStateMachine: marker \"${m.name}\" of state \"${s}\" is past its clip`);\n (byFrame[m.frame] ??= []).push(m.name);\n }\n out.markers.push(byFrame);\n });\n return out;\n}\n\nexport function compileTransitions(\n options: AnimStateMachineOptions<string, string, AnimParams>,\n states: CompiledStates,\n params: CompiledParams,\n): CompiledTransitions {\n const list = options.transitions ?? [];\n const n = list.length;\n const condParam: number[] = [];\n const condOp: number[] = [];\n const condValue: number[] = [];\n const t: CompiledTransitions = {\n to: new Int32Array(n),\n anyState: new Uint8Array(n),\n self: new Uint8Array(n),\n onEnd: new Uint8Array(n),\n exitTime: new Float64Array(n),\n condStart: new Int32Array(n),\n condEnd: new Int32Array(n),\n condParam: new Int32Array(0),\n condOp: new Uint8Array(0),\n condValue: new Float64Array(0),\n };\n const sources: number[][] = states.names.map(() => []);\n const stateOf = (s: string, where: string) => {\n const i = states.index.get(s);\n if (i === undefined) throw new Error(`AnimStateMachine: ${where} names no state \"${s}\"`);\n return i;\n };\n list.forEach((tr, i) => {\n const where = `transition ${i} (→ ${tr.to})`;\n t.to[i] = stateOf(tr.to, where);\n t.anyState[i] = tr.from === '*' ? 1 : 0;\n t.self[i] = tr.self ? 1 : 0;\n t.onEnd[i] = tr.onEnd ? 1 : 0;\n t.exitTime[i] = tr.exitTime ?? -1;\n if (tr.from === '*') states.names.forEach((_s, si) => sources[si]!.push(i));\n else for (const s of typeof tr.from === 'string' ? [tr.from] : tr.from) sources[stateOf(s, where)]!.push(i);\n t.condStart[i] = condParam.length;\n for (const [name, cond] of Object.entries(tr.when ?? {}) as [string, AnimCondition][]) {\n const p = params.index.get(name);\n if (p === undefined) throw new Error(`AnimStateMachine: ${where} asks for parameter \"${name}\", which is not declared`);\n const [op, value] = conditionOf(params.kinds[p]!, cond, `${where}, parameter \"${name}\"`);\n condParam.push(p);\n condOp.push(op);\n condValue.push(value);\n }\n t.condEnd[i] = condParam.length;\n });\n t.condParam = Int32Array.from(condParam);\n t.condOp = Uint8Array.from(condOp);\n t.condValue = Float64Array.from(condValue);\n const priority = (i: number) => list[i]!.priority ?? 0;\n // a stable sort: equal priorities keep the order they were written in\n states.candidates = sources.map((ids) => Int32Array.from(ids.sort((a, b) => priority(b) - priority(a) || a - b)));\n return t;\n}\n\nfunction conditionOf(kind: number, cond: AnimCondition, where: string): [number, number] {\n if (Array.isArray(cond)) {\n const [op, value] = cond as readonly [CompareOp, number];\n if (kind !== PARAM_NUMBER) throw new Error(`AnimStateMachine: ${where} compares a parameter that is not a number`);\n if (!(op in OPS) || !Number.isFinite(value)) throw new Error(`AnimStateMachine: ${where} has a bad comparison`);\n return [OPS[op], value];\n }\n if (kind === PARAM_NUMBER) {\n if (typeof cond !== 'number') throw new Error(`AnimStateMachine: ${where} is a number; give a number or [op, number]`);\n return [OP_EQ, cond];\n }\n if (typeof cond !== 'boolean') throw new Error(`AnimStateMachine: ${where} is a ${kind === PARAM_BOOL ? 'bool' : 'trigger'}; give true or false`);\n return [OP_EQ, cond ? 1 : 0];\n}\n\nfunction paramIndex(params: CompiledParams, name: string, kind: number, where: string): number {\n const i = params.index.get(name);\n if (i === undefined || params.kinds[i] !== kind) throw new Error(`AnimStateMachine: ${where} names \"${name}\", which is not a number parameter`);\n return i;\n}\n"],"names":["PARAM_BOOL","PARAM_NUMBER","PARAM_TRIGGER","OP_EQ","OP_NE","OP_GT","OP_GE","OP_LT","OP_LE","OPS","clipLength","clip","ms","i","compileParams","decl","names","kinds","initial","n","v","compileStates","options","clips","params","out","s","def","paramIndex","byFrame","m","compileTransitions","states","list","condParam","condOp","condValue","t","sources","stateOf","where","tr","_s","si","name","cond","p","op","value","conditionOf","priority","ids","a","b","kind"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"animStateSystem.js","sources":["../../src/anim/animStateSystem.ts"],"sourcesContent":["import { defineComponent, system, type ComponentType, type System } from '../ecs/World';\nimport { SpriteComponent } from './animationSystem';\nimport type { AnimStateMachine } from './AnimStateMachine';\n\n/** An entity's animation state machine. Give an entity this instead of `AnimationComponent`: the machine owns its animator. */\nexport const AnimStateComponent: ComponentType<AnimStateMachine> = defineComponent<AnimStateMachine>('AnimState');\n\n/**\n * Updates every `AnimStateMachine` each frame and shows its frame on the entity's sprite (only when it changed).\n * Add it after the systems that set the machines' parameters, so a change shows the same frame.\n */\nexport function animStateSystem(name = 'anim-state'): System {\n return system(name, 'frame', ({ world, dt }) => {\n world.each(SpriteComponent, AnimStateComponent, (_e, sprite, sm) => {\n sm.update(dt);\n const frame = sm.frame;\n if (sprite.frameName !== String(frame)) sprite.setFrame(frame);\n });\n });\n}\n"],"names":["AnimStateComponent","defineComponent","animStateSystem","name","system","world","dt","SpriteComponent","_e","sprite","sm","frame"],"mappings":";;AAKO,MAAMA,IAAsDC,EAAkC,WAAW;AAMzG,SAASC,EAAgBC,IAAO,cAAsB;AAC3D,SAAOC,EAAOD,GAAM,SAAS,CAAC,EAAE,OAAAE,GAAO,IAAAC,QAAS;AAC9C,IAAAD,EAAM,KAAKE,GAAiBP,GAAoB,CAACQ,GAAIC,GAAQC,MAAO;AAClE,MAAAA,EAAG,OAAOJ,CAAE;AACZ,YAAMK,IAAQD,EAAG;AACjB,MAAID,EAAO,cAAc,OAAOE,CAAK,KAAGF,EAAO,SAASE,CAAK;AAAA,IAC/D,CAAC;AAAA,EACH,CAAC;AACH;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"animationSystem.js","sources":["../../src/anim/animationSystem.ts"],"sourcesContent":["import type { Image } from '../core/Image';\nimport { defineComponent, system, type ComponentType, type System } from '../ecs/World';\nimport type { Animator } from './Animator';\n\n/** The engine's sprite component: the `Image` an entity is drawn as. */\nexport const SpriteComponent: ComponentType<Image> = defineComponent<Image>('Sprite');\n\n/** The engine's animation component: the `Animator` choosing the sprite's frame. */\nexport const AnimationComponent: ComponentType<Animator> = defineComponent<Animator>('Animation');\n\n/**\n * Advances every `Animator` each frame and shows its frame on the entity's sprite (only when it changed, so an idle\n * crowd costs no texture swaps).\n */\nexport function animationSystem(name = 'animation'): System {\n return system(name, 'frame', ({ world, dt }) => {\n world.each(SpriteComponent, AnimationComponent, (_e, sprite, anim) => {\n const changed = anim.update(dt);\n if (changed || sprite.frameName !== String(anim.frame)) sprite.setFrame(anim.frame);\n });\n });\n}\n"],"names":["SpriteComponent","defineComponent","AnimationComponent","animationSystem","name","system","world","dt","_e","sprite","anim"],"mappings":";AAKO,MAAMA,IAAwCC,EAAuB,QAAQ,GAGvEC,IAA8CD,EAA0B,WAAW;AAMzF,SAASE,EAAgBC,IAAO,aAAqB;AAC1D,SAAOC,EAAOD,GAAM,SAAS,CAAC,EAAE,OAAAE,GAAO,IAAAC,QAAS;AAC9C,IAAAD,EAAM,KAAKN,GAAiBE,GAAoB,CAACM,GAAIC,GAAQC,MAAS;AAEpE,OADgBA,EAAK,OAAOH,CAAE,KACfE,EAAO,cAAc,OAAOC,EAAK,KAAK,MAAGD,EAAO,SAASC,EAAK,KAAK;AAAA,IACpF,CAAC;AAAA,EACH,CAAC;AACH;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"asepriteSlices.js","sources":["../../src/anim/asepriteSlices.ts"],"sourcesContent":["import type { AsepriteSheet, AsepriteSlice, AsepriteSliceKey } from './asepriteTypes';\n\n/** A slice by name, or undefined. */\nexport function sliceNamed(sheet: AsepriteSheet, name: string): AsepriteSlice | undefined {\n return sheet.slices.find((s) => s.name === name);\n}\n\n/**\n * The key of `slice` in effect on sprite frame `frame`: Aseprite keys a slice on the frame it changes and holds it\n * until the next key, so the last key at or before the frame. Before the first key the slice is not there (undefined).\n */\nexport function sliceAt(slice: AsepriteSlice, frame = 0): AsepriteSliceKey | undefined {\n let found: AsepriteSliceKey | undefined;\n for (const k of slice.keys) {\n if (k.frame > frame) break;\n found = k;\n }\n return found;\n}\n\n/**\n * The 9-slice borders of a key with a centre — how far in from each edge the stretchable middle starts — in the shape\n * Pixi's `NineSliceSprite` and `Texture.defaultBorders` take. Undefined for a key with no centre.\n */\nexport function sliceBorders(key: AsepriteSliceKey): { left: number; top: number; right: number; bottom: number } | undefined {\n const c = key.center;\n if (!c) return undefined;\n return { left: c.x, top: c.y, right: key.bounds.w - c.x - c.w, bottom: key.bounds.h - c.y - c.h };\n}\n"],"names":["sliceNamed","sheet","name","s","sliceAt","slice","frame","found","k","sliceBorders","key","c"],"mappings":"AAGO,SAASA,EAAWC,GAAsBC,GAAyC;AACxF,SAAOD,EAAM,OAAO,KAAK,CAACE,MAAMA,EAAE,SAASD,CAAI;AACjD;AAMO,SAASE,EAAQC,GAAsBC,IAAQ,GAAiC;AACrF,MAAIC;AACJ,aAAWC,KAAKH,EAAM,MAAM;AAC1B,QAAIG,EAAE,QAAQF,EAAO;AACrB,IAAAC,IAAQC;AAAA,EACV;AACA,SAAOD;AACT;AAMO,SAASE,EAAaC,GAAiG;AAC5H,QAAMC,IAAID,EAAI;AACd,MAAKC;AACL,WAAO,EAAE,MAAMA,EAAE,GAAG,KAAKA,EAAE,GAAG,OAAOD,EAAI,OAAO,IAAIC,EAAE,IAAIA,EAAE,GAAG,QAAQD,EAAI,OAAO,IAAIC,EAAE,IAAIA,EAAE,EAAA;AAChG;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"asepriteTags.js","sources":["../../src/anim/asepriteTags.ts"],"sourcesContent":["import type { AsepriteDirection } from './asepriteTypes';\n\nconst DIRECTIONS: readonly AsepriteDirection[] = ['forward', 'reverse', 'pingpong', 'pingpong_reverse'];\n\n/** Aseprite's direction name, or forward for anything unknown (older files write none). */\nexport function tagDirection(raw: string | undefined): AsepriteDirection {\n return DIRECTIONS.includes(raw as AsepriteDirection) ? (raw as AsepriteDirection) : 'forward';\n}\n\n/** Aseprite writes the repeat count as a string; absent, 0 or nonsense is forever. */\nexport function tagRepeat(raw: string | number | undefined): number {\n const n = typeof raw === 'number' ? raw : raw === undefined ? 0 : Number.parseInt(raw, 10);\n return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;\n}\n\n/**\n * The sheet frames a tag plays, in playing order, unrolled so the engine's `Animator` (which only ever plays a list\n * forward) shows exactly what Aseprite's own player shows.\n *\n * - forever (`repeat` 0): forward and reverse are one pass, looped; a ping-pong is one round trip without its two\n * ends doubled (a b c d c b, then a again), so a loop never stutters on the turn.\n * - `repeat` n: n passes, played once. A ping-pong counts each pass in one direction as one, as Aseprite does, and two\n * passes share their turning frame (repeat 2 of a b c is a b c b a).\n */\nexport function tagFrames(from: number, to: number, direction: AsepriteDirection, repeat: number): number[] {\n const lo = Math.min(from, to);\n const hi = Math.max(from, to);\n const up: number[] = [];\n for (let i = lo; i <= hi; i++) up.push(i);\n const down = up.slice().reverse();\n const backward = direction === 'reverse' || direction === 'pingpong_reverse';\n const first = backward ? down : up;\n const pingpong = direction === 'pingpong' || direction === 'pingpong_reverse';\n\n if (repeat === 0) {\n if (!pingpong || up.length <= 2) return first;\n const second = backward ? up : down;\n return first.concat(second.slice(1, -1));\n }\n if (!pingpong) {\n const out: number[] = [];\n for (let r = 0; r < repeat; r++) out.push(...first);\n return out;\n }\n const out = first.slice();\n for (let r = 1; r < repeat; r++) {\n const pass = r % 2 === 0 ? first : backward ? up : down;\n // the turning frame closes one pass and opens the next: shown once, as Aseprite shows it\n out.push(...(up.length > 1 ? pass.slice(1) : pass));\n }\n return out;\n}\n"],"names":["DIRECTIONS","tagDirection","raw","tagRepeat","n","tagFrames","from","to","direction","repeat","lo","hi","up","i","down","backward","first","pingpong","second","out","r","pass"],"mappings":"AAEA,MAAMA,IAA2C,CAAC,WAAW,WAAW,YAAY,kBAAkB;AAG/F,SAASC,EAAaC,GAA4C;AACvE,SAAOF,EAAW,SAASE,CAAwB,IAAKA,IAA4B;AACtF;AAGO,SAASC,EAAUD,GAA0C;AAClE,QAAME,IAAI,OAAOF,KAAQ,WAAWA,IAAMA,MAAQ,SAAY,IAAI,OAAO,SAASA,GAAK,EAAE;AACzF,SAAO,OAAO,SAASE,CAAC,KAAKA,IAAI,IAAI,KAAK,MAAMA,CAAC,IAAI;AACvD;AAWO,SAASC,EAAUC,GAAcC,GAAYC,GAA8BC,GAA0B;AAC1G,QAAMC,IAAK,KAAK,IAAIJ,GAAMC,CAAE,GACtBI,IAAK,KAAK,IAAIL,GAAMC,CAAE,GACtBK,IAAe,CAAA;AACrB,WAASC,IAAIH,GAAIG,KAAKF,GAAIE,IAAK,CAAAD,EAAG,KAAKC,CAAC;AACxC,QAAMC,IAAOF,EAAG,MAAA,EAAQ,QAAA,GAClBG,IAAWP,MAAc,aAAaA,MAAc,oBACpDQ,IAAQD,IAAWD,IAAOF,GAC1BK,IAAWT,MAAc,cAAcA,MAAc;AAE3D,MAAIC,MAAW,GAAG;AAChB,QAAI,CAACQ,KAAYL,EAAG,UAAU,EAAG,QAAOI;AACxC,UAAME,IAASH,IAAWH,IAAKE;AAC/B,WAAOE,EAAM,OAAOE,EAAO,MAAM,GAAG,EAAE,CAAC;AAAA,EACzC;AACA,MAAI,CAACD,GAAU;AACb,UAAME,IAAgB,CAAA;AACtB,aAAS,IAAI,GAAG,IAAIV,GAAQ,IAAKU,CAAAA,EAAI,KAAK,GAAGH,CAAK;AAClD,WAAOG;AAAAA,EACT;AACA,QAAMA,IAAMH,EAAM,MAAA;AAClB,WAASI,IAAI,GAAGA,IAAIX,GAAQW,KAAK;AAC/B,UAAMC,IAAOD,IAAI,MAAM,IAAIJ,IAAQD,IAAWH,IAAKE;AAEnD,IAAAK,EAAI,KAAK,GAAIP,EAAG,SAAS,IAAIS,EAAK,MAAM,CAAC,IAAIA,CAAK;AAAA,EACpD;AACA,SAAOF;AACT;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parseAseprite.js","sources":["../../src/anim/parseAseprite.ts"],"sourcesContent":["import type { AnimationClip } from './Animator';\nimport { tagDirection, tagFrames, tagRepeat } from './asepriteTags';\nimport type {\n AsepriteCue,\n AsepriteFrame,\n AsepriteJson,\n AsepriteJsonFrame,\n AsepriteLayer,\n AsepriteSheet,\n AsepriteSlice,\n AsepriteTag,\n ClipMarker,\n} from './asepriteTypes';\n\n// Aseprite's default when a frame has no duration of its own (its \"Frame Duration\" default)\nconst DEFAULT_DURATION_MS = 100;\n\n/**\n * Reads Aseprite's exported sprite-sheet JSON — either frame layout, \"hash\" (frames keyed by file name) or \"array\" —\n * into frames, one `Animator` clip per tag, slices, layers and cel cues. Pure: no image is touched, so it runs under\n * node and in a worker; `addAsepriteTextures` / `loadAseprite` cut the sheet image by the frames it returns.\n *\n * ```ts\n * const sheet = parseAseprite(json);\n * const anim = new Animator(sheet.clips, 'idle'); // tag names are clip names; frames are sheet frame indices\n * ```\n */\nexport function parseAseprite(json: unknown): AsepriteSheet {\n if (!json || typeof json !== 'object') throw new Error('parseAseprite: not an Aseprite JSON object');\n const doc = json as AsepriteJson;\n if (!doc.frames || typeof doc.frames !== 'object') throw new Error('parseAseprite: no \"frames\"');\n const meta = doc.meta ?? {};\n\n const rawFrames: [string, AsepriteJsonFrame][] = Array.isArray(doc.frames)\n ? doc.frames.map((f, i) => [f.filename ?? String(i), f])\n : Object.entries(doc.frames);\n const frames = rawFrames.map(([name, f], index) => toFrame(name, f, index));\n if (frames.length === 0) throw new Error('parseAseprite: the sheet has no frames');\n\n const tags: AsepriteTag[] = (meta.frameTags ?? []).map((t) => {\n if (!(t.from >= 0 && t.to < frames.length && t.from <= t.to)) {\n throw new Error(`parseAseprite: tag \"${t.name}\" spans frames ${t.from}–${t.to} of ${frames.length}`);\n }\n const tag: AsepriteTag = { name: t.name, from: t.from, to: t.to, direction: tagDirection(t.direction), repeat: tagRepeat(t.repeat) };\n if (t.color !== undefined) tag.color = t.color;\n if (t.data !== undefined) tag.data = t.data;\n return tag;\n });\n\n const clips: Record<string, AnimationClip> = {};\n for (const tag of tags) {\n // Aseprite allows two tags of one name; the second must not silently replace the first\n let name = tag.name;\n for (let n = 2; name in clips; n++) name = `${tag.name}#${n}`;\n clips[name] = clipOf(frames, tagFrames(tag.from, tag.to, tag.direction, tag.repeat), tag.repeat === 0);\n }\n\n const all = clipOf(frames, frames.map((f) => f.index), true);\n const slices: AsepriteSlice[] = (meta.slices ?? []).map((s) => {\n const slice: AsepriteSlice = {\n name: s.name,\n keys: (s.keys ?? [])\n .map((k) => ({\n frame: k.frame,\n bounds: { ...k.bounds },\n ...(k.center ? { center: { ...k.center } } : {}),\n ...(k.pivot ? { pivot: { ...k.pivot } } : {}),\n }))\n .sort((a, b) => a.frame - b.frame),\n };\n if (s.color !== undefined) slice.color = s.color;\n if (s.data !== undefined) slice.data = s.data;\n return slice;\n });\n\n const layers: AsepriteLayer[] = [];\n const cues: AsepriteCue[] = [];\n for (const l of meta.layers ?? []) {\n const layer: AsepriteLayer = { name: l.name, opacity: l.opacity ?? 255, blendMode: l.blendMode ?? 'normal' };\n if (l.group !== undefined) layer.group = l.group;\n if (l.color !== undefined) layer.color = l.color;\n if (l.data !== undefined) layer.data = l.data;\n layers.push(layer);\n for (const c of l.cels ?? []) if (c.data) cues.push({ layer: l.name, frame: c.frame, data: c.data });\n }\n\n const size = meta.size ?? { w: 0, h: 0 };\n return { image: meta.image ?? '', width: size.w, height: size.h, frames, tags, clips, all, slices, layers, cues };\n}\n\nfunction toFrame(name: string, f: AsepriteJsonFrame, index: number): AsepriteFrame {\n if (!f || !f.frame) throw new Error(`parseAseprite: frame \"${name}\" has no rect`);\n // Aseprite never rotates frames; a packer that did would need its rect read sideways, which nothing here does\n if (f.rotated) throw new Error(`parseAseprite: frame \"${name}\" is rotated, which is not supported`);\n const { x, y, w, h } = f.frame;\n const src = f.sourceSize ?? { w, h };\n const off = f.trimmed && f.spriteSourceSize ? f.spriteSourceSize : { x: 0, y: 0 };\n return {\n index,\n name,\n rect: { x, y, w, h },\n duration: f.duration !== undefined && f.duration > 0 ? f.duration : DEFAULT_DURATION_MS,\n sourceW: src.w,\n sourceH: src.h,\n offsetX: off.x,\n offsetY: off.y,\n trimmed: f.trimmed === true,\n };\n}\n\nfunction clipOf(frames: readonly AsepriteFrame[], order: readonly number[], loop: boolean): AnimationClip {\n return { frames: order.slice(), durations: order.map((i) => frames[i]!.duration), loop };\n}\n\n/** The index of the frame Aseprite named `name`, or -1. */\nexport function frameNamed(sheet: AsepriteSheet, name: string): number {\n return sheet.frames.findIndex((f) => f.name === name);\n}\n\n/**\n * Markers for every clip from the sheet's cel cues: wherever a clip shows a sheet frame whose cel carries user data,\n * a marker of that data at that position of the clip. A ping-pong shows a frame twice a round trip, so it fires twice,\n * as a footstep should. `layer` keeps only the cues of one layer (a layer kept for marks, say \"events\").\n */\nexport function clipMarkers(sheet: AsepriteSheet, layer?: string): Record<string, ClipMarker[]> {\n const byFrame = new Map<number, string[]>();\n for (const c of sheet.cues) {\n if (layer !== undefined && c.layer !== layer) continue;\n const list = byFrame.get(c.frame) ?? [];\n list.push(c.data);\n byFrame.set(c.frame, list);\n }\n const out: Record<string, ClipMarker[]> = {};\n for (const [name, clip] of Object.entries(sheet.clips)) {\n const markers: ClipMarker[] = [];\n clip.frames.forEach((f, i) => {\n for (const data of byFrame.get(Number(f)) ?? []) markers.push({ frame: i, name: data });\n });\n if (markers.length) out[name] = markers;\n }\n return out;\n}\n"],"names":["DEFAULT_DURATION_MS","parseAseprite","json","doc","meta","frames","f","i","name","index","toFrame","tags","t","tag","tagDirection","tagRepeat","clips","n","clipOf","tagFrames","all","slices","s","slice","k","a","b","layers","cues","l","layer","c","size","x","y","w","h","src","off","order","loop","frameNamed","sheet","clipMarkers","byFrame","list","out","clip","markers","data"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AssetLoader.js","sources":["../../src/assets/AssetLoader.ts"],"sourcesContent":["import { parseTiled, type TileMap, type Tileset } from '../tilemap/tiled';\n\n/** Where loaded images go: the stage's `TextureRegistry` satisfies it. */\nexport interface TextureSink {\n addCanvas(key: string, canvas: HTMLCanvasElement): unknown;\n addSpriteSheet(key: string, canvas: HTMLCanvasElement, size: { frameWidth: number; frameHeight: number }): unknown;\n}\n\nexport interface AssetLoaderOptions {\n /** Prefix for relative URLs (default: none, so relative to the page). */\n baseUrl?: string;\n /** Requests running at once (default 6). */\n concurrency?: number;\n /** For tests and custom transports: defaults to the global `fetch`. */\n fetch?: (url: string) => Promise<{ ok: boolean; status: number; blob(): Promise<Blob>; json(): Promise<unknown>; text(): Promise<string> }>;\n /** Turns image bytes into a canvas; defaults to `createImageBitmap` drawn onto a new canvas. */\n toCanvas?: (blob: Blob) => Promise<HTMLCanvasElement>;\n}\n\nexport interface TilemapAssetOptions {\n /** The texture key a tileset's image is loaded under (default: the tileset's name). */\n textureKey?: (tileset: Tileset) => string;\n}\n\nexport interface LoadProgress {\n done: number;\n total: number;\n /** The asset that just finished. */\n key: string;\n}\n\ntype Job = { key: string; run: () => Promise<void> };\n\n/** `url` resolved against `base` the way a browser would (a bare file name sits beside its map). */\nexport function resolveUrl(url: string, base: string): string {\n if (/^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith('/')) return url;\n const dir = base.slice(0, base.lastIndexOf('/') + 1);\n const parts = (dir + url).split('/');\n const out: string[] = [];\n for (const p of parts) {\n if (p === '..' && out.length && out[out.length - 1] !== '..' && out[out.length - 1] !== '') out.pop();\n else if (p !== '.') out.push(p);\n }\n return out.join('/');\n}\n\nasync function defaultToCanvas(blob: Blob): Promise<HTMLCanvasElement> {\n const bitmap = await createImageBitmap(blob);\n const canvas = document.createElement('canvas');\n canvas.width = bitmap.width;\n canvas.height = bitmap.height;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('AssetLoader: no 2D context (out of canvas memory?)');\n ctx.drawImage(bitmap, 0, 0);\n bitmap.close();\n return canvas;\n}\n\n/**\n * Loads a scene's assets before it starts: queue them, then `await load()`. Images become textures in the stage's\n * registry (frames of a sprite sheet named 0, 1, 2…); JSON, text and Tiled maps are kept here by key. A failed asset\n * fails the whole load with its URL in the message.\n *\n * ```ts\n * override async onEnter() {\n * const assets = new AssetLoader(this.engine.stage.textures);\n * assets.spritesheet('hero', 'art/hero.png', { frameWidth: 20, frameHeight: 24 }).tilemap('level', 'maps/level.tmj');\n * await assets.load(({ done, total }) => bar.set(done / total));\n * const map = assets.map('level');\n * }\n * ```\n */\nexport class AssetLoader {\n private readonly queue: Job[] = [];\n private readonly data = new Map<string, unknown>();\n private readonly maps = new Map<string, TileMap>();\n private readonly fetcher: NonNullable<AssetLoaderOptions['fetch']>;\n private readonly toCanvas: (blob: Blob) => Promise<HTMLCanvasElement>;\n private total = 0;\n private done = 0;\n\n constructor(\n private readonly textures: TextureSink,\n private readonly options: AssetLoaderOptions = {},\n ) {\n this.fetcher = options.fetch ?? ((url) => fetch(url));\n this.toCanvas = options.toCanvas ?? defaultToCanvas;\n }\n\n image(key: string, url: string): this {\n return this.add(key, async () => {\n this.textures.addCanvas(key, await this.toCanvas(await this.blob(url)));\n });\n }\n\n /** An image cut into equal frames named 0, 1, 2…, left to right, then row by row. */\n spritesheet(key: string, url: string, size: { frameWidth: number; frameHeight: number }): this {\n return this.add(key, async () => {\n this.textures.addSpriteSheet(key, await this.toCanvas(await this.blob(url)), size);\n });\n }\n\n json(key: string, url: string): this {\n return this.add(key, async () => {\n this.data.set(key, await (await this.request(url)).json());\n });\n }\n\n text(key: string, url: string): this {\n return this.add(key, async () => {\n this.data.set(key, await (await this.request(url)).text());\n });\n }\n\n /** A Tiled JSON map, then every tileset image it names (resolved beside the map file). */\n tilemap(key: string, url: string, options: TilemapAssetOptions = {}): this {\n return this.add(key, async () => {\n const map = parseTiled(await (await this.request(url)).json());\n this.maps.set(key, map);\n const textureKey = options.textureKey ?? ((ts: Tileset) => ts.name);\n for (const ts of map.tilesets) this.image(textureKey(ts), resolveUrl(ts.image, url));\n });\n }\n\n /** Runs everything queued; `onProgress` after each asset. Safe to call again after queueing more. */\n async load(onProgress?: (p: LoadProgress) => void): Promise<void> {\n const limit = Math.max(1, this.options.concurrency ?? 6);\n const worker = async (): Promise<void> => {\n for (let job = this.queue.shift(); job; job = this.queue.shift()) {\n await job.run();\n this.done++;\n onProgress?.({ done: this.done, total: this.total, key: job.key });\n }\n };\n // a finished tilemap queues its tilesets: keep draining until nothing is left\n while (this.queue.length) await Promise.all(Array.from({ length: limit }, worker));\n }\n\n /** A loaded JSON value. */\n getJson<T = unknown>(key: string): T {\n if (!this.data.has(key)) throw new Error(`AssetLoader: no JSON or text loaded as \"${key}\"`);\n return this.data.get(key) as T;\n }\n\n /** A loaded text file. */\n getText(key: string): string {\n return this.getJson<string>(key);\n }\n\n /** A loaded Tiled map. */\n map(key: string): TileMap {\n const m = this.maps.get(key);\n if (!m) throw new Error(`AssetLoader: no tilemap loaded as \"${key}\"`);\n return m;\n }\n\n private add(key: string, run: () => Promise<void>): this {\n this.queue.push({ key, run });\n this.total++;\n return this;\n }\n\n private async request(url: string) {\n const full = resolveUrl(url, this.options.baseUrl ?? '');\n let res;\n try {\n res = await this.fetcher(full);\n } catch (err) {\n throw new Error(`AssetLoader: could not fetch ${full}: ${err instanceof Error ? err.message : String(err)}`);\n }\n if (!res.ok) throw new Error(`AssetLoader: ${full} answered ${res.status}`);\n return res;\n }\n\n private async blob(url: string): Promise<Blob> {\n return (await this.request(url)).blob();\n }\n}\n"],"names":["resolveUrl","url","base","parts","out","p","defaultToCanvas","blob","bitmap","canvas","ctx","AssetLoader","textures","options","key","size","map","parseTiled","textureKey","ts","onProgress","limit","worker","job","m","run","full","res","err"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loadAseprite.js","sources":["../../src/assets/loadAseprite.ts"],"sourcesContent":["import { Rectangle, Texture } from 'pixi.js';\nimport { parseAseprite } from '../anim/parseAseprite';\nimport type { AsepriteSheet } from '../anim/asepriteTypes';\nimport type { CanvasTexture } from '../core/TextureRegistry';\nimport { resolveUrl, type AssetLoaderOptions } from './AssetLoader';\n\n/** Where an Aseprite sheet's texture goes: the stage's `TextureRegistry` satisfies it. */\nexport interface AsepriteTextureSink {\n addCanvas(key: string, canvas: HTMLCanvasElement): CanvasTexture;\n}\n\n/** Fetch and decode hooks, as `AssetLoader` takes them (tests, custom transports, a base URL). */\nexport type AsepriteLoadOptions = Pick<AssetLoaderOptions, 'baseUrl' | 'fetch' | 'toCanvas'>;\n\n/**\n * Registers `canvas` (the sheet image) under `key` with one frame per sheet frame, named by its index (\"0\", \"1\"…), so\n * the clips `parseAseprite` returns play on an `Image` of that key as they are. A trimmed frame keeps its untrimmed\n * size and offset (Pixi's `orig` and `trim`), so a sprite anchored at its feet does not jump as the trim changes.\n */\nexport function addAsepriteTextures(textures: AsepriteTextureSink, key: string, canvas: HTMLCanvasElement, sheet: AsepriteSheet): CanvasTexture {\n const tex = textures.addCanvas(key, canvas);\n for (const f of sheet.frames) {\n const { x, y, w, h } = f.rect;\n if (!f.trimmed) {\n tex.add(String(f.index), 0, x, y, w, h);\n continue;\n }\n tex.frames.get(String(f.index))?.destroy();\n tex.frames.set(\n String(f.index),\n new Texture({\n source: tex.source,\n frame: new Rectangle(x, y, w, h),\n orig: new Rectangle(0, 0, f.sourceW, f.sourceH),\n trim: new Rectangle(f.offsetX, f.offsetY, w, h),\n }),\n );\n }\n return tex;\n}\n\n/**\n * Loads an Aseprite export — the JSON, then the sheet image it names (resolved beside the JSON) — registers its\n * frames under `key` and returns the parsed sheet (clips, slices, layers, cues).\n *\n * ```ts\n * const [hero] = await Promise.all([loadAseprite(stage.textures, 'hero', 'art/hero.json'), assets.load()]);\n * const sprite = stage.add.image(0, 0, 'hero', 0);\n * const anim = new Animator(hero.clips, 'idle');\n * ```\n */\nexport async function loadAseprite(textures: AsepriteTextureSink, key: string, jsonUrl: string, options: AsepriteLoadOptions = {}): Promise<AsepriteSheet> {\n const fetcher = options.fetch ?? ((url: string) => fetch(url));\n const get = async (url: string) => {\n let res;\n try {\n res = await fetcher(url);\n } catch (err) {\n throw new Error(`loadAseprite: could not fetch ${url}: ${err instanceof Error ? err.message : String(err)}`);\n }\n if (!res.ok) throw new Error(`loadAseprite: ${url} answered ${res.status}`);\n return res;\n };\n const jsonFull = resolveUrl(jsonUrl, options.baseUrl ?? '');\n const sheet = parseAseprite(await (await get(jsonFull)).json());\n if (!sheet.image) throw new Error(`loadAseprite: ${jsonFull} names no sheet image (meta.image)`);\n const blob = await (await get(resolveUrl(sheet.image, jsonFull))).blob();\n const canvas = await (options.toCanvas ?? blobToCanvas)(blob);\n addAsepriteTextures(textures, key, canvas, sheet);\n return sheet;\n}\n\nasync function blobToCanvas(blob: Blob): Promise<HTMLCanvasElement> {\n const bitmap = await createImageBitmap(blob);\n const canvas = document.createElement('canvas');\n canvas.width = bitmap.width;\n canvas.height = bitmap.height;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('loadAseprite: no 2D context (out of canvas memory?)');\n ctx.drawImage(bitmap, 0, 0);\n bitmap.close();\n return canvas;\n}\n"],"names":["addAsepriteTextures","textures","key","canvas","sheet","tex","f","x","y","w","h","Texture","Rectangle","loadAseprite","jsonUrl","options","fetcher","url","get","res","err","jsonFull","resolveUrl","parseAseprite","blob","blobToCanvas","bitmap","ctx"],"mappings":";;;AAmBO,SAASA,EAAoBC,GAA+BC,GAAaC,GAA2BC,GAAqC;AAC9I,QAAMC,IAAMJ,EAAS,UAAUC,GAAKC,CAAM;AAC1C,aAAWG,KAAKF,EAAM,QAAQ;AAC5B,UAAM,EAAE,GAAAG,GAAG,GAAAC,GAAG,GAAAC,GAAG,GAAAC,EAAA,IAAMJ,EAAE;AACzB,QAAI,CAACA,EAAE,SAAS;AACd,MAAAD,EAAI,IAAI,OAAOC,EAAE,KAAK,GAAG,GAAGC,GAAGC,GAAGC,GAAGC,CAAC;AACtC;AAAA,IACF;AACA,IAAAL,EAAI,OAAO,IAAI,OAAOC,EAAE,KAAK,CAAC,GAAG,QAAA,GACjCD,EAAI,OAAO;AAAA,MACT,OAAOC,EAAE,KAAK;AAAA,MACd,IAAIK,EAAQ;AAAA,QACV,QAAQN,EAAI;AAAA,QACZ,OAAO,IAAIO,EAAUL,GAAGC,GAAGC,GAAGC,CAAC;AAAA,QAC/B,MAAM,IAAIE,EAAU,GAAG,GAAGN,EAAE,SAASA,EAAE,OAAO;AAAA,QAC9C,MAAM,IAAIM,EAAUN,EAAE,SAASA,EAAE,SAASG,GAAGC,CAAC;AAAA,MAAA,CAC/C;AAAA,IAAA;AAAA,EAEL;AACA,SAAOL;AACT;AAYA,eAAsBQ,EAAaZ,GAA+BC,GAAaY,GAAiBC,IAA+B,CAAA,GAA4B;AACzJ,QAAMC,IAAUD,EAAQ,UAAU,CAACE,MAAgB,MAAMA,CAAG,IACtDC,IAAM,OAAOD,MAAgB;AACjC,QAAIE;AACJ,QAAI;AACF,MAAAA,IAAM,MAAMH,EAAQC,CAAG;AAAA,IACzB,SAASG,GAAK;AACZ,YAAM,IAAI,MAAM,iCAAiCH,CAAG,KAAKG,aAAe,QAAQA,EAAI,UAAU,OAAOA,CAAG,CAAC,EAAE;AAAA,IAC7G;AACA,QAAI,CAACD,EAAI,GAAI,OAAM,IAAI,MAAM,iBAAiBF,CAAG,aAAaE,EAAI,MAAM,EAAE;AAC1E,WAAOA;AAAA,EACT,GACME,IAAWC,EAAWR,GAASC,EAAQ,WAAW,EAAE,GACpDX,IAAQmB,EAAc,OAAO,MAAML,EAAIG,CAAQ,GAAG,MAAM;AAC9D,MAAI,CAACjB,EAAM,MAAO,OAAM,IAAI,MAAM,iBAAiBiB,CAAQ,oCAAoC;AAC/F,QAAMG,IAAO,OAAO,MAAMN,EAAII,EAAWlB,EAAM,OAAOiB,CAAQ,CAAC,GAAG,KAAA,GAC5DlB,IAAS,OAAOY,EAAQ,YAAYU,GAAcD,CAAI;AAC5D,SAAAxB,EAAoBC,GAAUC,GAAKC,GAAQC,CAAK,GACzCA;AACT;AAEA,eAAeqB,EAAaD,GAAwC;AAClE,QAAME,IAAS,MAAM,kBAAkBF,CAAI,GACrCrB,IAAS,SAAS,cAAc,QAAQ;AAC9C,EAAAA,EAAO,QAAQuB,EAAO,OACtBvB,EAAO,SAASuB,EAAO;AACvB,QAAMC,IAAMxB,EAAO,WAAW,IAAI;AAClC,MAAI,CAACwB,EAAK,OAAM,IAAI,MAAM,qDAAqD;AAC/E,SAAAA,EAAI,UAAUD,GAAQ,GAAG,CAAC,GAC1BA,EAAO,MAAA,GACAvB;AACT;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"takeCleanerWorker-DTcI_Jqr.js","sources":["../src/audio/cleanTake.ts","../src/audio/takeCleanerWorker.ts"],"sourcesContent":["/*\n * Cleans one recorded take (`SampleBank`): folded to mono, the silence an encoder or the model leaves at the start cut\n * (a swing heard 30 ms late reads as lag) and at the end trimmed, and brought to one loudness (`LOUDNESS`, RMS of what\n * is left, never past `PEAK`), so takes from different prompts sit at one level. Pure: run in `takeCleanerWorker.ts`,\n * because on the main thread the first blows of a fight froze the game 70–170 ms a take on a Mac and several times\n * that on a phone (profiled).\n */\n\n/** Below this share of the take's own peak a sample counts as silence when trimming (-40 dB). */\nconst FLOOR = 0.01;\n/** A take whose peak is under this is treated as empty (about -46 dBFS): the model returned near-silence. */\nconst EMPTY = 0.005;\n/** Target RMS of a cleaned take (-16 dBFS) and the peak it may not pass. */\nconst LOUDNESS = 0.16;\nconst PEAK = 0.95;\n/** Kept before the first loud sample, so the attack is not clipped into a click (s). */\nconst LEAD = 0.002;\n/** A short fade over the cut end, so a trimmed tail does not click (s). */\nconst TAIL_FADE = 0.01;\n\n/** The cleaned mono samples, or null for a take that is silence. */\nexport function cleanSamples(channels: Float32Array[], sampleRate: number): Float32Array | null {\n const n = channels[0]?.length ?? 0;\n const k0 = 1 / Math.max(1, channels.length);\n const mono = new Float32Array(n);\n for (const ch of channels) for (let i = 0; i < n; i++) mono[i]! += ch[i]! * k0;\n let top = 0;\n for (let i = 0; i < n; i++) {\n const a = Math.abs(mono[i]!);\n if (a > top) top = a;\n }\n if (top < EMPTY) return null;\n const floor = top * FLOOR;\n let start = 0;\n while (start < n && Math.abs(mono[start]!) < floor) start++;\n let end = n;\n while (end > start && Math.abs(mono[end - 1]!) < floor) end--;\n if (end <= start) return null;\n start = Math.max(0, start - Math.round(LEAD * sampleRate));\n const data = mono.slice(start, end);\n let power = 0;\n for (let i = 0; i < data.length; i++) power += data[i]! * data[i]!;\n const rms = Math.sqrt(power / data.length);\n const k = Math.min(LOUDNESS / Math.max(rms, 1e-6), PEAK / top);\n const fade = Math.min(data.length, Math.round(TAIL_FADE * sampleRate));\n for (let i = 0; i < data.length; i++) {\n const left = data.length - i;\n data[i]! *= k * (left < fade ? left / fade : 1);\n }\n return data;\n}\n","import { cleanSamples } from './cleanTake';\n\n/* Cleans recorded takes off the main thread (`SampleBank`); one message per take, answered with its id. */\nself.onmessage = (e: MessageEvent<{ id: number; channels: Float32Array[]; sampleRate: number }>) => {\n const { id, channels, sampleRate } = e.data;\n const data = cleanSamples(channels, sampleRate);\n self.postMessage({ id, data }, data ? { transfer: [data.buffer as ArrayBuffer] } : undefined);\n};\n"],"names":["cleanSamples","channels","sampleRate","n","k0","mono","ch","i","top","a","floor","start","end","data","power","rms","k","fade","left","e","id"],"mappings":"AAqBO,SAASA,EAAaC,GAA0BC,GAAyC;AAC9F,QAAMC,IAAIF,EAAS,CAAC,GAAG,UAAU,GAC3BG,IAAK,IAAI,KAAK,IAAI,GAAGH,EAAS,MAAM,GACpCI,IAAO,IAAI,aAAaF,CAAC;AAC/B,aAAWG,KAAML,EAAU,UAASM,IAAI,GAAGA,IAAIJ,GAAGI,IAAK,CAAAF,EAAKE,CAAC,KAAMD,EAAGC,CAAC,IAAKH;AAC5E,MAAII,IAAM;AACV,WAASD,IAAI,GAAGA,IAAIJ,GAAGI,KAAK;AAC1B,UAAME,IAAI,KAAK,IAAIJ,EAAKE,CAAC,CAAE;AAC3B,IAAIE,IAAID,MAAKA,IAAMC;AAAA,EACrB;AACA,MAAID,IAAM,KAAO,QAAO;AACxB,QAAME,IAAQF,IAAM;AACpB,MAAIG,IAAQ;AACZ,SAAOA,IAAQR,KAAK,KAAK,IAAIE,EAAKM,CAAK,CAAE,IAAID,IAAO,CAAAC;AACpD,MAAIC,IAAMT;AACV,SAAOS,IAAMD,KAAS,KAAK,IAAIN,EAAKO,IAAM,CAAC,CAAE,IAAIF,IAAO,CAAAE;AACxD,MAAIA,KAAOD,EAAO,QAAO;AACzB,EAAAA,IAAQ,KAAK,IAAI,GAAGA,IAAQ,KAAK,MAAM,OAAOT,CAAU,CAAC;AACzD,QAAMW,IAAOR,EAAK,MAAMM,GAAOC,CAAG;AAClC,MAAIE,IAAQ;AACZ,WAASP,IAAI,GAAGA,IAAIM,EAAK,QAAQN,IAAK,CAAAO,KAASD,EAAKN,CAAC,IAAKM,EAAKN,CAAC;AAChE,QAAMQ,IAAM,KAAK,KAAKD,IAAQD,EAAK,MAAM,GACnCG,IAAI,KAAK,IAAI,OAAW,KAAK,IAAID,GAAK,IAAI,GAAG,OAAOP,CAAG,GACvDS,IAAO,KAAK,IAAIJ,EAAK,QAAQ,KAAK,MAAM,OAAYX,CAAU,CAAC;AACrE,WAASK,IAAI,GAAGA,IAAIM,EAAK,QAAQN,KAAK;AACpC,UAAMW,IAAOL,EAAK,SAASN;AAC3B,IAAAM,EAAKN,CAAC,KAAMS,KAAKE,IAAOD,IAAOC,IAAOD,IAAO;AAAA,EAC/C;AACA,SAAOJ;AACT;AC/CA,KAAK,YAAY,CAACM,MAAkF;AAClG,QAAM,EAAE,IAAAC,GAAI,UAAAnB,GAAU,YAAAC,EAAA,IAAeiB,EAAE,MACjCN,IAAOb,EAAaC,GAAUC,CAAU;AAC9C,OAAK,YAAY,EAAE,IAAAkB,GAAI,MAAAP,EAAA,GAAQA,IAAO,EAAE,UAAU,CAACA,EAAK,MAAqB,EAAA,IAAM,MAAS;AAC9F;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Footsteps.js","sources":["../../src/audio/Footsteps.ts"],"sourcesContent":["import { sound } from './SoundEngine';\n\n/** What a surface sounds like underfoot: a step, and a beat of hooves (a mount) if it differs. */\nexport interface SurfaceSounds {\n step: string;\n hoof?: string;\n}\n\nexport interface FootstepOptions {\n /** Per surface name (`'grass'`, `'stone'`, `'wood'`…), the sounds a foot makes on it. */\n sounds: Record<string, SurfaceSounds>;\n /** The surface of anywhere `surfaceAt` does not name. */\n fallback: string;\n /** Weight of a step against other sounds (default 0.5). */\n gain?: number;\n /** Beyond this (world px) nobody else's steps are heard (default 200). */\n range?: number;\n}\n\n/**\n * Footsteps by the ground under the feet. The game says what a point is made of (`surfaceAt`: a tile's surface, a\n * Tiled property, a floor collider), and calls `step` on each beat of a walk or a gait; the sound is the surface's,\n * through `sound` at the feet, quieter and muffled with distance like every other sound, and dropped first when too\n * many sounds play at once.\n */\nexport class Footsteps {\n private surfaceAt: (x: number, y: number) => string | undefined = () => undefined;\n\n constructor(private readonly options: FootstepOptions) {}\n\n /** Where the surface of a world point comes from; `undefined` is the fallback surface. */\n setSurfaceAt(surfaceAt: (x: number, y: number) => string | undefined): void {\n this.surfaceAt = surfaceAt;\n }\n\n /** The surface at a world point. */\n surface(x: number, y: number): string {\n return this.surfaceAt(x, y) ?? this.options.fallback;\n }\n\n /** One step (or a beat of hooves when `mounted`) at the feet, world x/y; `local` is the player's own. */\n step(x: number, y: number, local: boolean, mounted = false): void {\n const o = this.options;\n const s = o.sounds[this.surface(x, y)] ?? o.sounds[o.fallback];\n if (!s) return;\n sound.play(mounted ? (s.hoof ?? s.step) : s.step, { x, y, local, gain: o.gain ?? 0.5, range: o.range ?? 200, minor: true });\n }\n}\n"],"names":["Footsteps","options","surfaceAt","x","y","local","mounted","o","s","sound"],"mappings":";AAyBO,MAAMA,EAAU;AAAA,EAGrB,YAA6BC,GAA0B;AAA1B,SAAA,UAAAA;AAAA,EAA2B;AAAA,EAA3B;AAAA,EAFrB,YAA0D,MAAA;AAAA;AAAA;AAAA,EAKlE,aAAaC,GAA+D;AAC1E,SAAK,YAAYA;AAAA,EACnB;AAAA;AAAA,EAGA,QAAQC,GAAWC,GAAmB;AACpC,WAAO,KAAK,UAAUD,GAAGC,CAAC,KAAK,KAAK,QAAQ;AAAA,EAC9C;AAAA;AAAA,EAGA,KAAKD,GAAWC,GAAWC,GAAgBC,IAAU,IAAa;AAChE,UAAMC,IAAI,KAAK,SACTC,IAAID,EAAE,OAAO,KAAK,QAAQJ,GAAGC,CAAC,CAAC,KAAKG,EAAE,OAAOA,EAAE,QAAQ;AAC7D,IAAKC,KACLC,EAAM,KAAKH,IAAWE,EAAE,QAAQA,EAAE,OAAQA,EAAE,MAAM,EAAE,GAAAL,GAAG,GAAAC,GAAG,OAAAC,GAAO,MAAME,EAAE,QAAQ,KAAK,OAAOA,EAAE,SAAS,KAAK,OAAO,GAAA,CAAM;AAAA,EAC5H;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MusicPlayer.js","sources":["../../src/audio/MusicPlayer.ts"],"sourcesContent":["import { engineSettings } from '../settings';\nimport { isTouchUi } from '../platform/touch';\nimport { ElementVoice } from './music/ElementVoice';\nimport { GraphVoice } from './music/GraphVoice';\nimport { MusicChannel, type PlayOptions } from './music/MusicChannel';\nimport { elementVolumeLocked, type MusicVoice } from './music/musicVoice';\n\nexport type { PlayOptions } from './music/MusicChannel';\n\n/**\n * How music reaches the speaker: `'element'` on the `<audio>` element's own volume (nothing in between; a file from\n * another origin plays as it is), `'graph'` through a Web Audio gain (the only way where that volume is read-only),\n * `'auto'` (default) the graph only where it has to be (iOS).\n */\nexport type MusicRoute = 'auto' | 'element' | 'graph';\n\nexport interface MusicOptions {\n route?: MusicRoute;\n /** Stop the music while the page is hidden (an app sent to the background). Default: on touch devices. */\n pauseInBackground?: boolean;\n}\n\n/**\n * Background music in layers: each named channel (`'music'` by default, `'ambience'`, any other) plays one looping\n * track at a time, crossfaded when it changes, all at `engineSettings.musicVolume`. Tracks stream through `<audio>`\n * elements (no decoding a whole song into memory).\n *\n * Browsers start sound only after a user gesture: a track played before one starts on the first tap or key. On iOS the\n * graph's context is woken inside that gesture too, and again after the app comes back from the background.\n *\n * ```ts\n * music.play('music/forest.ogg'); // the 'music' channel\n * music.channel('ambience').play('amb/wind.ogg', { volume: 0.5 }); // a second layer under it\n * music.play('music/boss.ogg', { fadeMs: 600 }); // crossfades the first only\n * music.stop(2000);\n * ```\n */\nexport class MusicPlayer {\n private readonly channels = new Map<string, MusicChannel>();\n private route: MusicRoute = 'auto';\n private graph: AudioContext | null = null;\n private graphLocked: boolean | null = null;\n private pauseInBackground: boolean | null = null;\n private installed = false;\n\n /** Picks the route and background behaviour; call before the first track for the route to apply to every track. */\n configure(options: MusicOptions): this {\n if (options.route) this.route = options.route;\n if (options.pauseInBackground !== undefined) this.pauseInBackground = options.pauseInBackground;\n return this;\n }\n\n /** The channel named `name`, made on first use. */\n channel(name = 'music'): MusicChannel {\n let ch = this.channels.get(name);\n if (!ch) {\n this.install();\n ch = new MusicChannel(() => engineSettings.getState().musicVolume, (el) => this.voiceFor(el));\n this.channels.set(name, ch);\n }\n return ch;\n }\n\n /** The url the `'music'` channel plays, if any. */\n get current(): string | null {\n return this.channels.get('music')?.current ?? null;\n }\n\n /** Plays `url` on the `'music'` channel. */\n play(url: string, options?: PlayOptions): void {\n this.channel().play(url, options);\n }\n\n /** Fades every channel out. */\n stop(fadeMs = 1200): void {\n for (const ch of this.channels.values()) ch.stop(fadeMs);\n }\n\n pause(): void {\n for (const ch of this.channels.values()) ch.pause();\n }\n\n resume(): void {\n this.wake();\n }\n\n destroy(): void {\n for (const ch of this.channels.values()) ch.dispose();\n this.channels.clear();\n void this.graph?.close();\n this.graph = null;\n }\n\n private voiceFor(el: HTMLAudioElement): MusicVoice {\n const graph = this.throughGraph() ? this.context() : null;\n return graph ? new GraphVoice(el, graph) : new ElementVoice(el);\n }\n\n private throughGraph(): boolean {\n if (this.route !== 'auto') return this.route === 'graph';\n return (this.graphLocked ??= elementVolumeLocked());\n }\n\n private context(): AudioContext | null {\n if (this.graph) return this.graph;\n const Ctor = (globalThis as { AudioContext?: typeof AudioContext; webkitAudioContext?: typeof AudioContext }).AudioContext ?? (globalThis as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctor) return null;\n this.graph = new Ctor();\n return this.graph;\n }\n\n /** Retries every refused start and follows the volume; the graph's context resumes inside the gesture that called it. */\n private wake(): void {\n if (this.graph && this.graph.state !== 'running') void this.graph.resume().catch(() => {});\n if (typeof document !== 'undefined' && document.hidden) return;\n for (const ch of this.channels.values()) ch.refresh();\n }\n\n private install(): void {\n if (this.installed || typeof window === 'undefined') return;\n this.installed = true;\n const wake = () => this.wake();\n // `touchend` is the gesture iOS accepts for starting media; the others are the desktop's\n for (const type of ['pointerdown', 'touchend', 'keydown'] as const) window.addEventListener(type, wake, { capture: true });\n engineSettings.subscribe(wake);\n document.addEventListener('visibilitychange', () => {\n if (!(this.pauseInBackground ?? isTouchUi())) return;\n if (document.hidden) this.pause();\n else wake();\n });\n }\n}\n\n/** A fade's value at `now`: linear from `from` to `to` over `ms`. */\nexport function fadeValue(f: { from: number; to: number; start: number; ms: number }, now: number): number {\n if (f.ms <= 0 || now >= f.start + f.ms) return f.to;\n if (now <= f.start) return f.from;\n return f.from + ((f.to - f.from) * (now - f.start)) / f.ms;\n}\n\n/** The shared music player. */\nexport const music = new MusicPlayer();\n"],"names":["MusicPlayer","options","name","ch","MusicChannel","engineSettings","el","url","fadeMs","graph","GraphVoice","ElementVoice","elementVolumeLocked","Ctor","wake","type","isTouchUi","music"],"mappings":";;;;;;AAqCO,MAAMA,EAAY;AAAA,EACN,+BAAe,IAAA;AAAA,EACxB,QAAoB;AAAA,EACpB,QAA6B;AAAA,EAC7B,cAA8B;AAAA,EAC9B,oBAAoC;AAAA,EACpC,YAAY;AAAA;AAAA,EAGpB,UAAUC,GAA6B;AACrC,WAAIA,EAAQ,UAAO,KAAK,QAAQA,EAAQ,QACpCA,EAAQ,sBAAsB,WAAW,KAAK,oBAAoBA,EAAQ,oBACvE;AAAA,EACT;AAAA;AAAA,EAGA,QAAQC,IAAO,SAAuB;AACpC,QAAIC,IAAK,KAAK,SAAS,IAAID,CAAI;AAC/B,WAAKC,MACH,KAAK,QAAA,GACLA,IAAK,IAAIC,EAAa,MAAMC,EAAe,SAAA,EAAW,aAAa,CAACC,MAAO,KAAK,SAASA,CAAE,CAAC,GAC5F,KAAK,SAAS,IAAIJ,GAAMC,CAAE,IAErBA;AAAA,EACT;AAAA;AAAA,EAGA,IAAI,UAAyB;AAC3B,WAAO,KAAK,SAAS,IAAI,OAAO,GAAG,WAAW;AAAA,EAChD;AAAA;AAAA,EAGA,KAAKI,GAAaN,GAA6B;AAC7C,SAAK,QAAA,EAAU,KAAKM,GAAKN,CAAO;AAAA,EAClC;AAAA;AAAA,EAGA,KAAKO,IAAS,MAAY;AACxB,eAAWL,KAAM,KAAK,SAAS,SAAU,CAAAA,EAAG,KAAKK,CAAM;AAAA,EACzD;AAAA,EAEA,QAAc;AACZ,eAAWL,KAAM,KAAK,SAAS,OAAA,KAAa,MAAA;AAAA,EAC9C;AAAA,EAEA,SAAe;AACb,SAAK,KAAA;AAAA,EACP;AAAA,EAEA,UAAgB;AACd,eAAWA,KAAM,KAAK,SAAS,OAAA,KAAa,QAAA;AAC5C,SAAK,SAAS,MAAA,GACT,KAAK,OAAO,MAAA,GACjB,KAAK,QAAQ;AAAA,EACf;AAAA,EAEQ,SAASG,GAAkC;AACjD,UAAMG,IAAQ,KAAK,aAAA,IAAiB,KAAK,YAAY;AACrD,WAAOA,IAAQ,IAAIC,EAAWJ,GAAIG,CAAK,IAAI,IAAIE,EAAaL,CAAE;AAAA,EAChE;AAAA,EAEQ,eAAwB;AAC9B,WAAI,KAAK,UAAU,SAAe,KAAK,UAAU,UACzC,KAAK,gBAAgBM,EAAA;AAAA,EAC/B;AAAA,EAEQ,UAA+B;AACrC,QAAI,KAAK,MAAO,QAAO,KAAK;AAC5B,UAAMC,IAAQ,WAAgG,gBAAiB,WAA4D;AAC3L,WAAKA,KACL,KAAK,QAAQ,IAAIA,EAAA,GACV,KAAK,SAFM;AAAA,EAGpB;AAAA;AAAA,EAGQ,OAAa;AAEnB,QADI,KAAK,SAAS,KAAK,MAAM,UAAU,aAAgB,KAAK,MAAM,OAAA,EAAS,MAAM,MAAM;AAAA,IAAC,CAAC,GACrF,SAAO,WAAa,OAAe,SAAS;AAChD,iBAAWV,KAAM,KAAK,SAAS,OAAA,KAAa,QAAA;AAAA,EAC9C;AAAA,EAEQ,UAAgB;AACtB,QAAI,KAAK,aAAa,OAAO,SAAW,IAAa;AACrD,SAAK,YAAY;AACjB,UAAMW,IAAO,MAAM,KAAK,KAAA;AAExB,eAAWC,KAAQ,CAAC,eAAe,YAAY,SAAS,EAAY,QAAO,iBAAiBA,GAAMD,GAAM,EAAE,SAAS,IAAM;AACzH,IAAAT,EAAe,UAAUS,CAAI,GAC7B,SAAS,iBAAiB,oBAAoB,MAAM;AAClD,OAAM,KAAK,qBAAqBE,EAAA,OAC5B,SAAS,SAAQ,KAAK,MAAA,IACrBF,EAAA;AAAA,IACP,CAAC;AAAA,EACH;AACF;AAUO,MAAMG,IAAQ,IAAIjB,EAAA;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SoundEngine.js","sources":["../../src/audio/SoundEngine.ts"],"sourcesContent":["import { engineSettings } from '../settings';\nimport { hasTakes, SampleBank } from './sampleBank';\nimport { SOUNDS } from './soundTable';\nimport { renderRecipe, SYNTH_RATE } from './synth';\n\n/**\n * Plays sound effects through Web Audio, on a gain of their own (`engineSettings.sfxVolume`, beside the music's).\n * A sound is one of its recorded takes (`registerTakes`, `SampleBank`) when it has\n * any, else its `sounds.json` recipe rendered once into a buffer the first time it is asked for. A sound in the\n * world fades with its distance from the local player, pans with where it happened and loses its highs as it goes\n * (`MUFFLE`); the local player's own swings and blows play at full weight, everyone else's at `OTHERS`.\n *\n * Kept lean on purpose (an earlier, much larger set of effects was taken out for being too busy): one sound per\n * kind per `MIN_GAP_MS`, never more than `MAX_VOICES` at once, and nothing but combat.\n */\n\n/** Beyond this (px) a sound in the world is not heard at all. */\nconst HEARING = 480;\nconst MAX_VOICES = 10;\n/** Minor sounds (footsteps) start only while fewer than this are playing, so they never take a blow's voice. */\nconst MINOR_VOICES = 6;\n/** The same kind twice inside this (ms) is one sound: a cleave clipping six bodies lands once. */\nconst MIN_GAP_MS = 60;\n/** Weight of what other players, pets and monsters do, against the local player's own. */\nconst OTHERS = 0.45;\nconst DEFAULT_JITTER = 0.05;\n/** A sound cut short by `maxMs` fades out over this, so the cut does not click. */\nconst CUT_FADE_MS = 80;\n/** Low-pass cutoff (Hz) right beside the listener and at the edge of hearing: far blows sound dull, not just quiet. */\nconst MUFFLE: [number, number] = [18000, 1800];\n/**\n * The tone of the whole effects bus: the recorded takes came back bright (hiss, glassy ice, blade scrapes) and read\n * as shrill next to the music, so the highs are shelved down and the very top cut, for every sound at once.\n */\nconst TAME_SHELF = { freq: 3500, gainDb: -7 };\nconst TAME_TOP = 11000;\n\nexport interface SoundOptions {\n /** Where it happened in the world; absent for what happens to or is done by the local player. */\n x?: number;\n y?: number;\n /** Done by (or to) the local player: full weight. */\n local?: boolean;\n /** Extra weight (default 1). */\n gain?: number;\n /** How far away it can be heard (px), when nearer than `HEARING`. */\n range?: number;\n /** Skipped when the mix is busy (footsteps). */\n minor?: boolean;\n /** Cut off after this long (ms), faded over `CUT_FADE_MS`: a cast's sound ends with the cast. */\n maxMs?: number;\n}\n\nexport class SoundEngine {\n private ctx: AudioContext | null = null;\n private master: GainNode | null = null;\n private readonly buffers = new Map<string, AudioBuffer | null>();\n private readonly samples = new SampleBank();\n private readonly lastAt = new Map<string, number>();\n private voices = 0;\n /** Where the local player stands, set by the world scene; null off the world screens. */\n listener: () => { x: number; y: number } | null = () => null;\n private onRunning: (running: boolean) => void = () => {};\n\n constructor() {\n if (typeof window === 'undefined') return;\n // iOS lets a page start sound only inside a gesture, counts `touchend` (not `pointerdown`) as one, and leaves\n // the context 'interrupted' after the app comes back from the background: every touch retries until it runs\n const unlock = () => {\n const ctx = this.context();\n if (!ctx || ctx.state === 'running') return;\n void ctx.resume();\n // one silent sample started inside the gesture is what WebKit needs to open the output\n const blank = ctx.createBufferSource();\n blank.buffer = ctx.createBuffer(1, 1, 22050);\n blank.connect(ctx.destination);\n blank.start();\n };\n // listening only while the context is not running: once it runs a touch has nothing to do, and the listeners\n // come back the moment it stops (the interrupted iOS case)\n const types = ['pointerdown', 'pointerup', 'touchend', 'keydown'] as const;\n const listen = (on: boolean) => {\n for (const type of types) {\n if (on) window.addEventListener(type, unlock, { capture: true });\n else window.removeEventListener(type, unlock, { capture: true });\n }\n };\n this.onRunning = (running) => listen(!running);\n listen(true);\n // game sound, not media: it follows the ring/silent switch and mixes with the player's own music (iOS 17+)\n const session = (navigator as Navigator & { audioSession?: { type: string } }).audioSession;\n if (session) session.type = 'ambient';\n engineSettings.subscribe((s) => {\n if (this.ctx && this.master) this.master.gain.setTargetAtTime(this.level(s.sfxVolume), this.ctx.currentTime, 0.02);\n });\n }\n\n play(name: string | undefined, opts: SoundOptions = {}): void {\n if (!name || (!SOUNDS.recipes[name] && !hasTakes(name))) return;\n if (engineSettings.getState().sfxVolume <= 0) return;\n const ctx = this.ctx;\n if (!ctx || ctx.state !== 'running' || this.voices >= (opts.minor ? MINOR_VOICES : MAX_VOICES)) return;\n let gain = (opts.gain ?? 1) * (opts.local ? 1 : OTHERS);\n let pan = 0;\n let far = 0;\n if (opts.x !== undefined && opts.y !== undefined) {\n const me = this.listener();\n if (me) {\n const dx = opts.x - me.x;\n const d = Math.hypot(dx, opts.y - me.y);\n const hearing = Math.min(HEARING, opts.range ?? HEARING);\n if (d >= hearing) return;\n gain *= Math.pow(1 - d / hearing, 1.5);\n far = d / HEARING;\n pan = Math.max(-0.7, Math.min(0.7, dx / 320));\n }\n }\n if (gain < 0.03) return;\n const now = performance.now();\n if (now - (this.lastAt.get(name) ?? -1e9) < MIN_GAP_MS) return;\n const take = this.samples.take(ctx, name);\n const buffer = take ?? this.buffer(ctx, name);\n if (!buffer) return;\n if (take) gain *= SOUNDS.recipes[name]?.fileGain ?? 1;\n this.lastAt.set(name, now);\n const src = ctx.createBufferSource();\n src.buffer = buffer;\n const jitter = SOUNDS.recipes[name]?.jitter ?? DEFAULT_JITTER;\n src.playbackRate.value = 1 + (Math.random() * 2 - 1) * jitter;\n const g = ctx.createGain();\n g.gain.value = Math.min(1, gain);\n const p = ctx.createStereoPanner();\n p.pan.value = pan;\n const lp = ctx.createBiquadFilter();\n lp.type = 'lowpass';\n lp.frequency.value = MUFFLE[0] * Math.pow(MUFFLE[1] / MUFFLE[0], far);\n src.connect(lp).connect(g).connect(p).connect(this.master!);\n this.voices++;\n src.onended = () => {\n this.voices--;\n src.disconnect();\n lp.disconnect();\n g.disconnect();\n p.disconnect();\n };\n src.start();\n const lasts = (buffer.duration / src.playbackRate.value) * 1000;\n if (opts.maxMs !== undefined && lasts > opts.maxMs) {\n const end = ctx.currentTime + Math.max(CUT_FADE_MS, opts.maxMs) / 1000;\n g.gain.setValueAtTime(g.gain.value, end - CUT_FADE_MS / 1000);\n g.gain.linearRampToValueAtTime(0, end);\n src.stop(end);\n }\n }\n\n private level(volume: number): number {\n return Math.max(0, Math.min(1, volume)) * SOUNDS.master;\n }\n\n private context(): AudioContext | null {\n if (this.ctx) return this.ctx;\n if (typeof AudioContext === 'undefined') return null;\n this.ctx = new AudioContext();\n this.master = this.ctx.createGain();\n this.master.gain.value = this.level(engineSettings.getState().sfxVolume);\n const shelf = this.ctx.createBiquadFilter();\n shelf.type = 'highshelf';\n shelf.frequency.value = TAME_SHELF.freq;\n shelf.gain.value = TAME_SHELF.gainDb;\n const top = this.ctx.createBiquadFilter();\n top.type = 'lowpass';\n top.frequency.value = TAME_TOP;\n this.master.connect(shelf).connect(top).connect(this.ctx.destination);\n const ctx = this.ctx;\n const changed = () => {\n const running = ctx.state === 'running';\n this.onRunning(running);\n if (running) this.prerender(ctx);\n };\n ctx.addEventListener('statechange', changed);\n // a context allowed to run from the start changes no state\n if (ctx.state === 'running') queueMicrotask(changed);\n return ctx;\n }\n\n /**\n * Starts decoding every recorded take and renders every recipe not rendered yet, one per task, once the context\n * first runs — so the first swing of a fight does not pay for its sound in that frame.\n */\n private prerender(ctx: AudioContext): void {\n for (const name of this.samples.names()) this.samples.load(ctx, name);\n // a sound with recorded takes plays them; its recipe is only the fallback while they decode, not worth a 40 ms task\n const todo = Object.keys(SOUNDS.recipes).filter((name) => !this.buffers.has(name) && !hasTakes(name));\n const next = () => {\n const name = todo.shift();\n if (name === undefined) return;\n this.buffer(ctx, name);\n setTimeout(next, 0);\n };\n next();\n }\n\n /** Rendered on first use or by `prerender` (a few ms each), then kept. */\n private buffer(ctx: AudioContext, name: string): AudioBuffer | null {\n const cached = this.buffers.get(name);\n if (cached !== undefined) return cached;\n const recipe = SOUNDS.recipes[name];\n let buf: AudioBuffer | null = null;\n if (recipe) {\n const data = renderRecipe(recipe);\n buf = ctx.createBuffer(1, data.length, SYNTH_RATE);\n buf.getChannelData(0).set(data);\n }\n this.buffers.set(name, buf);\n return buf;\n }\n}\n\nexport const sound = new SoundEngine();\n"],"names":["HEARING","MAX_VOICES","MINOR_VOICES","MIN_GAP_MS","OTHERS","DEFAULT_JITTER","CUT_FADE_MS","MUFFLE","TAME_SHELF","TAME_TOP","SoundEngine","SampleBank","unlock","ctx","blank","types","listen","on","type","running","session","engineSettings","name","opts","SOUNDS","hasTakes","gain","pan","far","me","dx","hearing","now","take","buffer","src","jitter","g","p","lp","lasts","end","volume","shelf","top","changed","todo","next","cached","recipe","buf","data","renderRecipe","SYNTH_RATE","sound"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cleanTake.js","sources":["../../src/audio/cleanTake.ts"],"sourcesContent":["/*\n * Cleans one recorded take (`SampleBank`): folded to mono, the silence an encoder or the model leaves at the start cut\n * (a swing heard 30 ms late reads as lag) and at the end trimmed, and brought to one loudness (`LOUDNESS`, RMS of what\n * is left, never past `PEAK`), so takes from different prompts sit at one level. Pure: run in `takeCleanerWorker.ts`,\n * because on the main thread the first blows of a fight froze the game 70–170 ms a take on a Mac and several times\n * that on a phone (profiled).\n */\n\n/** Below this share of the take's own peak a sample counts as silence when trimming (-40 dB). */\nconst FLOOR = 0.01;\n/** A take whose peak is under this is treated as empty (about -46 dBFS): the model returned near-silence. */\nconst EMPTY = 0.005;\n/** Target RMS of a cleaned take (-16 dBFS) and the peak it may not pass. */\nconst LOUDNESS = 0.16;\nconst PEAK = 0.95;\n/** Kept before the first loud sample, so the attack is not clipped into a click (s). */\nconst LEAD = 0.002;\n/** A short fade over the cut end, so a trimmed tail does not click (s). */\nconst TAIL_FADE = 0.01;\n\n/** The cleaned mono samples, or null for a take that is silence. */\nexport function cleanSamples(channels: Float32Array[], sampleRate: number): Float32Array | null {\n const n = channels[0]?.length ?? 0;\n const k0 = 1 / Math.max(1, channels.length);\n const mono = new Float32Array(n);\n for (const ch of channels) for (let i = 0; i < n; i++) mono[i]! += ch[i]! * k0;\n let top = 0;\n for (let i = 0; i < n; i++) {\n const a = Math.abs(mono[i]!);\n if (a > top) top = a;\n }\n if (top < EMPTY) return null;\n const floor = top * FLOOR;\n let start = 0;\n while (start < n && Math.abs(mono[start]!) < floor) start++;\n let end = n;\n while (end > start && Math.abs(mono[end - 1]!) < floor) end--;\n if (end <= start) return null;\n start = Math.max(0, start - Math.round(LEAD * sampleRate));\n const data = mono.slice(start, end);\n let power = 0;\n for (let i = 0; i < data.length; i++) power += data[i]! * data[i]!;\n const rms = Math.sqrt(power / data.length);\n const k = Math.min(LOUDNESS / Math.max(rms, 1e-6), PEAK / top);\n const fade = Math.min(data.length, Math.round(TAIL_FADE * sampleRate));\n for (let i = 0; i < data.length; i++) {\n const left = data.length - i;\n data[i]! *= k * (left < fade ? left / fade : 1);\n }\n return data;\n}\n"],"names":["cleanSamples","channels","sampleRate","n","k0","mono","ch","i","top","a","floor","start","end","data","power","rms","k","fade","left"],"mappings":"AAqBO,SAASA,EAAaC,GAA0BC,GAAyC;AAC9F,QAAMC,IAAIF,EAAS,CAAC,GAAG,UAAU,GAC3BG,IAAK,IAAI,KAAK,IAAI,GAAGH,EAAS,MAAM,GACpCI,IAAO,IAAI,aAAaF,CAAC;AAC/B,aAAWG,KAAML,EAAU,UAASM,IAAI,GAAGA,IAAIJ,GAAGI,IAAK,CAAAF,EAAKE,CAAC,KAAMD,EAAGC,CAAC,IAAKH;AAC5E,MAAII,IAAM;AACV,WAASD,IAAI,GAAGA,IAAIJ,GAAGI,KAAK;AAC1B,UAAME,IAAI,KAAK,IAAIJ,EAAKE,CAAC,CAAE;AAC3B,IAAIE,IAAID,MAAKA,IAAMC;AAAA,EACrB;AACA,MAAID,IAAM,KAAO,QAAO;AACxB,QAAME,IAAQF,IAAM;AACpB,MAAIG,IAAQ;AACZ,SAAOA,IAAQR,KAAK,KAAK,IAAIE,EAAKM,CAAK,CAAE,IAAID,IAAO,CAAAC;AACpD,MAAIC,IAAMT;AACV,SAAOS,IAAMD,KAAS,KAAK,IAAIN,EAAKO,IAAM,CAAC,CAAE,IAAIF,IAAO,CAAAE;AACxD,MAAIA,KAAOD,EAAO,QAAO;AACzB,EAAAA,IAAQ,KAAK,IAAI,GAAGA,IAAQ,KAAK,MAAM,OAAOT,CAAU,CAAC;AACzD,QAAMW,IAAOR,EAAK,MAAMM,GAAOC,CAAG;AAClC,MAAIE,IAAQ;AACZ,WAASP,IAAI,GAAGA,IAAIM,EAAK,QAAQN,IAAK,CAAAO,KAASD,EAAKN,CAAC,IAAKM,EAAKN,CAAC;AAChE,QAAMQ,IAAM,KAAK,KAAKD,IAAQD,EAAK,MAAM,GACnCG,IAAI,KAAK,IAAI,OAAW,KAAK,IAAID,GAAK,IAAI,GAAG,OAAOP,CAAG,GACvDS,IAAO,KAAK,IAAIJ,EAAK,QAAQ,KAAK,MAAM,OAAYX,CAAU,CAAC;AACrE,WAASK,IAAI,GAAGA,IAAIM,EAAK,QAAQN,KAAK;AACpC,UAAMW,IAAOL,EAAK,SAASN;AAC3B,IAAAM,EAAKN,CAAC,KAAMS,KAAKE,IAAOD,IAAOC,IAAOD,IAAO;AAAA,EAC/C;AACA,SAAOJ;AACT;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ElementVoice.js","sources":["../../../src/audio/music/ElementVoice.ts"],"sourcesContent":["import { clamp01, type MusicVoice } from './musicVoice';\n\n/** A track on the `<audio>` element's own volume, faded frame by frame: nothing between the file and the speaker. */\nexport class ElementVoice implements MusicVoice {\n private fading: number | null = null;\n\n constructor(readonly el: HTMLAudioElement) {}\n\n level(): number {\n return this.el.volume;\n }\n\n set(level: number): void {\n this.stopFade();\n this.el.volume = clamp01(level);\n }\n\n fade(to: number, ms: number, done?: () => void): void {\n this.stopFade();\n const from = this.el.volume;\n const start = performance.now();\n const step = () => {\n const k = ms > 0 ? Math.min(1, (performance.now() - start) / ms) : 1;\n this.el.volume = clamp01(from + (to - from) * k);\n if (k < 1) this.fading = requestAnimationFrame(step);\n else {\n this.fading = null;\n done?.();\n }\n };\n this.fading = requestAnimationFrame(step);\n }\n\n get busy(): boolean {\n return this.fading !== null;\n }\n\n dispose(): void {\n this.stopFade();\n this.el.pause();\n this.el.removeAttribute('src');\n this.el.load();\n }\n\n private stopFade(): void {\n if (this.fading !== null) cancelAnimationFrame(this.fading);\n this.fading = null;\n }\n}\n"],"names":["ElementVoice","el","level","clamp01","to","ms","done","from","start","step","k"],"mappings":";AAGO,MAAMA,EAAmC;AAAA,EAG9C,YAAqBC,GAAsB;AAAtB,SAAA,KAAAA;AAAA,EAAuB;AAAA,EAAvB;AAAA,EAFb,SAAwB;AAAA,EAIhC,QAAgB;AACd,WAAO,KAAK,GAAG;AAAA,EACjB;AAAA,EAEA,IAAIC,GAAqB;AACvB,SAAK,SAAA,GACL,KAAK,GAAG,SAASC,EAAQD,CAAK;AAAA,EAChC;AAAA,EAEA,KAAKE,GAAYC,GAAYC,GAAyB;AACpD,SAAK,SAAA;AACL,UAAMC,IAAO,KAAK,GAAG,QACfC,IAAQ,YAAY,IAAA,GACpBC,IAAO,MAAM;AACjB,YAAMC,IAAIL,IAAK,IAAI,KAAK,IAAI,IAAI,YAAY,IAAA,IAAQG,KAASH,CAAE,IAAI;AACnE,WAAK,GAAG,SAASF,EAAQI,KAAQH,IAAKG,KAAQG,CAAC,GAC3CA,IAAI,IAAG,KAAK,SAAS,sBAAsBD,CAAI,KAEjD,KAAK,SAAS,MACdH,IAAA;AAAA,IAEJ;AACA,SAAK,SAAS,sBAAsBG,CAAI;AAAA,EAC1C;AAAA,EAEA,IAAI,OAAgB;AAClB,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA,EAEA,UAAgB;AACd,SAAK,SAAA,GACL,KAAK,GAAG,MAAA,GACR,KAAK,GAAG,gBAAgB,KAAK,GAC7B,KAAK,GAAG,KAAA;AAAA,EACV;AAAA,EAEQ,WAAiB;AACvB,IAAI,KAAK,WAAW,QAAM,qBAAqB,KAAK,MAAM,GAC1D,KAAK,SAAS;AAAA,EAChB;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GraphVoice.js","sources":["../../../src/audio/music/GraphVoice.ts"],"sourcesContent":["import { clamp01, type MusicVoice } from './musicVoice';\n\n/**\n * A track routed `<audio>` → MediaElementSource → gain → speakers, so its volume and crossfades work where the\n * element's own `volume` is read-only (iOS). The fade is scheduled on the audio clock (a ramp), so it runs even while\n * the page draws no frames. A cross-origin file without CORS headers plays silent this way: serve music from the\n * game's own origin, or keep `ElementVoice` where volume works.\n */\nexport class GraphVoice implements MusicVoice {\n private readonly gain: GainNode;\n private readonly source: MediaElementAudioSourceNode;\n private timer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(\n readonly el: HTMLAudioElement,\n private readonly graph: AudioContext,\n ) {\n this.source = graph.createMediaElementSource(el);\n this.gain = graph.createGain();\n this.gain.gain.value = 0;\n this.source.connect(this.gain).connect(graph.destination);\n }\n\n level(): number {\n return this.gain.gain.value;\n }\n\n set(level: number): void {\n this.stopFade();\n const g = this.gain.gain;\n g.cancelScheduledValues(this.graph.currentTime);\n g.setValueAtTime(clamp01(level), this.graph.currentTime);\n }\n\n fade(to: number, ms: number, done?: () => void): void {\n this.stopFade();\n const g = this.gain.gain;\n const now = this.graph.currentTime;\n g.cancelScheduledValues(now);\n g.setValueAtTime(g.value, now);\n g.linearRampToValueAtTime(clamp01(to), now + ms / 1000);\n this.timer = setTimeout(() => {\n this.timer = null;\n done?.();\n }, ms);\n }\n\n get busy(): boolean {\n return this.timer !== null;\n }\n\n dispose(): void {\n this.stopFade();\n this.el.pause();\n this.source.disconnect();\n this.gain.disconnect();\n }\n\n private stopFade(): void {\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n }\n}\n"],"names":["GraphVoice","el","graph","level","g","clamp01","to","ms","done","now"],"mappings":";AAQO,MAAMA,EAAiC;AAAA,EAK5C,YACWC,GACQC,GACjB;AAFS,SAAA,KAAAD,GACQ,KAAA,QAAAC,GAEjB,KAAK,SAASA,EAAM,yBAAyBD,CAAE,GAC/C,KAAK,OAAOC,EAAM,WAAA,GAClB,KAAK,KAAK,KAAK,QAAQ,GACvB,KAAK,OAAO,QAAQ,KAAK,IAAI,EAAE,QAAQA,EAAM,WAAW;AAAA,EAC1D;AAAA,EAPW;AAAA,EACQ;AAAA,EANF;AAAA,EACA;AAAA,EACT,QAA8C;AAAA,EAYtD,QAAgB;AACd,WAAO,KAAK,KAAK,KAAK;AAAA,EACxB;AAAA,EAEA,IAAIC,GAAqB;AACvB,SAAK,SAAA;AACL,UAAMC,IAAI,KAAK,KAAK;AACpB,IAAAA,EAAE,sBAAsB,KAAK,MAAM,WAAW,GAC9CA,EAAE,eAAeC,EAAQF,CAAK,GAAG,KAAK,MAAM,WAAW;AAAA,EACzD;AAAA,EAEA,KAAKG,GAAYC,GAAYC,GAAyB;AACpD,SAAK,SAAA;AACL,UAAMJ,IAAI,KAAK,KAAK,MACdK,IAAM,KAAK,MAAM;AACvB,IAAAL,EAAE,sBAAsBK,CAAG,GAC3BL,EAAE,eAAeA,EAAE,OAAOK,CAAG,GAC7BL,EAAE,wBAAwBC,EAAQC,CAAE,GAAGG,IAAMF,IAAK,GAAI,GACtD,KAAK,QAAQ,WAAW,MAAM;AAC5B,WAAK,QAAQ,MACbC,IAAA;AAAA,IACF,GAAGD,CAAE;AAAA,EACP;AAAA,EAEA,IAAI,OAAgB;AAClB,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA,EAEA,UAAgB;AACd,SAAK,SAAA,GACL,KAAK,GAAG,MAAA,GACR,KAAK,OAAO,WAAA,GACZ,KAAK,KAAK,WAAA;AAAA,EACZ;AAAA,EAEQ,WAAiB;AACvB,IAAI,KAAK,SAAO,aAAa,KAAK,KAAK,GACvC,KAAK,QAAQ;AAAA,EACf;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MusicChannel.js","sources":["../../../src/audio/music/MusicChannel.ts"],"sourcesContent":["import type { MusicVoice } from './musicVoice';\n\nexport interface PlayOptions {\n /** Crossfade from what this channel plays now, ms (default 1200). */\n fadeMs?: number;\n /** Start over at the end (default true). */\n loop?: boolean;\n /** This track's own level, 0–1, under the music volume (default 1). */\n volume?: number;\n}\n\n/**\n * One layer of music (the score, a place's ambience…): one looping track at a time, crossfaded when it changes, at\n * its own level under the player's music volume.\n */\nexport class MusicChannel {\n private voice: MusicVoice | null = null;\n private url: string | null = null;\n private level = 1;\n\n constructor(\n private readonly volume: () => number,\n private readonly makeVoice: (el: HTMLAudioElement) => MusicVoice,\n ) {}\n\n /** The url of the track playing (or fading in), if any. */\n get current(): string | null {\n return this.url;\n }\n\n /** Plays `url`, crossfading from what plays now; the track already playing only takes the new `volume`. */\n play(url: string, options: PlayOptions = {}): void {\n const level = Math.min(1, Math.max(0, options.volume ?? 1));\n if (url === this.url) {\n if (level !== this.level) {\n this.level = level;\n this.refresh();\n }\n return;\n }\n if (typeof Audio === 'undefined') return;\n const fadeMs = Math.max(0, options.fadeMs ?? 1200);\n this.fadeOut(fadeMs);\n this.url = url;\n this.level = level;\n const el = new Audio(url);\n el.loop = options.loop !== false;\n el.preload = 'auto';\n const voice = this.makeVoice(el);\n voice.set(0);\n this.voice = voice;\n void el.play().catch(() => {\n // refused until the first gesture; the player's unlock retries it\n });\n voice.fade(this.target(), fadeMs);\n }\n\n /** Fades this channel out and stops it. */\n stop(fadeMs = 1200): void {\n this.fadeOut(Math.max(0, fadeMs));\n this.url = null;\n }\n\n /** Retries a start the browser refused, and follows the music volume. */\n refresh(): void {\n const v = this.voice;\n if (!v) return;\n if (v.el.paused) void v.el.play().catch(() => {});\n if (!v.busy) v.set(this.target());\n }\n\n /** Stops where it is (the app went to the background); `refresh` picks it up again. */\n pause(): void {\n this.voice?.el.pause();\n }\n\n dispose(): void {\n this.voice?.dispose();\n this.voice = null;\n this.url = null;\n }\n\n private target(): number {\n return Math.max(0, Math.min(1, this.volume() * this.level));\n }\n\n private fadeOut(ms: number): void {\n const old = this.voice;\n this.voice = null;\n if (old) old.fade(0, ms, () => old.dispose());\n }\n}\n"],"names":["MusicChannel","volume","makeVoice","url","options","level","fadeMs","el","voice","v","ms","old"],"mappings":"AAeO,MAAMA,EAAa;AAAA,EAKxB,YACmBC,GACAC,GACjB;AAFiB,SAAA,SAAAD,GACA,KAAA,YAAAC;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA,EANX,QAA2B;AAAA,EAC3B,MAAqB;AAAA,EACrB,QAAQ;AAAA;AAAA,EAQhB,IAAI,UAAyB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,KAAKC,GAAaC,IAAuB,IAAU;AACjD,UAAMC,IAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,GAAGD,EAAQ,UAAU,CAAC,CAAC;AAC1D,QAAID,MAAQ,KAAK,KAAK;AACpB,MAAIE,MAAU,KAAK,UACjB,KAAK,QAAQA,GACb,KAAK,QAAA;AAEP;AAAA,IACF;AACA,QAAI,OAAO,QAAU,IAAa;AAClC,UAAMC,IAAS,KAAK,IAAI,GAAGF,EAAQ,UAAU,IAAI;AACjD,SAAK,QAAQE,CAAM,GACnB,KAAK,MAAMH,GACX,KAAK,QAAQE;AACb,UAAME,IAAK,IAAI,MAAMJ,CAAG;AACxB,IAAAI,EAAG,OAAOH,EAAQ,SAAS,IAC3BG,EAAG,UAAU;AACb,UAAMC,IAAQ,KAAK,UAAUD,CAAE;AAC/B,IAAAC,EAAM,IAAI,CAAC,GACX,KAAK,QAAQA,GACRD,EAAG,OAAO,MAAM,MAAM;AAAA,IAE3B,CAAC,GACDC,EAAM,KAAK,KAAK,OAAA,GAAUF,CAAM;AAAA,EAClC;AAAA;AAAA,EAGA,KAAKA,IAAS,MAAY;AACxB,SAAK,QAAQ,KAAK,IAAI,GAAGA,CAAM,CAAC,GAChC,KAAK,MAAM;AAAA,EACb;AAAA;AAAA,EAGA,UAAgB;AACd,UAAMG,IAAI,KAAK;AACf,IAAKA,MACDA,EAAE,GAAG,UAAaA,EAAE,GAAG,OAAO,MAAM,MAAM;AAAA,IAAC,CAAC,GAC3CA,EAAE,UAAQ,IAAI,KAAK,QAAQ;AAAA,EAClC;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,OAAO,GAAG,MAAA;AAAA,EACjB;AAAA,EAEA,UAAgB;AACd,SAAK,OAAO,QAAA,GACZ,KAAK,QAAQ,MACb,KAAK,MAAM;AAAA,EACb;AAAA,EAEQ,SAAiB;AACvB,WAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,OAAA,IAAW,KAAK,KAAK,CAAC;AAAA,EAC5D;AAAA,EAEQ,QAAQC,GAAkB;AAChC,UAAMC,IAAM,KAAK;AACjB,SAAK,QAAQ,MACTA,OAAS,KAAK,GAAGD,GAAI,MAAMC,EAAI,SAAS;AAAA,EAC9C;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"musicVoice.js","sources":["../../../src/audio/music/musicVoice.ts"],"sourcesContent":["/**\n * One looping track playing on one channel of `MusicPlayer`, with a volume that can be set and faded. Two ways to get\n * one: `ElementVoice` (the `<audio>` element's own volume) and `GraphVoice` (the element routed through a Web Audio\n * gain, for where an `<audio>`'s `volume` is read-only: iOS).\n */\nexport interface MusicVoice {\n readonly el: HTMLAudioElement;\n /** The volume now (0..1). */\n level(): number;\n /** Sets it at once, cancelling a fade in flight. */\n set(level: number): void;\n /** Fades to `level` over `ms`; `done` runs at the end (not if a later fade or `set` takes over). */\n fade(level: number, ms: number, done?: () => void): void;\n /** A fade is in flight: the channel leaves the volume setting alone until it ends. */\n readonly busy: boolean;\n /** Stops the sound and lets the element go. */\n dispose(): void;\n}\n\n/**\n * True where an `<audio>` ignores `volume` (iOS WebKit), so music has to go through Web Audio to be faded at all.\n * Newer iOS reads a set volume back while the speaker ignores it, so the probe alone misses it: an iPhone, iPod or an\n * iPad posing as a Mac is named first.\n */\nexport function elementVolumeLocked(): boolean {\n if (typeof Audio === 'undefined') return false;\n const nav = typeof navigator !== 'undefined' ? navigator : null;\n if (nav && (/iPhone|iPad|iPod/.test(nav.userAgent) || (/Macintosh/.test(nav.userAgent) && nav.maxTouchPoints > 1))) return true;\n const probe = new Audio();\n probe.volume = 0.5;\n return Math.abs(probe.volume - 0.5) > 0.01;\n}\n\nexport const clamp01 = (n: number): number => Math.max(0, Math.min(1, n));\n"],"names":["elementVolumeLocked","nav","probe","clamp01","n"],"mappings":"AAwBO,SAASA,IAA+B;AAC7C,MAAI,OAAO,QAAU,IAAa,QAAO;AACzC,QAAMC,IAAM,OAAO,YAAc,MAAc,YAAY;AAC3D,MAAIA,MAAQ,mBAAmB,KAAKA,EAAI,SAAS,KAAM,YAAY,KAAKA,EAAI,SAAS,KAAKA,EAAI,iBAAiB,GAAK,QAAO;AAC3H,QAAMC,IAAQ,IAAI,MAAA;AAClB,SAAAA,EAAM,SAAS,KACR,KAAK,IAAIA,EAAM,SAAS,GAAG,IAAI;AACxC;AAEO,MAAMC,IAAU,CAACC,MAAsB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAGA,CAAC,CAAC;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sampleBank.js","sources":["../../src/audio/sampleBank.ts"],"sourcesContent":["/**\n * The recorded takes of the sounds, registered by the game (`registerTakes`): `<sound>.mp3`, and `<sound>_2.mp3`…\n * when a sound has more than one. A\n * sound with takes plays one of them at random, never the same one twice running when it has more; a sound without\n * any — or whose takes have not decoded yet — falls back to its synth recipe in `SoundEngine`.\n *\n * Every take is cleaned once on decode (`cleanTake.ts`: mono, silence trimmed, one loudness), off the main thread\n * in `takeCleanerWorker.ts`, so `sounds.json`'s `fileGain` is the only mix knob. Measured on the first batch, peaks\n * alone left their loudness 20 dB apart.\n */\n\nimport { cleanSamples } from './cleanTake';\n\nconst TAKES: Record<string, string[]> = {};\n\n/**\n * Registers recorded takes by file path → url, e.g. a Vite `import.meta.glob('./sfx/*.mp3', { query: '?url',\n * import: 'default', eager: true })`. `hit.mp3`, `hit_2.mp3`… are takes of `hit`; sound names carry no digits.\n */\nexport function registerTakes(urls: Record<string, string>): void {\n for (const [path, url] of Object.entries(urls)) {\n const m = /(?:^|\\/)([a-z_]+?)(?:_\\d+)?\\.mp3$/.exec(path);\n if (m) (TAKES[m[1]!] ??= []).push(url);\n }\n}\n\n/** The sound has recorded takes in the build (decoded or not). */\nexport const hasTakes = (name: string): boolean => !!TAKES[name];\n\nexport class SampleBank {\n private readonly decoded = new Map<string, AudioBuffer[]>();\n private readonly loading = new Set<string>();\n private readonly last = new Map<string, number>();\n\n names(): string[] {\n return Object.keys(TAKES);\n }\n\n /** A decoded take, or null while none is ready (the caller then synthesises); starts loading on first ask. */\n take(ctx: AudioContext, name: string): AudioBuffer | null {\n const list = this.decoded.get(name);\n if (!list?.length) {\n this.load(ctx, name);\n return null;\n }\n let i = Math.floor(Math.random() * list.length);\n if (list.length > 1 && i === this.last.get(name)) i = (i + 1) % list.length;\n this.last.set(name, i);\n return list[i]!;\n }\n\n load(ctx: AudioContext, name: string): void {\n const urlsOf = TAKES[name];\n if (!urlsOf || this.loading.has(name)) return;\n this.loading.add(name);\n const list: AudioBuffer[] = [];\n this.decoded.set(name, list);\n for (const url of urlsOf) {\n fetch(url)\n .then((r) => r.arrayBuffer())\n .then((data) => ctx.decodeAudioData(data))\n .then((buf) => clean(buf))\n .then((data) => {\n if (!data) return;\n const out = ctx.createBuffer(1, data.length, ctx.sampleRate);\n out.copyToChannel(data as Float32Array<ArrayBuffer>, 0);\n list.push(out);\n })\n .catch(() => {});\n }\n }\n}\n\nlet worker: Worker | null | undefined;\nlet nextId = 0;\nconst waiting = new Map<number, (data: Float32Array | null) => void>();\n\n/** The cleaner worker, started on first use; null where workers cannot start (then takes are cleaned here). */\nfunction cleaner(): Worker | null {\n if (worker !== undefined) return worker;\n try {\n worker = new Worker(new URL('./takeCleanerWorker.ts', import.meta.url), { type: 'module' });\n worker.onmessage = (e: MessageEvent<{ id: number; data: Float32Array | null }>) => {\n waiting.get(e.data.id)?.(e.data.data);\n waiting.delete(e.data.id);\n };\n worker.onerror = () => {\n worker?.terminate();\n worker = null;\n for (const done of waiting.values()) done(null);\n waiting.clear();\n };\n } catch {\n worker = null;\n }\n return worker;\n}\n\n/** Takes handed over one per task: a fight's first blows ask for a dozen at once, and copying them all out in one\n * task still froze a frame. */\nlet turn: Promise<void> = Promise.resolve();\n\n/** A decoded take cleaned at the context's own rate (decodeAudioData resamples to it). */\nfunction clean(buf: AudioBuffer): Promise<Float32Array | null> {\n return new Promise((resolve) => {\n turn = turn\n .then(() => new Promise<void>((r) => setTimeout(r, 0)))\n .then(() => {\n send(buf).then(resolve, () => resolve(null));\n });\n });\n}\n\nfunction send(buf: AudioBuffer): Promise<Float32Array | null> {\n const channels = Array.from({ length: buf.numberOfChannels }, (_, c) => buf.getChannelData(c).slice());\n const w = cleaner();\n if (!w) return Promise.resolve(cleanSamples(channels, buf.sampleRate));\n const id = nextId++;\n return new Promise((resolve) => {\n waiting.set(id, resolve);\n w.postMessage({ id, channels, sampleRate: buf.sampleRate }, { transfer: channels.map((c) => c.buffer as ArrayBuffer) });\n });\n}\n"],"names":["TAKES","registerTakes","urls","path","url","m","hasTakes","name","SampleBank","ctx","list","i","urlsOf","r","data","buf","clean","out","worker","nextId","waiting","cleaner","done","turn","resolve","send","channels","_","c","w","cleanSamples","id"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"soundTable.js","sources":["../../src/audio/soundTable.ts"],"sourcesContent":["import type { SynthRecipe } from './synth';\n\n/**\n * The sounds the engine can play: synthesised recipes by name, plus the master share every sound plays at. Empty\n * until the game registers its own (`registerSounds`); the mapping from game happenings to sound names stays in\n * the game.\n */\nexport interface SoundTable {\n /** Share of the effects volume everything plays at: keeps full volume modest next to the music. */\n master: number;\n recipes: Record<string, SynthRecipe>;\n}\n\nexport const SOUNDS: SoundTable = { master: 1, recipes: {} };\n\nexport function registerSounds(table: Partial<SoundTable>): void {\n if (table.master !== undefined) SOUNDS.master = table.master;\n if (table.recipes) Object.assign(SOUNDS.recipes, table.recipes);\n}\n"],"names":["SOUNDS","registerSounds","table"],"mappings":"AAaO,MAAMA,IAAqB,EAAE,QAAQ,GAAG,SAAS,CAAA,EAAC;AAElD,SAASC,EAAeC,GAAkC;AAC/D,EAAIA,EAAM,WAAW,WAAWF,EAAO,SAASE,EAAM,SAClDA,EAAM,WAAS,OAAO,OAAOF,EAAO,SAASE,EAAM,OAAO;AAChE;"}
|
package/dist/audio/synth.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"synth.js","sources":["../../src/audio/synth.ts"],"sourcesContent":["/**\n * The combat sounds are written, not recorded: a few oscillator or noise layers rendered straight into a buffer\n * (`sounds.json` → `recipes`). Lo-fi on purpose — 22 kHz mono, short and soft — so they sit with the pixel art\n * instead of pretending to be a recording.\n */\nexport const SYNTH_RATE = 22050;\n\nexport interface SynthLayer {\n /** Waveform; `noise` is white noise, or sample-and-hold noise at `f` Hz when `f` is given (crunchier). */\n w: 'sine' | 'square' | 'tri' | 'saw' | 'noise';\n /** Pitch at the start and at the end of the layer (Hz), swept exponentially. */\n f?: [number, number];\n /** Length (s). */\n t: number;\n /** Delay before the layer starts (s). */\n d?: number;\n /** Attack (s). */\n at?: number;\n g: number;\n /** Decay curve exponent: 1 is linear, higher falls away faster. */\n c?: number;\n duty?: number;\n /** Low- and high-pass cutoff at the start and the end (Hz). */\n lp?: [number, number];\n hp?: [number, number];\n}\n\nexport interface SynthRecipe {\n layers: SynthLayer[];\n /** Random pitch spread per play (share of 1); the engine's default when absent. */\n jitter?: number;\n /** Level of the sound's recorded takes (`registerTakes`), which are normalised on decode; default 1. */\n fileGain?: number;\n}\n\nconst WAVES: Record<Exclude<SynthLayer['w'], 'noise'>, (p: number, duty: number) => number> = {\n sine: (p) => Math.sin(p * Math.PI * 2),\n square: (p, duty) => (p < duty ? 1 : -1),\n tri: (p) => 1 - 4 * Math.abs(p - 0.5),\n saw: (p) => 2 * p - 1,\n};\n\nconst sweep = (a: number, b: number, k: number): number => (a > 0 && b > 0 ? a * Math.pow(b / a, k) : a + (b - a) * k);\n\n/** A one-pole filter is enough for a stand-in, and it cannot ring or blow up the way a swept biquad can. */\nfunction onePole(cut: number): number {\n return 1 - Math.exp((-2 * Math.PI * Math.min(cut, SYNTH_RATE * 0.45)) / SYNTH_RATE);\n}\n\nexport function renderRecipe(recipe: SynthRecipe): Float32Array {\n const total = Math.max(...recipe.layers.map((l) => (l.d ?? 0) + l.t));\n const out = new Float32Array(Math.ceil(total * SYNTH_RATE) + 1);\n let seed = 0x9e3779b9;\n const rand = () => {\n seed ^= seed << 13;\n seed ^= seed >>> 17;\n seed ^= seed << 5;\n return ((seed >>> 0) / 0xffffffff) * 2 - 1;\n };\n for (const l of recipe.layers) {\n const start = Math.round((l.d ?? 0) * SYNTH_RATE);\n const n = Math.round(l.t * SYNTH_RATE);\n const at = Math.max(1, Math.round((l.at ?? 0.004) * SYNTH_RATE));\n let phase = 0;\n let held = 0;\n let lpState = 0;\n let hpState = 0;\n for (let i = 0; i < n && start + i < out.length; i++) {\n const k = i / n;\n const freq = l.f ? sweep(l.f[0], l.f[1], k) : 0;\n phase += freq / SYNTH_RATE;\n const p = phase - Math.floor(phase);\n let s: number;\n if (l.w === 'noise') {\n // sample-and-hold: a new value each time the phase wraps, which is what makes it crunch\n if (!l.f || p < freq / SYNTH_RATE || i === 0) held = rand();\n s = l.f ? held : rand();\n } else s = WAVES[l.w](p, l.duty ?? 0.5);\n if (l.lp) {\n lpState += (s - lpState) * onePole(sweep(l.lp[0], l.lp[1], k));\n s = lpState;\n }\n if (l.hp) {\n hpState += (s - hpState) * onePole(sweep(l.hp[0], l.hp[1], k));\n s -= hpState;\n }\n const env = i < at ? i / at : Math.pow(1 - (i - at) / Math.max(1, n - at), l.c ?? 2);\n out[start + i]! += s * env * l.g;\n }\n }\n // a stand-in never clips: peaks are brought back under full scale\n let peak = 0;\n for (const v of out) peak = Math.max(peak, Math.abs(v));\n if (peak > 0.95) for (let i = 0; i < out.length; i++) out[i]! *= 0.95 / peak;\n return out;\n}\n"],"names":["SYNTH_RATE","WAVES","p","duty","sweep","a","b","k","onePole","cut","renderRecipe","recipe","total","l","out","seed","rand","start","n","at","phase","held","lpState","hpState","i","freq","s","env","peak","v"],"mappings":"AAKO,MAAMA,IAAa,OA8BpBC,IAAwF;AAAA,EAC5F,MAAM,CAACC,MAAM,KAAK,IAAIA,IAAI,KAAK,KAAK,CAAC;AAAA,EACrC,QAAQ,CAACA,GAAGC,MAAUD,IAAIC,IAAO,IAAI;AAAA,EACrC,KAAK,CAACD,MAAM,IAAI,IAAI,KAAK,IAAIA,IAAI,GAAG;AAAA,EACpC,KAAK,CAACA,MAAM,IAAIA,IAAI;AACtB,GAEME,IAAQ,CAACC,GAAWC,GAAWC,MAAuBF,IAAI,KAAKC,IAAI,IAAID,IAAI,KAAK,IAAIC,IAAID,GAAGE,CAAC,IAAIF,KAAKC,IAAID,KAAKE;AAGpH,SAASC,EAAQC,GAAqB;AACpC,SAAO,IAAI,KAAK,IAAK,KAAK,KAAK,KAAK,KAAK,IAAIA,GAAK,QAAa,IAAI,IAAK,KAAU;AACpF;AAEO,SAASC,EAAaC,GAAmC;AAC9D,QAAMC,IAAQ,KAAK,IAAI,GAAGD,EAAO,OAAO,IAAI,CAACE,OAAOA,EAAE,KAAK,KAAKA,EAAE,CAAC,CAAC,GAC9DC,IAAM,IAAI,aAAa,KAAK,KAAKF,IAAQ,KAAU,IAAI,CAAC;AAC9D,MAAIG,IAAO;AACX,QAAMC,IAAO,OACXD,KAAQA,KAAQ,IAChBA,KAAQA,MAAS,IACjBA,KAAQA,KAAQ,IACPA,MAAS,KAAK,aAAc,IAAI;AAE3C,aAAWF,KAAKF,EAAO,QAAQ;AAC7B,UAAMM,IAAQ,KAAK,OAAOJ,EAAE,KAAK,KAAK,KAAU,GAC1CK,IAAI,KAAK,MAAML,EAAE,IAAI,KAAU,GAC/BM,IAAK,KAAK,IAAI,GAAG,KAAK,OAAON,EAAE,MAAM,QAAS,KAAU,CAAC;AAC/D,QAAIO,IAAQ,GACRC,IAAO,GACPC,IAAU,GACVC,IAAU;AACd,aAASC,IAAI,GAAGA,IAAIN,KAAKD,IAAQO,IAAIV,EAAI,QAAQU,KAAK;AACpD,YAAMjB,IAAIiB,IAAIN,GACRO,IAAOZ,EAAE,IAAIT,EAAMS,EAAE,EAAE,CAAC,GAAGA,EAAE,EAAE,CAAC,GAAGN,CAAC,IAAI;AAC9C,MAAAa,KAASK,IAAO;AAChB,YAAMvB,IAAIkB,IAAQ,KAAK,MAAMA,CAAK;AAClC,UAAIM;AACJ,MAAIb,EAAE,MAAM,YAEN,CAACA,EAAE,KAAKX,IAAIuB,IAAO,SAAcD,MAAM,OAAGH,IAAOL,EAAA,IACrDU,IAAIb,EAAE,IAAIQ,IAAOL,EAAA,SACRf,EAAMY,EAAE,CAAC,EAAEX,GAAGW,EAAE,QAAQ,GAAG,GAClCA,EAAE,OACJS,MAAYI,IAAIJ,KAAWd,EAAQJ,EAAMS,EAAE,GAAG,CAAC,GAAGA,EAAE,GAAG,CAAC,GAAGN,CAAC,CAAC,GAC7DmB,IAAIJ,IAEFT,EAAE,OACJU,MAAYG,IAAIH,KAAWf,EAAQJ,EAAMS,EAAE,GAAG,CAAC,GAAGA,EAAE,GAAG,CAAC,GAAGN,CAAC,CAAC,GAC7DmB,KAAKH;AAEP,YAAMI,IAAMH,IAAIL,IAAKK,IAAIL,IAAK,KAAK,IAAI,KAAKK,IAAIL,KAAM,KAAK,IAAI,GAAGD,IAAIC,CAAE,GAAGN,EAAE,KAAK,CAAC;AACnF,MAAAC,EAAIG,IAAQO,CAAC,KAAME,IAAIC,IAAMd,EAAE;AAAA,IACjC;AAAA,EACF;AAEA,MAAIe,IAAO;AACX,aAAWC,KAAKf,EAAK,CAAAc,IAAO,KAAK,IAAIA,GAAM,KAAK,IAAIC,CAAC,CAAC;AACtD,MAAID,IAAO,KAAM,UAASJ,IAAI,GAAGA,IAAIV,EAAI,QAAQU,IAAK,CAAAV,EAAIU,CAAC,KAAM,OAAOI;AACxE,SAAOd;AACT;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BitmapLabel.js","sources":["../../src/core/BitmapLabel.ts"],"sourcesContent":["import { BitmapFont, BitmapText, DynamicBitmapFont } from 'pixi.js';\nimport { GameObject } from './gameObject';\n\nconst INSTALLED = new Set<string>();\n\n/**\n * The glyphs baked into every label font: printable ASCII (space to `~`) plus whatever a game adds (`addLabelGlyphs`)\n * for the words its labels print, so no glyph is baked mid-frame onto a fresh page (a new canvas a page, and iOS\n * hands out none once its canvas memory is spent).\n */\nconst CHARS: (string | [string, string])[] = [[' ', '~']];\n\n/**\n * Adds letters every label font made **after this call** bakes ahead — the accented letters of a language the labels\n * print (German's `'äöüß'`, say), an arrow, a star. A font already installed keeps the glyphs it was made with, so\n * call it once at start-up, before the first label or `FloatingText.warm`.\n *\n * Without it, a label font holds ASCII only: a letter outside it is still drawn (Pixi bakes it when first printed),\n * but on that frame, onto a new glyph page — the stall and the canvas memory this module exists to avoid. For one\n * font's own extra letters, pass them to `installLabelFont` (or `Label.bitmap`, `FloatingText`'s `glyphs`) instead.\n */\nexport function addLabelGlyphs(letters: string): void {\n CHARS.push(letters);\n}\n/**\n * The side of one glyph page in CSS pixels; a page's canvas is this × the resolution. Pixi's default, 512, made every\n * label font a 2048² canvas at resolution 4 (16 MB each, ten of them at boot); the numbers' glyphs fill a few pages of\n * this size instead.\n */\nconst PAGE_SIZE = 128;\n\n/**\n * Rasterises one label font once and returns its name. Called ahead of time for the damage numbers: baking a font on\n * its first number froze the first frame that printed one by ~60 ms. `glyphs` are letters this font bakes besides\n * ASCII and `addLabelGlyphs`' (a font with its own is a font of its own: pass the same `glyphs` wherever it is used).\n */\nexport function installLabelFont(fontFamily: string, size: number, color: string, stroke: string, strokeWidth: number, resolution: number, glyphs = ''): string {\n const name = `label|${fontFamily}|${size}|${color}|${stroke}|${strokeWidth}|${resolution}${glyphs ? `|${glyphs}` : ''}`;\n if (!INSTALLED.has(name)) {\n // `install` takes no page size of its own: it reads the dynamic font's default, set for this call only\n const defaults = DynamicBitmapFont.defaultOptions;\n const was = defaults.textureSize;\n defaults.textureSize = PAGE_SIZE;\n try {\n BitmapFont.install({\n name,\n chars: glyphs ? [...CHARS, glyphs] : CHARS,\n resolution,\n style: { fontFamily, fontSize: size, fill: color, stroke: { color: stroke, width: strokeWidth, join: 'round' } },\n });\n } finally {\n defaults.textureSize = was;\n }\n INSTALLED.add(name);\n }\n return name;\n}\n\n/**\n * Text drawn from a bitmap font: the glyphs are rasterised once per font, so changing the string costs no canvas\n * redraw and no texture upload, and every label of one font batches with the others. For the floating numbers, which\n * a crowded fight throws dozens of a second — a plain `Text` repaints its canvas at resolution 4 and uploads it on\n * every change.\n */\nexport class BitmapLabel extends GameObject(BitmapText) {\n /**\n * `fontFamily` must already be loaded (`document.fonts.load`), or the glyphs bake in the fallback.\n * Styled like `worldText`: the same font, size, colour and outline, rasterised at the same resolution. `glyphs`:\n * letters beyond ASCII it prints (see `installLabelFont`).\n */\n constructor(x: number, y: number, text: string, fontFamily: string, size: number, color: string, stroke: string, strokeWidth: number, resolution: number, glyphs = '') {\n const name = installLabelFont(fontFamily, size, color, stroke, strokeWidth, resolution, glyphs);\n super({ text, style: { fontFamily: name, fontSize: size } });\n this.position.set(x, y);\n }\n\n setOrigin(x = 0.5, y = x): this {\n this.anchor.set(x, y);\n return this;\n }\n\n setText(text: string): this {\n this.text = text;\n return this;\n }\n}\n"],"names":["INSTALLED","CHARS","addLabelGlyphs","letters","PAGE_SIZE","installLabelFont","fontFamily","size","color","stroke","strokeWidth","resolution","glyphs","name","defaults","DynamicBitmapFont","was","BitmapFont","BitmapLabel","GameObject","BitmapText","x","y","text"],"mappings":";;AAGA,MAAMA,wBAAgB,IAAA,GAOhBC,IAAuC,CAAC,CAAC,KAAK,GAAG,CAAC;AAWjD,SAASC,EAAeC,GAAuB;AACpD,EAAAF,EAAM,KAAKE,CAAO;AACpB;AAMA,MAAMC,IAAY;AAOX,SAASC,EAAiBC,GAAoBC,GAAcC,GAAeC,GAAgBC,GAAqBC,GAAoBC,IAAS,IAAY;AAC9J,QAAMC,IAAO,SAASP,CAAU,IAAIC,CAAI,IAAIC,CAAK,IAAIC,CAAM,IAAIC,CAAW,IAAIC,CAAU,GAAGC,IAAS,IAAIA,CAAM,KAAK,EAAE;AACrH,MAAI,CAACZ,EAAU,IAAIa,CAAI,GAAG;AAExB,UAAMC,IAAWC,EAAkB,gBAC7BC,IAAMF,EAAS;AACrB,IAAAA,EAAS,cAAcV;AACvB,QAAI;AACF,MAAAa,EAAW,QAAQ;AAAA,QACjB,MAAAJ;AAAA,QACA,OAAOD,IAAS,CAAC,GAAGX,GAAOW,CAAM,IAAIX;AAAA,QACrC,YAAAU;AAAA,QACA,OAAO,EAAE,YAAAL,GAAY,UAAUC,GAAM,MAAMC,GAAO,QAAQ,EAAE,OAAOC,GAAQ,OAAOC,GAAa,MAAM,UAAQ;AAAA,MAAE,CAChH;AAAA,IACH,UAAA;AACE,MAAAI,EAAS,cAAcE;AAAA,IACzB;AACA,IAAAhB,EAAU,IAAIa,CAAI;AAAA,EACpB;AACA,SAAOA;AACT;AAQO,MAAMK,UAAoBC,EAAWC,CAAU,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtD,YAAYC,GAAWC,GAAWC,GAAcjB,GAAoBC,GAAcC,GAAeC,GAAgBC,GAAqBC,GAAoBC,IAAS,IAAI;AACrK,UAAMC,IAAOR,EAAiBC,GAAYC,GAAMC,GAAOC,GAAQC,GAAaC,GAAYC,CAAM;AAC9F,UAAM,EAAE,MAAAW,GAAM,OAAO,EAAE,YAAYV,GAAM,UAAUN,EAAA,GAAQ,GAC3D,KAAK,SAAS,IAAIc,GAAGC,CAAC;AAAA,EACxB;AAAA,EAEA,UAAUD,IAAI,KAAKC,IAAID,GAAS;AAC9B,gBAAK,OAAO,IAAIA,GAAGC,CAAC,GACb;AAAA,EACT;AAAA,EAEA,QAAQC,GAAoB;AAC1B,gBAAK,OAAOA,GACL;AAAA,EACT;AACF;"}
|
package/dist/core/Camera.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Camera.js","sources":["../../src/core/Camera.ts"],"sourcesContent":["import type { Container } from 'pixi.js';\nimport { Rectangle } from './Rectangle';\n\nexport interface TrackOptions {\n /**\n * How much of the way to the target the camera closes each 60th of a second (0–1, default 1: locked on). 0.1 is a\n * soft follow; the rate is the same at any frame rate.\n */\n lerp?: number;\n /** A box round the screen's centre (world px) the target may move in without the camera moving. */\n deadzoneWidth?: number;\n deadzoneHeight?: number;\n /** Where on the target to look (world px from its position), e.g. a little above a body's feet. */\n offsetX?: number;\n offsetY?: number;\n}\n\nexport interface ViewRect {\n x: number;\n y: number;\n width: number;\n height: number;\n right: number;\n bottom: number;\n}\n\n/**\n * Moves and zooms the world layer. `scrollX/scrollY` is the world point at the top-left of the screen, so\n * `round(scroll * zoom) / zoom` snaps the view to whole screen pixels.\n */\nexport class Camera {\n zoom = 1;\n /** The zoom `reset` goes back to: the engine sets it from its `zoom` option (default 1). */\n defaultZoom = 1;\n /** Backing pixels per screen pixel (the renderer's resolution, `zoomResolution`): what `snap` rounds to. */\n resolution = 1;\n /** Lands the view on whole backing pixels (pixel art); off, the world moves smoothly between them. */\n roundPixels = false;\n scrollX = 0;\n scrollY = 0;\n width = 0;\n height = 0;\n private bounds: { x: number; y: number; width: number; height: number } | null = null;\n private shakeUntil = 0;\n private shakeMs = 1;\n private shakeAngle: number | undefined = undefined;\n private shakeIntensity = 0;\n private flashRect: Rectangle | null = null;\n private flashStart = 0;\n private flashMs = 1;\n private flashPeak = 1;\n /** Screen-layer containers laid over the world with its exact transform, shake included (see `follow`). */\n private readonly followers = new Set<Container>();\n private target: { readonly x: number; readonly y: number } | null = null;\n private trackOpts: Required<TrackOptions> = { lerp: 1, deadzoneWidth: 0, deadzoneHeight: 0, offsetX: 0, offsetY: 0 };\n private lastApply = -1;\n\n constructor(\n private readonly world: Container,\n private readonly screen: Container,\n ) {}\n\n get worldView(): ViewRect {\n const width = this.width / this.zoom;\n const height = this.height / this.zoom;\n return { x: this.scrollX, y: this.scrollY, width, height, right: this.scrollX + width, bottom: this.scrollY + height };\n }\n\n setZoom(z: number): this {\n this.zoom = z;\n return this;\n }\n\n /** A world coordinate snapped to the nearest backing pixel: one screen pixel, or a half at ×1.5. */\n snap(v: number): number {\n const k = this.zoom * this.resolution;\n return Math.round(v * k) / k;\n }\n\n setBounds(x: number, y: number, width: number, height: number): this {\n this.bounds = { x, y, width, height };\n return this;\n }\n\n /** Lets the view go anywhere again. */\n clearBounds(): this {\n this.bounds = null;\n return this;\n }\n\n /**\n * Back to how the engine made it: no target, no bounds, `defaultZoom`, scrolled to 0, no shake or flash. The camera\n * is the engine's, shared by every scene, so a level's target and bounds would otherwise hold over the next scene\n * (a title screen kept following a hero that no longer existed). `SceneManager.replace` calls it between the old\n * scenes leaving and the new one entering; `push` and `pop` leave the camera alone, since the scene under a pause\n * menu still wants its view when it comes back. Layers added with `follow` are kept: they belong to tools that\n * outlive scenes (a debug overlay), not to a scene.\n */\n reset(): this {\n this.target = null;\n this.bounds = null;\n this.zoom = this.defaultZoom;\n this.scrollX = 0;\n this.scrollY = 0;\n this.shakeUntil = 0;\n this.shakeIntensity = 0;\n if (this.flashRect && !this.flashRect.destroyed) this.flashRect.setVisible(false);\n return this;\n }\n\n /** The tracked target, if any. */\n get tracking(): { readonly x: number; readonly y: number } | null {\n return this.target;\n }\n\n /** The bounds the view is kept inside, if any. */\n get boundsRect(): { readonly x: number; readonly y: number; readonly width: number; readonly height: number } | null {\n return this.bounds;\n }\n\n centerOn(x: number, y: number): this {\n this.scrollX = x - this.width / this.zoom / 2;\n this.scrollY = y - this.height / this.zoom / 2;\n this.clamp();\n return this;\n }\n\n setScroll(x: number, y: number): this {\n this.scrollX = x;\n this.scrollY = y;\n this.clamp();\n return this;\n }\n\n /**\n * Screen shake: `intensity` is a fraction of the screen size, like before. With an `angle` (the way a blow travelled)\n * the shake runs back and forth along it instead of at random, so a hit is felt coming from somewhere.\n */\n shake(ms: number, intensity: number, angle?: number): void {\n const now = performance.now();\n if (now < this.shakeUntil && intensity < this.shakeIntensity) return;\n this.shakeUntil = now + ms;\n this.shakeMs = ms;\n this.shakeIntensity = intensity;\n this.shakeAngle = angle;\n }\n\n /**\n * Keeps a container on the screen layer placed exactly over the world layer — zoom, scroll and this frame's\n * shake — for things drawn in world units above the screen-layer night (fireflies). Written in `apply`, after\n * the scene updated, so it never lags the world by a frame or misses a shake the world takes.\n */\n follow(layer: Container): void {\n this.followers.add(layer);\n }\n\n unfollow(layer: Container): void {\n this.followers.delete(layer);\n }\n\n /** Full-screen colour flash starting at `peak` alpha and fading out over `ms`. */\n flash(ms: number, r: number, g: number, b: number, _force = false, peak = 1): void {\n if (!this.flashRect || this.flashRect.destroyed) {\n this.flashRect = new Rectangle(1, 1, 0xffffff).setOrigin(0, 0);\n this.screen.addChild(this.flashRect);\n }\n this.flashRect.resize(this.width, this.height).setFillStyle((r << 16) | (g << 8) | b).setAlpha(peak).setVisible(true);\n this.flashStart = performance.now();\n this.flashMs = ms;\n this.flashPeak = peak;\n }\n\n /**\n * Keeps `target` (anything with `x` and `y`, read every frame: an entity's position, a sprite) in view, inside the\n * bounds if there are any. Replaces any target before.\n */\n track(target: { readonly x: number; readonly y: number }, options: TrackOptions = {}): this {\n this.target = target;\n this.trackOpts = { lerp: 1, deadzoneWidth: 0, deadzoneHeight: 0, offsetX: 0, offsetY: 0, ...options };\n if ((options.lerp ?? 1) >= 1) this.centerOn(target.x + this.trackOpts.offsetX, target.y + this.trackOpts.offsetY);\n return this;\n }\n\n untrack(): this {\n this.target = null;\n return this;\n }\n\n /** Moves the view toward the tracked target for a frame `dtMs` long. */\n private follow1(dtMs: number): void {\n const t = this.target;\n if (!t) return;\n const o = this.trackOpts;\n const vw = this.width / this.zoom;\n const vh = this.height / this.zoom;\n const cx = this.scrollX + vw / 2;\n const cy = this.scrollY + vh / 2;\n const tx = t.x + o.offsetX;\n const ty = t.y + o.offsetY;\n // only the part of the target's offset outside the deadzone moves the camera\n const hx = o.deadzoneWidth / 2;\n const hy = o.deadzoneHeight / 2;\n const dx = tx - cx > hx ? tx - cx - hx : tx - cx < -hx ? tx - cx + hx : 0;\n const dy = ty - cy > hy ? ty - cy - hy : ty - cy < -hy ? ty - cy + hy : 0;\n const k = o.lerp >= 1 ? 1 : 1 - Math.pow(1 - Math.max(0, o.lerp), dtMs / (1000 / 60));\n this.scrollX += dx * k;\n this.scrollY += dy * k;\n this.clamp();\n }\n\n /** Writes the camera into the world layer; called once per frame after the scene updated. */\n apply(now: number): void {\n const dtMs = this.lastApply < 0 ? 1000 / 60 : Math.min(100, now - this.lastApply);\n this.lastApply = now;\n this.follow1(dtMs);\n let ox = 0;\n let oy = 0;\n if (now < this.shakeUntil) {\n const k = (this.shakeUntil - now) / this.shakeMs;\n if (this.shakeAngle !== undefined) {\n // along the blow: thrown the way it went, then rocking back, a little less each beat\n const swing = Math.cos(((now - (this.shakeUntil - this.shakeMs)) / 28) * Math.PI) * this.shakeIntensity * this.width * k;\n ox = Math.round(Math.cos(this.shakeAngle) * swing);\n oy = Math.round(Math.sin(this.shakeAngle) * swing);\n } else {\n ox = Math.round((Math.random() * 2 - 1) * this.shakeIntensity * this.width * k);\n oy = Math.round((Math.random() * 2 - 1) * this.shakeIntensity * this.height * k);\n }\n }\n this.world.scale.set(this.zoom);\n const k = this.zoom * this.resolution;\n const sx = this.roundPixels ? Math.round(this.scrollX * k) / this.resolution : this.scrollX * this.zoom;\n const sy = this.roundPixels ? Math.round(this.scrollY * k) / this.resolution : this.scrollY * this.zoom;\n this.world.position.set(-sx + ox, -sy + oy);\n for (const f of this.followers) {\n f.scale.set(this.zoom);\n f.position.copyFrom(this.world.position);\n }\n if (this.flashRect?.visible) {\n const t = (now - this.flashStart) / this.flashMs;\n if (t >= 1) this.flashRect.setVisible(false);\n else this.flashRect.alpha = this.flashPeak * (1 - t);\n }\n }\n\n private clamp(): void {\n const b = this.bounds;\n if (!b) return;\n const vw = this.width / this.zoom;\n const vh = this.height / this.zoom;\n this.scrollX = vw >= b.width ? b.x + (b.width - vw) / 2 : Math.min(Math.max(this.scrollX, b.x), b.x + b.width - vw);\n this.scrollY = vh >= b.height ? b.y + (b.height - vh) / 2 : Math.min(Math.max(this.scrollY, b.y), b.y + b.height - vh);\n }\n}\n"],"names":["Camera","world","screen","width","height","z","v","k","x","y","ms","intensity","angle","now","layer","r","g","b","_force","peak","Rectangle","target","options","dtMs","t","o","vw","vh","cx","cy","tx","ty","hx","hy","dx","dy","ox","oy","swing","sx","sy","f"],"mappings":"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"}
|
package/dist/core/Clock.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Clock.js","sources":["../../src/core/Clock.ts"],"sourcesContent":["interface Timer {\n at: number;\n fn: () => void;\n removed: boolean;\n}\n\n/** Scene time and one-shot timers, advanced by the frame loop. */\nexport class Clock {\n now = performance.now();\n private timers: Timer[] = [];\n\n delayedCall(ms: number, fn: () => void): { remove: () => void } {\n const t: Timer = { at: this.now + ms, fn, removed: false };\n this.timers.push(t);\n return { remove: () => void (t.removed = true) };\n }\n\n update(now: number): void {\n this.now = now;\n const due = this.timers.filter((t) => !t.removed && t.at <= now);\n if (!due.length) return;\n this.timers = this.timers.filter((t) => !t.removed && t.at > now);\n for (const t of due) {\n // one timer that throws does not take the others due this frame with it\n try {\n t.fn();\n } catch (err) {\n console.error('[timer] failed', err);\n }\n }\n }\n\n clear(): void {\n this.timers = [];\n }\n}\n"],"names":["Clock","ms","fn","t","now","due","err"],"mappings":"AAOO,MAAMA,EAAM;AAAA,EACjB,MAAM,YAAY,IAAA;AAAA,EACV,SAAkB,CAAA;AAAA,EAE1B,YAAYC,GAAYC,GAAwC;AAC9D,UAAMC,IAAW,EAAE,IAAI,KAAK,MAAMF,GAAI,IAAAC,GAAI,SAAS,GAAA;AACnD,gBAAK,OAAO,KAAKC,CAAC,GACX,EAAE,QAAQ,MAAA;AAAM,MAAMA,EAAE,UAAU;AAAA,MAAA;AAAA,EAC3C;AAAA,EAEA,OAAOC,GAAmB;AACxB,SAAK,MAAMA;AACX,UAAMC,IAAM,KAAK,OAAO,OAAO,CAACF,MAAM,CAACA,EAAE,WAAWA,EAAE,MAAMC,CAAG;AAC/D,QAAKC,EAAI,QACT;AAAA,WAAK,SAAS,KAAK,OAAO,OAAO,CAACF,MAAM,CAACA,EAAE,WAAWA,EAAE,KAAKC,CAAG;AAChE,iBAAWD,KAAKE;AAEd,YAAI;AACF,UAAAF,EAAE,GAAA;AAAA,QACJ,SAASG,GAAK;AACZ,kBAAQ,MAAM,kBAAkBA,CAAG;AAAA,QACrC;AAAA;AAAA,EAEJ;AAAA,EAEA,QAAc;AACZ,SAAK,SAAS,CAAA;AAAA,EAChB;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Graphics.js","sources":["../../src/core/Graphics.ts"],"sourcesContent":["import { Container, Graphics as PixiGraphics } from 'pixi.js';\nimport { GameObject } from './gameObject';\n\nexport interface Vec2 {\n x: number;\n y: number;\n}\n\n/**\n * Immediate-mode vector drawing with a current fill and line style (fillStyle + fillRect), on top of Pixi's\n * shape-then-fill Graphics. Wrapped rather than subclassed: Pixi's Graphics owns `fillStyle` as a property.\n */\nexport class Graphics extends GameObject(Container) {\n private readonly g = new PixiGraphics();\n private fill = { color: 0xffffff, alpha: 1 };\n private line = { width: 1, color: 0xffffff, alpha: 1 };\n\n constructor() {\n super();\n this.addChild(this.g);\n }\n\n fillStyle(color: number, alpha = 1): this {\n this.fill = { color, alpha };\n return this;\n }\n\n lineStyle(width: number, color: number, alpha = 1): this {\n this.line = { width, color, alpha };\n return this;\n }\n\n clear(): this {\n this.g.clear();\n return this;\n }\n\n fillRect(x: number, y: number, w: number, h: number): this {\n this.g.rect(x, y, w, h).fill(this.fill);\n return this;\n }\n\n fillEllipse(x: number, y: number, w: number, h: number): this {\n this.g.ellipse(x, y, w / 2, h / 2).fill(this.fill);\n return this;\n }\n\n strokeEllipse(x: number, y: number, w: number, h: number): this {\n this.g.ellipse(x, y, w / 2, h / 2).stroke(this.line);\n return this;\n }\n\n fillCircle(x: number, y: number, r: number): this {\n this.g.circle(x, y, r).fill(this.fill);\n return this;\n }\n\n strokeCircle(x: number, y: number, r: number): this {\n this.g.circle(x, y, r).stroke(this.line);\n return this;\n }\n\n lineBetween(x1: number, y1: number, x2: number, y2: number): this {\n this.g.moveTo(x1, y1).lineTo(x2, y2).stroke(this.line);\n return this;\n }\n\n fillPoints(points: Vec2[], _closeShape = true): this {\n this.g.poly(points.flatMap((p) => [p.x, p.y]), true).fill(this.fill);\n return this;\n }\n\n strokePoints(points: Vec2[], closeShape = false): this {\n this.g.poly(points.flatMap((p) => [p.x, p.y]), closeShape).stroke(this.line);\n return this;\n }\n\n /** A pie slice from the centre, for cone telegraphs. */\n slice(x: number, y: number, r: number, start: number, end: number, anticlockwise = false): this {\n this.g.moveTo(x, y).arc(x, y, r, start, end, anticlockwise).closePath();\n return this;\n }\n\n beginPath(): this {\n this.g.beginPath();\n return this;\n }\n\n moveTo(x: number, y: number): this {\n this.g.moveTo(x, y);\n return this;\n }\n\n lineTo(x: number, y: number): this {\n this.g.lineTo(x, y);\n return this;\n }\n\n closePath(): this {\n this.g.closePath();\n return this;\n }\n\n fillPath(): this {\n this.g.fill(this.fill);\n return this;\n }\n\n strokePath(): this {\n this.g.stroke(this.line);\n return this;\n }\n}\n"],"names":["Graphics","GameObject","Container","PixiGraphics","color","alpha","width","x","y","w","h","r","x1","y1","x2","y2","points","_closeShape","p","closeShape","start","end","anticlockwise"],"mappings":"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"}
|
package/dist/core/Group.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Group.js","sources":["../../src/core/Group.ts"],"sourcesContent":["import { Container, type Container as PixiContainer } from 'pixi.js';\nimport { GameObject } from './gameObject';\n\n/** A container of world objects, drawn in list order (children do not sort by depth). */\nexport class Group extends GameObject(Container) {\n get list(): PixiContainer[] {\n return this.children;\n }\n\n add(child: PixiContainer | PixiContainer[]): this {\n for (const c of Array.isArray(child) ? child : [child]) this.addChild(c);\n return this;\n }\n\n addAt(child: PixiContainer, index: number): this {\n this.addChildAt(child, Math.min(index, this.children.length));\n return this;\n }\n\n remove(child: PixiContainer, destroy = false): this {\n if (child.parent === this) this.removeChild(child);\n if (destroy) child.destroy({ children: true, context: true });\n return this;\n }\n\n /** Puts `child` directly below `other` in the draw order. */\n moveBelow(child: PixiContainer, other: PixiContainer): this {\n if (child.parent !== this || other.parent !== this || child === other) return this;\n this.removeChild(child);\n this.addChildAt(child, this.getChildIndex(other));\n return this;\n }\n\n /** Puts `child` directly above `other` in the draw order. */\n moveAbove(child: PixiContainer, other: PixiContainer): this {\n if (child.parent !== this || other.parent !== this || child === other) return this;\n this.removeChild(child);\n this.addChildAt(child, this.getChildIndex(other) + 1);\n return this;\n }\n}\n"],"names":["Group","GameObject","Container","child","c","index","destroy","other"],"mappings":";;AAIO,MAAMA,UAAcC,EAAWC,CAAS,EAAE;AAAA,EAC/C,IAAI,OAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAIC,GAA8C;AAChD,eAAWC,KAAK,MAAM,QAAQD,CAAK,IAAIA,IAAQ,CAACA,CAAK,EAAG,MAAK,SAASC,CAAC;AACvE,WAAO;AAAA,EACT;AAAA,EAEA,MAAMD,GAAsBE,GAAqB;AAC/C,gBAAK,WAAWF,GAAO,KAAK,IAAIE,GAAO,KAAK,SAAS,MAAM,CAAC,GACrD;AAAA,EACT;AAAA,EAEA,OAAOF,GAAsBG,IAAU,IAAa;AAClD,WAAIH,EAAM,WAAW,QAAM,KAAK,YAAYA,CAAK,GAC7CG,OAAe,QAAQ,EAAE,UAAU,IAAM,SAAS,IAAM,GACrD;AAAA,EACT;AAAA;AAAA,EAGA,UAAUH,GAAsBI,GAA4B;AAC1D,WAAIJ,EAAM,WAAW,QAAQI,EAAM,WAAW,QAAQJ,MAAUI,IAAc,QAC9E,KAAK,YAAYJ,CAAK,GACtB,KAAK,WAAWA,GAAO,KAAK,cAAcI,CAAK,CAAC,GACzC;AAAA,EACT;AAAA;AAAA,EAGA,UAAUJ,GAAsBI,GAA4B;AAC1D,WAAIJ,EAAM,WAAW,QAAQI,EAAM,WAAW,QAAQJ,MAAUI,IAAc,QAC9E,KAAK,YAAYJ,CAAK,GACtB,KAAK,WAAWA,GAAO,KAAK,cAAcI,CAAK,IAAI,CAAC,GAC7C;AAAA,EACT;AACF;"}
|
package/dist/core/Image.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Image.js","sources":["../../src/core/Image.ts"],"sourcesContent":["import { Sprite } from 'pixi.js';\nimport { GameObject } from './gameObject';\nimport type { TextureRegistry } from './TextureRegistry';\n\n/** A textured sprite, centred by default; frames are looked up by name in the texture registry. */\nexport class Image extends GameObject(Sprite) {\n textureKey: string;\n frameName: string;\n\n constructor(\n private readonly registry: TextureRegistry,\n x: number,\n y: number,\n key: string,\n frame?: string | number,\n ) {\n super(registry.get(key, frame));\n this.textureKey = key;\n this.frameName = frame === undefined ? '' : String(frame);\n this.anchor.set(0.5);\n this.position.set(x, y);\n }\n\n get originX(): number {\n return this.anchor.x;\n }\n\n get originY(): number {\n return this.anchor.y;\n }\n\n /** Width of the frame itself, unscaled. */\n get frameWidth(): number {\n return this.texture.orig.width;\n }\n\n get frameHeight(): number {\n return this.texture.orig.height;\n }\n\n get displayWidth(): number {\n return Math.abs(this.frameWidth * this.scaleX);\n }\n\n set displayWidth(w: number) {\n this.scaleX = w / this.frameWidth;\n }\n\n get displayHeight(): number {\n return Math.abs(this.frameHeight * this.scaleY);\n }\n\n set displayHeight(h: number) {\n this.scaleY = h / this.frameHeight;\n }\n\n setOrigin(x = 0.5, y = x): this {\n this.anchor.set(x, y);\n return this;\n }\n\n setDisplaySize(w: number, h: number): this {\n return this.setScale(w / this.frameWidth, h / this.frameHeight);\n }\n\n setFrame(frame: string | number): this {\n this.frameName = String(frame);\n this.texture = this.registry.get(this.textureKey, frame);\n return this;\n }\n\n setTexture(key: string, frame?: string | number): this {\n this.textureKey = key;\n this.frameName = frame === undefined ? '' : String(frame);\n this.texture = this.registry.get(key, frame);\n return this;\n }\n}\n"],"names":["Image","GameObject","Sprite","registry","x","y","key","frame","w","h"],"mappings":";;AAKO,MAAMA,UAAcC,EAAWC,CAAM,EAAE;AAAA,EAI5C,YACmBC,GACjBC,GACAC,GACAC,GACAC,GACA;AACA,UAAMJ,EAAS,IAAIG,GAAKC,CAAK,CAAC,GANb,KAAA,WAAAJ,GAOjB,KAAK,aAAaG,GAClB,KAAK,YAAYC,MAAU,SAAY,KAAK,OAAOA,CAAK,GACxD,KAAK,OAAO,IAAI,GAAG,GACnB,KAAK,SAAS,IAAIH,GAAGC,CAAC;AAAA,EACxB;AAAA,EAXmB;AAAA,EAJnB;AAAA,EACA;AAAA,EAgBA,IAAI,UAAkB;AACpB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA,EAEA,IAAI,UAAkB;AACpB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA,EAGA,IAAI,aAAqB;AACvB,WAAO,KAAK,QAAQ,KAAK;AAAA,EAC3B;AAAA,EAEA,IAAI,cAAsB;AACxB,WAAO,KAAK,QAAQ,KAAK;AAAA,EAC3B;AAAA,EAEA,IAAI,eAAuB;AACzB,WAAO,KAAK,IAAI,KAAK,aAAa,KAAK,MAAM;AAAA,EAC/C;AAAA,EAEA,IAAI,aAAaG,GAAW;AAC1B,SAAK,SAASA,IAAI,KAAK;AAAA,EACzB;AAAA,EAEA,IAAI,gBAAwB;AAC1B,WAAO,KAAK,IAAI,KAAK,cAAc,KAAK,MAAM;AAAA,EAChD;AAAA,EAEA,IAAI,cAAcC,GAAW;AAC3B,SAAK,SAASA,IAAI,KAAK;AAAA,EACzB;AAAA,EAEA,UAAUL,IAAI,KAAKC,IAAID,GAAS;AAC9B,gBAAK,OAAO,IAAIA,GAAGC,CAAC,GACb;AAAA,EACT;AAAA,EAEA,eAAeG,GAAWC,GAAiB;AACzC,WAAO,KAAK,SAASD,IAAI,KAAK,YAAYC,IAAI,KAAK,WAAW;AAAA,EAChE;AAAA,EAEA,SAASF,GAA8B;AACrC,gBAAK,YAAY,OAAOA,CAAK,GAC7B,KAAK,UAAU,KAAK,SAAS,IAAI,KAAK,YAAYA,CAAK,GAChD;AAAA,EACT;AAAA,EAEA,WAAWD,GAAaC,GAA+B;AACrD,gBAAK,aAAaD,GAClB,KAAK,YAAYC,MAAU,SAAY,KAAK,OAAOA,CAAK,GACxD,KAAK,UAAU,KAAK,SAAS,IAAID,GAAKC,CAAK,GACpC;AAAA,EACT;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ObjectFactory.js","sources":["../../src/core/ObjectFactory.ts"],"sourcesContent":["import type { Container } from 'pixi.js';\nimport { BitmapLabel } from './BitmapLabel';\nimport { Graphics } from './Graphics';\nimport { Group } from './Group';\nimport { Image } from './Image';\nimport { Rectangle } from './Rectangle';\nimport type { Stage } from './Stage';\nimport { Text, type TextStyle } from './Text';\n\n/** `scene.add.*`: creates an object and puts it on the world layer, where depth sorts it. */\nexport class ObjectFactory {\n constructor(private readonly stage: Stage) {}\n\n image(x: number, y: number, key: string, frame?: string | number): Image {\n return this.attach(new Image(this.stage.textures, x, y, key, frame));\n }\n\n /**\n * A copy of `src` as it is drawn right now (afterimages, clones). A live texture, repainted every frame, is\n * snapshotted first, and the snapshot is freed with the copy.\n */\n still(x: number, y: number, src: Image): Image {\n const textures = this.stage.textures;\n if (!textures.isLive(src.textureKey)) return this.image(x, y, src.textureKey, src.frameName);\n const key = textures.snapshot(src.textureKey, src.frameName);\n const img = this.image(x, y, key, '0');\n img.once('destroyed', () => textures.remove(key));\n return img;\n }\n\n graphics(opts?: { x?: number; y?: number }): Graphics {\n const g = this.attach(new Graphics());\n g.position.set(opts?.x ?? 0, opts?.y ?? 0);\n return g;\n }\n\n container(x = 0, y = 0, children: Container[] = []): Group {\n const c = this.attach(new Group());\n c.position.set(x, y);\n c.add(children);\n return c;\n }\n\n rectangle(x: number, y: number, w: number, h: number, color: number, alpha = 1): Rectangle {\n const r = this.attach(new Rectangle(w, h, color, alpha));\n r.position.set(x, y);\n return r;\n }\n\n text(x: number, y: number, text: string, style: TextStyle): Text {\n return this.attach(new Text(x, y, text, style));\n }\n\n /** A bitmap-font label (`BitmapLabel`): cheap to change, for text rewritten many times a second. */\n label(x: number, y: number, text: string, fontFamily: string, size: number, color: string, stroke: string, strokeWidth: number, resolution: number, glyphs = ''): BitmapLabel {\n return this.attach(new BitmapLabel(x, y, text, fontFamily, size, color, stroke, strokeWidth, resolution, glyphs));\n }\n\n /** Puts an object made elsewhere on the world layer, as the factory's own are (a `PixelLayer`, a custom class). */\n existing<T extends Container & { scene: Stage }>(obj: T): T {\n return this.attach(obj);\n }\n\n private attach<T extends Container & { scene: Stage }>(obj: T): T {\n obj.scene = this.stage;\n this.stage.world.addChild(obj);\n return obj;\n }\n}\n"],"names":["ObjectFactory","stage","x","y","key","frame","Image","src","textures","img","opts","g","Graphics","children","c","Group","w","h","color","alpha","r","Rectangle","text","style","Text","fontFamily","size","stroke","strokeWidth","resolution","glyphs","BitmapLabel","obj"],"mappings":";;;;;;AAUO,MAAMA,EAAc;AAAA,EACzB,YAA6BC,GAAc;AAAd,SAAA,QAAAA;AAAA,EAAe;AAAA,EAAf;AAAA,EAE7B,MAAMC,GAAWC,GAAWC,GAAaC,GAAgC;AACvE,WAAO,KAAK,OAAO,IAAIC,EAAM,KAAK,MAAM,UAAUJ,GAAGC,GAAGC,GAAKC,CAAK,CAAC;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAMH,GAAWC,GAAWI,GAAmB;AAC7C,UAAMC,IAAW,KAAK,MAAM;AAC5B,QAAI,CAACA,EAAS,OAAOD,EAAI,UAAU,EAAG,QAAO,KAAK,MAAML,GAAGC,GAAGI,EAAI,YAAYA,EAAI,SAAS;AAC3F,UAAMH,IAAMI,EAAS,SAASD,EAAI,YAAYA,EAAI,SAAS,GACrDE,IAAM,KAAK,MAAMP,GAAGC,GAAGC,GAAK,GAAG;AACrC,WAAAK,EAAI,KAAK,aAAa,MAAMD,EAAS,OAAOJ,CAAG,CAAC,GACzCK;AAAA,EACT;AAAA,EAEA,SAASC,GAA6C;AACpD,UAAMC,IAAI,KAAK,OAAO,IAAIC,GAAU;AACpC,WAAAD,EAAE,SAAS,IAAID,GAAM,KAAK,GAAGA,GAAM,KAAK,CAAC,GAClCC;AAAA,EACT;AAAA,EAEA,UAAUT,IAAI,GAAGC,IAAI,GAAGU,IAAwB,IAAW;AACzD,UAAMC,IAAI,KAAK,OAAO,IAAIC,GAAO;AACjC,WAAAD,EAAE,SAAS,IAAIZ,GAAGC,CAAC,GACnBW,EAAE,IAAID,CAAQ,GACPC;AAAA,EACT;AAAA,EAEA,UAAUZ,GAAWC,GAAWa,GAAWC,GAAWC,GAAeC,IAAQ,GAAc;AACzF,UAAMC,IAAI,KAAK,OAAO,IAAIC,EAAUL,GAAGC,GAAGC,GAAOC,CAAK,CAAC;AACvD,WAAAC,EAAE,SAAS,IAAIlB,GAAGC,CAAC,GACZiB;AAAA,EACT;AAAA,EAEA,KAAKlB,GAAWC,GAAWmB,GAAcC,GAAwB;AAC/D,WAAO,KAAK,OAAO,IAAIC,EAAKtB,GAAGC,GAAGmB,GAAMC,CAAK,CAAC;AAAA,EAChD;AAAA;AAAA,EAGA,MAAMrB,GAAWC,GAAWmB,GAAcG,GAAoBC,GAAcR,GAAeS,GAAgBC,GAAqBC,GAAoBC,IAAS,IAAiB;AAC5K,WAAO,KAAK,OAAO,IAAIC,EAAY7B,GAAGC,GAAGmB,GAAMG,GAAYC,GAAMR,GAAOS,GAAQC,GAAaC,GAAYC,CAAM,CAAC;AAAA,EAClH;AAAA;AAAA,EAGA,SAAiDE,GAAW;AAC1D,WAAO,KAAK,OAAOA,CAAG;AAAA,EACxB;AAAA,EAEQ,OAA+CA,GAAW;AAChE,WAAAA,EAAI,QAAQ,KAAK,OACjB,KAAK,MAAM,MAAM,SAASA,CAAG,GACtBA;AAAA,EACT;AACF;"}
|
package/dist/core/Pointer.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Pointer.js","sources":["../../src/core/Pointer.ts"],"sourcesContent":["import type { Camera } from './Camera';\nimport { isTouchUi } from '../platform/touch';\n\n/** MouseEvent.button (0 left, 1 middle, 2 right) as a MouseEvent.buttons bit. */\nfunction buttonBit(button: number): number {\n return button === 1 ? 4 : button === 2 ? 2 : button === 0 ? 1 : 0;\n}\n\n/**\n * The mouse over the game canvas. Presses count only when they start on the canvas (not on the React UI above it);\n * moves are followed everywhere so aiming keeps working at the edge of a panel.\n */\nexport class Pointer {\n /**\n * A press this returns true for is not counted as a held button (e.g. Shift + right-click opening a context menu\n * instead of firing what the right button is bound to). Unset, every press counts.\n */\n static ignorePress: ((e: PointerEvent) => boolean) | null = null;\n\n /** Screen position relative to the canvas, in CSS pixels. */\n x = 0;\n y = 0;\n worldX = 0;\n worldY = 0;\n private buttons = 0;\n private readonly cleanup: (() => void)[] = [];\n\n constructor(private readonly canvas: HTMLCanvasElement) {\n // with the on-screen controls a finger on the world is not a mouse: it would swing at every tap and drag the\n // aim after the joystick thumb (touch controls aim on their own). A real mouse still works.\n const fingers = isTouchUi();\n const on = <K extends keyof WindowEventMap>(target: Window | HTMLElement, type: K, fn: (e: WindowEventMap[K]) => void) => {\n const handler = (e: WindowEventMap[K]) => {\n if (fingers && e instanceof PointerEvent && e.pointerType === 'touch') return;\n fn(e);\n };\n target.addEventListener(type, handler as EventListener);\n this.cleanup.push(() => target.removeEventListener(type, handler as EventListener));\n };\n // Pointer events, since Pixi's event system cancels the mouse events that follow them. Read `button` (the one\n // that changed), not `buttons`: synthetic input (CDP) leaves `buttons` at 0.\n on(canvas, 'pointerdown', (e) => {\n this.track(e);\n if (Pointer.ignorePress?.(e)) return;\n this.buttons |= buttonBit(e.button);\n });\n on(window, 'pointermove', (e) => {\n this.track(e);\n // a second button pressed or released while one is held arrives as a move\n if (e.button >= 0) this.buttons = (e.buttons & buttonBit(e.button)) !== 0 ? this.buttons | buttonBit(e.button) : this.buttons & ~buttonBit(e.button);\n });\n on(window, 'pointerup', (e) => {\n this.buttons &= ~buttonBit(e.button);\n });\n on(window, 'blur', () => {\n this.buttons = 0;\n });\n on(canvas, 'contextmenu', (e) => e.preventDefault());\n }\n\n leftButtonDown(): boolean {\n return (this.buttons & 1) !== 0;\n }\n\n rightButtonDown(): boolean {\n return (this.buttons & 2) !== 0;\n }\n\n updateWorldPoint(cam: Camera): void {\n this.worldX = cam.scrollX + this.x / cam.zoom;\n this.worldY = cam.scrollY + this.y / cam.zoom;\n }\n\n destroy(): void {\n for (const fn of this.cleanup) fn();\n this.cleanup.length = 0;\n }\n\n private track(e: MouseEvent): void {\n const r = this.canvas.getBoundingClientRect();\n this.x = e.clientX - r.left;\n this.y = e.clientY - r.top;\n }\n}\n"],"names":["buttonBit","button","Pointer","canvas","fingers","isTouchUi","on","target","type","fn","handler","e","cam","r"],"mappings":";AAIA,SAASA,EAAUC,GAAwB;AACzC,SAAOA,MAAW,IAAI,IAAIA,MAAW,IAAI,IAAIA,MAAW,IAAI,IAAI;AAClE;AAMO,MAAMC,EAAQ;AAAA,EAenB,YAA6BC,GAA2B;AAA3B,SAAA,SAAAA;AAG3B,UAAMC,IAAUC,EAAA,GACVC,IAAK,CAAiCC,GAA8BC,GAASC,MAAuC;AACxH,YAAMC,IAAU,CAACC,MAAyB;AACxC,QAAIP,KAAWO,aAAa,gBAAgBA,EAAE,gBAAgB,WAC9DF,EAAGE,CAAC;AAAA,MACN;AACA,MAAAJ,EAAO,iBAAiBC,GAAME,CAAwB,GACtD,KAAK,QAAQ,KAAK,MAAMH,EAAO,oBAAoBC,GAAME,CAAwB,CAAC;AAAA,IACpF;AAGA,IAAAJ,EAAGH,GAAQ,eAAe,CAACQ,MAAM;AAE/B,MADA,KAAK,MAAMA,CAAC,GACR,CAAAT,EAAQ,cAAcS,CAAC,MAC3B,KAAK,WAAWX,EAAUW,EAAE,MAAM;AAAA,IACpC,CAAC,GACDL,EAAG,QAAQ,eAAe,CAACK,MAAM;AAC/B,WAAK,MAAMA,CAAC,GAERA,EAAE,UAAU,MAAG,KAAK,WAAWA,EAAE,UAAUX,EAAUW,EAAE,MAAM,OAAO,IAAI,KAAK,UAAUX,EAAUW,EAAE,MAAM,IAAI,KAAK,UAAU,CAACX,EAAUW,EAAE,MAAM;AAAA,IACrJ,CAAC,GACDL,EAAG,QAAQ,aAAa,CAACK,MAAM;AAC7B,WAAK,WAAW,CAACX,EAAUW,EAAE,MAAM;AAAA,IACrC,CAAC,GACDL,EAAG,QAAQ,QAAQ,MAAM;AACvB,WAAK,UAAU;AAAA,IACjB,CAAC,GACDA,EAAGH,GAAQ,eAAe,CAACQ,MAAMA,EAAE,gBAAgB;AAAA,EACrD;AAAA,EA/B6B;AAAA;AAAA;AAAA;AAAA;AAAA,EAV7B,OAAO,cAAqD;AAAA;AAAA,EAG5D,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,SAAS;AAAA,EACD,UAAU;AAAA,EACD,UAA0B,CAAA;AAAA,EAmC3C,iBAA0B;AACxB,YAAQ,KAAK,UAAU,OAAO;AAAA,EAChC;AAAA,EAEA,kBAA2B;AACzB,YAAQ,KAAK,UAAU,OAAO;AAAA,EAChC;AAAA,EAEA,iBAAiBC,GAAmB;AAClC,SAAK,SAASA,EAAI,UAAU,KAAK,IAAIA,EAAI,MACzC,KAAK,SAASA,EAAI,UAAU,KAAK,IAAIA,EAAI;AAAA,EAC3C;AAAA,EAEA,UAAgB;AACd,eAAWH,KAAM,KAAK,QAAS,CAAAA,EAAA;AAC/B,SAAK,QAAQ,SAAS;AAAA,EACxB;AAAA,EAEQ,MAAME,GAAqB;AACjC,UAAME,IAAI,KAAK,OAAO,sBAAA;AACtB,SAAK,IAAIF,EAAE,UAAUE,EAAE,MACvB,KAAK,IAAIF,EAAE,UAAUE,EAAE;AAAA,EACzB;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Rectangle.js","sources":["../../src/core/Rectangle.ts"],"sourcesContent":["import { Container, Graphics as PixiGraphics } from 'pixi.js';\nimport { GameObject } from './gameObject';\n\n/** A filled rectangle placed by its origin (centred by default). */\nexport class Rectangle extends GameObject(Container) {\n private readonly g = new PixiGraphics();\n private ox = 0.5;\n private oy = 0.5;\n\n constructor(\n private w: number,\n private h: number,\n private color: number,\n private fillAlpha = 1,\n ) {\n super();\n this.addChild(this.g);\n this.redraw();\n }\n\n get originX(): number {\n return this.ox;\n }\n\n get originY(): number {\n return this.oy;\n }\n\n get rectWidth(): number {\n return this.w;\n }\n\n get rectHeight(): number {\n return this.h;\n }\n\n get fillColor(): number {\n return this.color;\n }\n\n get fillOpacity(): number {\n return this.fillAlpha;\n }\n\n setOrigin(x = 0.5, y = x): this {\n this.ox = x;\n this.oy = y;\n return this.redraw();\n }\n\n setFillStyle(color: number, alpha = 1): this {\n this.color = color;\n this.fillAlpha = alpha;\n return this.redraw();\n }\n\n setDisplaySize(w: number, h: number): this {\n return this.setScale(w / this.w, h / this.h);\n }\n\n resize(w: number, h: number): this {\n this.w = w;\n this.h = h;\n return this.redraw();\n }\n\n private redraw(): this {\n this.g.clear().rect(-this.w * this.ox, -this.h * this.oy, this.w, this.h).fill({ color: this.color, alpha: this.fillAlpha });\n return this;\n }\n}\n"],"names":["Rectangle","GameObject","Container","w","h","color","fillAlpha","PixiGraphics","x","y","alpha"],"mappings":";;AAIO,MAAMA,UAAkBC,EAAWC,CAAS,EAAE;AAAA,EAKnD,YACUC,GACAC,GACAC,GACAC,IAAY,GACpB;AACA,UAAA,GALQ,KAAA,IAAAH,GACA,KAAA,IAAAC,GACA,KAAA,QAAAC,GACA,KAAA,YAAAC,GAGR,KAAK,SAAS,KAAK,CAAC,GACpB,KAAK,OAAA;AAAA,EACP;AAAA,EARU;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EARO,IAAI,IAAIC,EAAA;AAAA,EACjB,KAAK;AAAA,EACL,KAAK;AAAA,EAab,IAAI,UAAkB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,UAAkB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,aAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,cAAsB;AACxB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAUC,IAAI,KAAKC,IAAID,GAAS;AAC9B,gBAAK,KAAKA,GACV,KAAK,KAAKC,GACH,KAAK,OAAA;AAAA,EACd;AAAA,EAEA,aAAaJ,GAAeK,IAAQ,GAAS;AAC3C,gBAAK,QAAQL,GACb,KAAK,YAAYK,GACV,KAAK,OAAA;AAAA,EACd;AAAA,EAEA,eAAeP,GAAWC,GAAiB;AACzC,WAAO,KAAK,SAASD,IAAI,KAAK,GAAGC,IAAI,KAAK,CAAC;AAAA,EAC7C;AAAA,EAEA,OAAOD,GAAWC,GAAiB;AACjC,gBAAK,IAAID,GACT,KAAK,IAAIC,GACF,KAAK,OAAA;AAAA,EACd;AAAA,EAEQ,SAAe;AACrB,gBAAK,EAAE,MAAA,EAAQ,KAAK,CAAC,KAAK,IAAI,KAAK,IAAI,CAAC,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,OAAO,KAAK,UAAA,CAAW,GACpH;AAAA,EACT;AACF;"}
|
package/dist/core/Stage.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Stage.js","sources":["../../src/core/Stage.ts"],"sourcesContent":["import { Container, type Application } from 'pixi.js';\nimport { Camera } from './Camera';\nimport { Clock } from './Clock';\nimport { ObjectFactory } from './ObjectFactory';\nimport { Pointer } from './Pointer';\nimport type { TextureRegistry } from './TextureRegistry';\nimport { Tweener } from './Tweener';\n\n/**\n * What a world object is created in: a world layer the camera moves (children sorted by depth), a screen layer\n * above it, and the per-frame services (tweens, timers, camera, pointer). Subclasses implement `update`.\n */\nexport abstract class Stage {\n /** Told of every fault a guarded step caught (error telemetry, if the game sets one). */\n static onCaught: ((where: string, err: unknown) => void) | null = null;\n\n readonly world = new Container({ sortableChildren: true });\n readonly screen = new Container();\n readonly add = new ObjectFactory(this);\n readonly tweens = new Tweener();\n readonly time = new Clock();\n readonly cameras: { main: Camera };\n readonly input: { activePointer: Pointer };\n private readonly resizeListeners: (() => void)[] = [];\n private lastWidth = 0;\n private lastHeight = 0;\n\n constructor(\n readonly app: Application,\n readonly textures: TextureRegistry,\n ) {\n app.stage.addChild(this.world, this.screen);\n this.cameras = { main: new Camera(this.world, this.screen) };\n this.input = { activePointer: new Pointer(app.canvas) };\n this.syncSize();\n }\n\n get width(): number {\n return this.app.screen.width;\n }\n\n get height(): number {\n return this.app.screen.height;\n }\n\n get fps(): number {\n return this.app.ticker.FPS;\n }\n\n /** Calls `fn` whenever the window changes size; the returned function stops it (a per-map listener must). */\n onResize(fn: () => void): () => void {\n this.resizeListeners.push(fn);\n return () => {\n const i = this.resizeListeners.indexOf(fn);\n if (i >= 0) this.resizeListeners.splice(i, 1);\n };\n }\n\n abstract update(now: number, delta: number): void;\n\n /**\n * One frame: game logic, then tweens and timers, then the camera. Pixi renders after. Each step is guarded: a\n * ticker listener that throws never asks for the next animation frame, so one bad frame used to stop the world for\n * good — the canvas frozen and no input reaching the server (seen on Android).\n */\n frame(delta: number): void {\n const now = performance.now();\n this.guard('resize', () => this.syncSize());\n this.guard('update', () => this.update(now, delta));\n this.guard('tweens', () => this.tweens.update(delta));\n this.guard('timers', () => this.time.update(now));\n this.guard('camera', () => this.cameras.main.apply(now));\n }\n\n private readonly reported = new Set<string>();\n\n /**\n * Runs one step of a frame and swallows what it throws, so the steps after it still run. Subclasses guard the parts\n * of their own `update` too: a fault before the camera follows the player used to leave the view standing on one\n * spot while the world went on.\n */\n protected guard(step: string, fn: () => void): void {\n this.guarded(step, fn, undefined);\n }\n\n /** `guard` for a step that answers something: `fallback` when it threw. */\n protected guarded<T>(step: string, fn: () => T, fallback: T): T {\n try {\n return fn();\n } catch (err) {\n this.caught(step, err);\n return fallback;\n }\n }\n\n /** What a guarded step threw: logged once per distinct error, or a fault repeating every frame floods the log at 60 a second. */\n protected caught(step: string, err: unknown): void {\n const key = `${step}|${err instanceof Error ? err.message : String(err)}`;\n if (this.reported.has(key)) return;\n this.reported.add(key);\n console.error(`[frame] ${step} failed`, err);\n // caught here, so the window's error listener never hears of it: tell the telemetry ourselves\n Stage.onCaught?.(`[frame] ${step}`, err);\n }\n\n destroy(): void {\n this.tweens.clear();\n this.time.clear();\n this.input.activePointer.destroy();\n this.world.destroy({ children: true, context: true });\n this.screen.destroy({ children: true, context: true });\n }\n\n private syncSize(): void {\n const { width, height } = this.app.screen;\n const cam = this.cameras.main;\n cam.width = width;\n cam.height = height;\n if (width === this.lastWidth && height === this.lastHeight) return;\n this.lastWidth = width;\n this.lastHeight = height;\n // a copy: a listener may unsubscribe itself (or another) while they run\n for (const fn of [...this.resizeListeners]) fn();\n }\n}\n"],"names":["Stage","app","textures","Camera","Pointer","Container","ObjectFactory","Tweener","Clock","fn","i","delta","now","step","fallback","err","key","width","height","cam"],"mappings":"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"}
|
package/dist/core/Text.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Text.js","sources":["../../src/core/Text.ts"],"sourcesContent":["import { Text as PixiText, type TextStyleOptions } from 'pixi.js';\nimport { GameObject } from './gameObject';\n\nexport interface TextStyle {\n fontFamily?: string;\n fontSize: string | number;\n color?: string;\n stroke?: string;\n strokeThickness?: number;\n /** Texture pixels per world pixel: world text is zoomed by the camera, so it is rasterised finer. */\n resolution?: number;\n align?: 'left' | 'center' | 'right';\n lineSpacing?: number;\n wordWrapWidth?: number;\n}\n\n/** Canvas text in world or screen space. */\nexport class Text extends GameObject(PixiText) {\n constructor(x: number, y: number, text: string, style: TextStyle) {\n // only keys that are set: an explicit undefined overrides Pixi's default (leading: undefined = NaN height)\n const pixiStyle: TextStyleOptions = {\n fontFamily: style.fontFamily ?? 'monospace',\n fontSize: typeof style.fontSize === 'string' ? parseFloat(style.fontSize) : style.fontSize,\n fill: style.color ?? '#ffffff',\n };\n if (style.stroke) pixiStyle.stroke = { color: style.stroke, width: style.strokeThickness ?? 1, join: 'round' };\n if (style.align) pixiStyle.align = style.align;\n if (style.lineSpacing !== undefined) pixiStyle.leading = style.lineSpacing;\n if (style.wordWrapWidth !== undefined) {\n pixiStyle.wordWrap = true;\n pixiStyle.wordWrapWidth = style.wordWrapWidth;\n }\n super({ text, resolution: style.resolution ?? 1, style: pixiStyle });\n this.position.set(x, y);\n }\n\n setOrigin(x = 0.5, y = x): this {\n this.anchor.set(x, y);\n return this;\n }\n\n setText(text: string): this {\n this.text = text;\n return this;\n }\n}\n"],"names":["Text","GameObject","PixiText","x","y","text","style","pixiStyle"],"mappings":";;AAiBO,MAAMA,UAAaC,EAAWC,CAAQ,EAAE;AAAA,EAC7C,YAAYC,GAAWC,GAAWC,GAAcC,GAAkB;AAEhE,UAAMC,IAA8B;AAAA,MAClC,YAAYD,EAAM,cAAc;AAAA,MAChC,UAAU,OAAOA,EAAM,YAAa,WAAW,WAAWA,EAAM,QAAQ,IAAIA,EAAM;AAAA,MAClF,MAAMA,EAAM,SAAS;AAAA,IAAA;AAEvB,IAAIA,EAAM,WAAQC,EAAU,SAAS,EAAE,OAAOD,EAAM,QAAQ,OAAOA,EAAM,mBAAmB,GAAG,MAAM,QAAA,IACjGA,EAAM,UAAOC,EAAU,QAAQD,EAAM,QACrCA,EAAM,gBAAgB,WAAWC,EAAU,UAAUD,EAAM,cAC3DA,EAAM,kBAAkB,WAC1BC,EAAU,WAAW,IACrBA,EAAU,gBAAgBD,EAAM,gBAElC,MAAM,EAAE,MAAAD,GAAM,YAAYC,EAAM,cAAc,GAAG,OAAOC,GAAW,GACnE,KAAK,SAAS,IAAIJ,GAAGC,CAAC;AAAA,EACxB;AAAA,EAEA,UAAUD,IAAI,KAAKC,IAAID,GAAS;AAC9B,gBAAK,OAAO,IAAIA,GAAGC,CAAC,GACb;AAAA,EACT;AAAA,EAEA,QAAQC,GAAoB;AAC1B,gBAAK,OAAOA,GACL;AAAA,EACT;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"TextureRegistry.js","sources":["../../src/core/TextureRegistry.ts"],"sourcesContent":["import { CanvasSource, Rectangle, Texture } from 'pixi.js';\n\n/** A canvas-backed texture with named frames; repaint the canvas, then `refresh()` to upload it. */\nexport class CanvasTexture {\n readonly frames = new Map<string, Texture>();\n readonly source: CanvasSource;\n readonly whole: Texture;\n\n constructor(\n readonly key: string,\n readonly canvas: HTMLCanvasElement,\n ) {\n this.source = new CanvasSource({ resource: canvas, scaleMode: 'nearest' });\n this.whole = new Texture({ source: this.source });\n }\n\n get width(): number {\n return this.canvas.width;\n }\n\n get height(): number {\n return this.canvas.height;\n }\n\n get context(): CanvasRenderingContext2D {\n return this.canvas.getContext('2d')!;\n }\n\n /** Adds a frame cut from the canvas (`_source` kept for the old call shape). */\n add(name: string, _source: number, x: number, y: number, w: number, h: number): void {\n this.frames.get(name)?.destroy();\n this.frames.set(name, new Texture({ source: this.source, frame: new Rectangle(x, y, w, h) }));\n }\n\n /**\n * Registers under this texture a frame that lives on another one (an atlas page after the first), so the frame is\n * found by this texture's key. A texture of its own over the other page's source: each side frees its own.\n */\n adopt(name: string, from: CanvasTexture): void {\n const src = from.frames.get(name);\n if (!src) return;\n this.frames.get(name)?.destroy();\n this.frames.set(name, new Texture({ source: from.source, frame: src.frame.clone() }));\n }\n\n refresh(): void {\n this.source.update();\n }\n\n destroy(): void {\n for (const t of this.frames.values()) t.destroy();\n this.frames.clear();\n this.whole.destroy();\n this.source.destroy();\n }\n}\n\n/** Every texture of the world by key: the sprite atlas and per-player repainted sheets. */\nexport class TextureRegistry {\n private readonly textures = new Map<string, CanvasTexture>();\n /** Textures repainted or freed while in use (a rig body, its hand): a copy of one must snapshot it first. */\n private readonly live = new Set<string>();\n private missing = new Set<string>();\n private snapshots = 0;\n\n markLive(key: string): void {\n this.live.add(key);\n }\n\n isLive(key: string): boolean {\n return this.live.has(key);\n }\n\n /** A new texture holding frame `frame` of `key` as it is painted right now; returns its key (one frame, '0'). */\n snapshot(key: string, frame?: string | number): string {\n const src = this.get(key, frame);\n const { x, y, width, height } = src.frame;\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const from = this.textures.get(key)?.canvas;\n if (from) canvas.getContext('2d')!.drawImage(from, x, y, width, height, 0, 0, width, height);\n const copy = `${key}#still${++this.snapshots}`;\n this.addCanvas(copy, canvas).add('0', 0, 0, 0, width, height);\n return copy;\n }\n\n /**\n * `snapshot` into a texture kept for reuse (`into`): its canvas is repainted in place and uploaded again, and only\n * made anew when the frame's size changes. Frame '0' is the copy.\n */\n snapshotInto(key: string, frame: string | number | undefined, into: string): void {\n const src = this.get(key, frame);\n const { x, y, width, height } = src.frame;\n let tex = this.textures.get(into);\n if (!tex || tex.width !== width || tex.height !== height) {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n tex = this.addCanvas(into, canvas);\n tex.add('0', 0, 0, 0, width, height);\n }\n const ctx = tex.context;\n ctx.clearRect(0, 0, width, height);\n const from = this.textures.get(key)?.canvas;\n if (from) ctx.drawImage(from, x, y, width, height, 0, 0, width, height);\n tex.refresh();\n }\n\n exists(key: string): boolean {\n return this.textures.has(key);\n }\n\n addCanvas(key: string, canvas: HTMLCanvasElement): CanvasTexture {\n this.remove(key);\n const tex = new CanvasTexture(key, canvas);\n this.textures.set(key, tex);\n return tex;\n }\n\n /** A canvas cut into equal frames named 0, 1, 2... left to right, then row by row. */\n addSpriteSheet(key: string, canvas: HTMLCanvasElement, size: { frameWidth: number; frameHeight: number }): CanvasTexture {\n const tex = this.addCanvas(key, canvas);\n const cols = Math.floor(canvas.width / size.frameWidth);\n const rows = Math.floor(canvas.height / size.frameHeight);\n for (let i = 0; i < cols * rows; i++) {\n tex.add(String(i), 0, (i % cols) * size.frameWidth, Math.floor(i / cols) * size.frameHeight, size.frameWidth, size.frameHeight);\n }\n return tex;\n }\n\n createCanvas(key: string, width: number, height: number): CanvasTexture {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n return this.addCanvas(key, canvas);\n }\n\n remove(key: string): void {\n this.textures.get(key)?.destroy();\n this.textures.delete(key);\n this.live.delete(key);\n }\n\n get(key: string, frame?: string | number): Texture {\n const tex = this.textures.get(key);\n if (!tex) return this.warn(key);\n if (frame === undefined || frame === '') return tex.whole;\n return tex.frames.get(String(frame)) ?? this.warn(`${key}/${frame}`);\n }\n\n private warn(what: string): Texture {\n if (!this.missing.has(what)) {\n this.missing.add(what);\n console.warn(`[textures] missing ${what}`);\n }\n return Texture.EMPTY;\n }\n}\n"],"names":["CanvasTexture","key","canvas","CanvasSource","Texture","name","_source","x","y","w","Rectangle","from","src","TextureRegistry","frame","width","height","copy","into","tex","ctx","size","cols","rows","i","what"],"mappings":"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"}
|