@ouidesigner/toubani 0.0.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -8
- package/dist/index.d.mts +19 -18
- package/dist/index.d.ts +19 -18
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/docs/ai/index.md +26 -0
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
# Toubani (tourterelle)
|
|
2
2
|
|
|
3
|
-
A small TypeScript 2D game helper built on top of the [ouider](https://www.npmjs.com/package
|
|
3
|
+
A small TypeScript 2D game helper built on top of the [ouider](https://www.npmjs.com/package/@ouidesigner%2Fouider) bridge. It wraps ODOM canvas APIs with a lightweight game loop, asset pipeline, sprite/scene graph utilities, tile rendering, simple physics/collision helpers, camera controls, and Web Audio playback.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
- Simple game loop with lifecycle hooks (`preload`, `setup`, `update`, `draw`)
|
|
7
|
-
- Asset manager for images, sprite sheets, and decoded
|
|
7
|
+
- Asset manager for images, sprite sheets, and `OUID.media.audio` decoded sounds
|
|
8
8
|
- Sprite sheets and animations, plus a scene graph for transforms/parallax
|
|
9
9
|
- Tile map rendering with camera-aware culling
|
|
10
10
|
- Basic input handling (keyboard and mouse pressed/held state)
|
|
11
11
|
- Minimal physics for AABB bodies on a tilemap and tag-based collision rules
|
|
12
12
|
- Camera helper for pan/zoom/anchor transforms
|
|
13
|
-
- Web Audio playback
|
|
13
|
+
- Web Audio playback through `OUID.media.audio` from `@ouidesigner/ouider-device`
|
|
14
14
|
|
|
15
15
|
## Installation
|
|
16
16
|
```bash
|
|
17
|
+
npm install @ouidesigner/toubani @ouidesigner/ouider @ouidesigner/ouider-device @ouidesigner/ouider-network
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
For local package development:
|
|
21
|
+
```bash
|
|
17
22
|
npm install
|
|
18
23
|
npm run build # bundles to dist/ via tsup
|
|
19
24
|
```
|
|
@@ -23,8 +28,8 @@ Toubani expects to run inside an ouider environment. Your app should already ini
|
|
|
23
28
|
## Quick start
|
|
24
29
|
Create a canvas (provided by ouider) and hand it to `tulon`:
|
|
25
30
|
```ts
|
|
26
|
-
import { tulon, GameContext } from "toubani";
|
|
27
|
-
import { ODOM } from "ouider";
|
|
31
|
+
import { tulon, GameContext } from "@ouidesigner/toubani";
|
|
32
|
+
import { ODOM } from "@ouidesigner/ouider";
|
|
28
33
|
|
|
29
34
|
const canvas = new ODOM.CanvasElement(/* ouider element */);
|
|
30
35
|
|
|
@@ -59,11 +64,11 @@ game.start();
|
|
|
59
64
|
|
|
60
65
|
## Library modules
|
|
61
66
|
- **Game/core**: `Game`, `tulon`, and the `GameConfig/GameContext` contracts in `src/game.ts` and `src/core.ts`.
|
|
62
|
-
- **Assets/audio**: `AssetManager` loads images/sounds and builds sprite sheets; `AudioManager`
|
|
67
|
+
- **Assets/audio**: `AssetManager` loads images/sounds and builds sprite sheets; `AudioManager` delegates decoded playback to `OUID.media.audio`.
|
|
63
68
|
- **Sprites/scene**: `SpriteSheet`, `SpriteAnimation`, `Sprite`, `SpriteLayer`, and a small scene graph (`Scene`, `SceneNode`, `SpriteNode`) for nested transforms.
|
|
64
69
|
- **Tiles/camera**: `TileRenderer` and helpers for tile maps/tilesets, plus `Camera2D` for pan/zoom/rotation with configurable focus anchors.
|
|
65
70
|
- **Physics/collision**: `PhysicsBody` for AABB tile collisions with gravity/friction and `CollisionSystem` for tag-based collider handlers.
|
|
66
|
-
- **Input**: `InputManager` tracks key and mouse pressed/held state per frame.
|
|
71
|
+
- **Input/network**: `InputManager` tracks key and mouse pressed/held state per frame; `WebSocketTransport` uses `@ouidesigner/ouider-network` under the hood.
|
|
67
72
|
|
|
68
73
|
## Examples
|
|
69
74
|
See `examples/simple` for runnable demos:
|
|
@@ -77,5 +82,5 @@ Each example has its own `package.json`/webpack config; install and run from `ex
|
|
|
77
82
|
- TypeScript config: `tsconfig.json` targets CommonJS output for the bundled library.
|
|
78
83
|
|
|
79
84
|
## Notes
|
|
80
|
-
- Audio uses
|
|
85
|
+
- Audio uses `OUID.media.audio`, so apps should install `@ouidesigner/ouider-device` and declare the `media` manifest permission when required by the host.
|
|
81
86
|
- Assets cache image sizes on load to support sprite and tile calculations.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ODOM } from '@ouidesigner/ouider';
|
|
2
|
+
import { WebAudioPlayOptions } from '@ouidesigner/ouider-device';
|
|
3
|
+
import { WebSocketOptions } from '@ouidesigner/ouider-network';
|
|
2
4
|
|
|
3
5
|
interface SpriteSheetConfig {
|
|
4
6
|
image: string;
|
|
@@ -81,23 +83,14 @@ declare class AssetManager implements AssetRegistry {
|
|
|
81
83
|
getImage(key: string): ODOM.Image | undefined;
|
|
82
84
|
getSpriteSheet(key: string): SpriteSheet | undefined;
|
|
83
85
|
createAnimation(sheetKey: string, fps?: number, loop?: boolean): SpriteAnimation;
|
|
84
|
-
getSound(key: string):
|
|
86
|
+
getSound(key: string): string | undefined;
|
|
85
87
|
clean(): void;
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
type VolumeOptions =
|
|
89
|
-
loop?: boolean;
|
|
90
|
-
volume?: number;
|
|
91
|
-
};
|
|
90
|
+
type VolumeOptions = WebAudioPlayOptions;
|
|
92
91
|
declare class AudioManager {
|
|
93
92
|
private assets;
|
|
94
|
-
private audioContext;
|
|
95
|
-
private audioDestination;
|
|
96
|
-
private decodedBuffers;
|
|
97
93
|
constructor(assets: AssetManager);
|
|
98
|
-
private getContext;
|
|
99
|
-
private getDestination;
|
|
100
|
-
private getBuffer;
|
|
101
94
|
clean(): void;
|
|
102
95
|
play(key: string, options?: VolumeOptions): Promise<void>;
|
|
103
96
|
}
|
|
@@ -358,16 +351,21 @@ interface NetworkTransport {
|
|
|
358
351
|
onClose(cb: (ev?: any) => void): void;
|
|
359
352
|
onError(cb: (err: any) => void): void;
|
|
360
353
|
}
|
|
354
|
+
interface WebSocketTransportOptions extends WebSocketOptions {
|
|
355
|
+
protocols?: string | string[];
|
|
356
|
+
connectTimeoutMs?: number;
|
|
357
|
+
}
|
|
361
358
|
declare class WebSocketTransport implements NetworkTransport {
|
|
362
|
-
private
|
|
363
|
-
private static readonly CONNECTING;
|
|
359
|
+
private options;
|
|
364
360
|
private opened;
|
|
365
361
|
private socket;
|
|
362
|
+
private currentUrl;
|
|
363
|
+
private offMessage;
|
|
366
364
|
private msgHandlers;
|
|
367
365
|
private openHandlers;
|
|
368
366
|
private closeHandlers;
|
|
369
367
|
private errorHandlers;
|
|
370
|
-
|
|
368
|
+
constructor(options?: WebSocketTransportOptions);
|
|
371
369
|
connect(url: string): Promise<void>;
|
|
372
370
|
send(data: string): Promise<void>;
|
|
373
371
|
close(code?: number, reason?: string): Promise<void>;
|
|
@@ -390,12 +388,15 @@ declare class NetworkClient {
|
|
|
390
388
|
constructor(transport: NetworkTransport);
|
|
391
389
|
connect(url: string): Promise<void>;
|
|
392
390
|
isConnected(): boolean;
|
|
393
|
-
send<TType extends string, TPayload>(type: TType, payload: TPayload): void
|
|
391
|
+
send<TType extends string, TPayload>(type: TType, payload: TPayload): Promise<void>;
|
|
394
392
|
on<TPayload = any>(type: string, handler: Handler<NetMessage<string, TPayload>>): void;
|
|
395
393
|
off(type: string, handler: Handler): void;
|
|
396
394
|
call<TReq = any, TRes = any>(type: string, payload: TReq): Promise<TRes>;
|
|
397
395
|
private handleRawMessage;
|
|
398
396
|
close(): Promise<void>;
|
|
397
|
+
private extractRpcId;
|
|
398
|
+
private extractRpcValue;
|
|
399
|
+
private rejectPendingRpc;
|
|
399
400
|
}
|
|
400
401
|
interface StatePatch<TState> {
|
|
401
402
|
full?: TState;
|
|
@@ -409,11 +410,11 @@ declare class StateSyncClient<TState extends object> {
|
|
|
409
410
|
constructor(net: NetworkClient, initialState: TState, channels?: {
|
|
410
411
|
update: string;
|
|
411
412
|
patch: string;
|
|
412
|
-
});
|
|
413
|
+
} | undefined);
|
|
413
414
|
getState(): TState;
|
|
414
415
|
onChange(handler: (state: TState) => void): void;
|
|
415
416
|
private emitChange;
|
|
416
|
-
sendPatch(patch: Partial<TState>): void
|
|
417
|
+
sendPatch(patch: Partial<TState>): Promise<void>;
|
|
417
418
|
}
|
|
418
419
|
|
|
419
|
-
export { type AABB, AssetManager, type AssetRegistry, AudioManager, Camera2D, type Collider, type CollisionHandler, CollisionSystem, Game, type GameConfig, type GameContext, InputManager, type NetMessage, NetworkClient, type NetworkTransport, PhysicsBody, type PhysicsBodySettings, Scene, SceneLayer, SceneNode, Sprite, SpriteAnimation, SpriteLayer, SpriteNode, type SpriteOptions, SpriteSheet, type SpriteSheetConfig, type StatePatch, StateSyncClient, type TileMap, TileRenderer, type TileRendererOptions, type TileSet, WebSocketTransport, aabbOverlap, createSpritePhysicsCollider, getTileSourceRect, objectSize, tulon, worldToTileX, worldToTileY };
|
|
420
|
+
export { type AABB, AssetManager, type AssetRegistry, AudioManager, Camera2D, type Collider, type CollisionHandler, CollisionSystem, Game, type GameConfig, type GameContext, InputManager, type NetMessage, NetworkClient, type NetworkTransport, PhysicsBody, type PhysicsBodySettings, Scene, SceneLayer, SceneNode, Sprite, SpriteAnimation, SpriteLayer, SpriteNode, type SpriteOptions, SpriteSheet, type SpriteSheetConfig, type StatePatch, StateSyncClient, type TileMap, TileRenderer, type TileRendererOptions, type TileSet, WebSocketTransport, type WebSocketTransportOptions, aabbOverlap, createSpritePhysicsCollider, getTileSourceRect, objectSize, tulon, worldToTileX, worldToTileY };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ODOM } from '@ouidesigner/ouider';
|
|
2
|
+
import { WebAudioPlayOptions } from '@ouidesigner/ouider-device';
|
|
3
|
+
import { WebSocketOptions } from '@ouidesigner/ouider-network';
|
|
2
4
|
|
|
3
5
|
interface SpriteSheetConfig {
|
|
4
6
|
image: string;
|
|
@@ -81,23 +83,14 @@ declare class AssetManager implements AssetRegistry {
|
|
|
81
83
|
getImage(key: string): ODOM.Image | undefined;
|
|
82
84
|
getSpriteSheet(key: string): SpriteSheet | undefined;
|
|
83
85
|
createAnimation(sheetKey: string, fps?: number, loop?: boolean): SpriteAnimation;
|
|
84
|
-
getSound(key: string):
|
|
86
|
+
getSound(key: string): string | undefined;
|
|
85
87
|
clean(): void;
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
type VolumeOptions =
|
|
89
|
-
loop?: boolean;
|
|
90
|
-
volume?: number;
|
|
91
|
-
};
|
|
90
|
+
type VolumeOptions = WebAudioPlayOptions;
|
|
92
91
|
declare class AudioManager {
|
|
93
92
|
private assets;
|
|
94
|
-
private audioContext;
|
|
95
|
-
private audioDestination;
|
|
96
|
-
private decodedBuffers;
|
|
97
93
|
constructor(assets: AssetManager);
|
|
98
|
-
private getContext;
|
|
99
|
-
private getDestination;
|
|
100
|
-
private getBuffer;
|
|
101
94
|
clean(): void;
|
|
102
95
|
play(key: string, options?: VolumeOptions): Promise<void>;
|
|
103
96
|
}
|
|
@@ -358,16 +351,21 @@ interface NetworkTransport {
|
|
|
358
351
|
onClose(cb: (ev?: any) => void): void;
|
|
359
352
|
onError(cb: (err: any) => void): void;
|
|
360
353
|
}
|
|
354
|
+
interface WebSocketTransportOptions extends WebSocketOptions {
|
|
355
|
+
protocols?: string | string[];
|
|
356
|
+
connectTimeoutMs?: number;
|
|
357
|
+
}
|
|
361
358
|
declare class WebSocketTransport implements NetworkTransport {
|
|
362
|
-
private
|
|
363
|
-
private static readonly CONNECTING;
|
|
359
|
+
private options;
|
|
364
360
|
private opened;
|
|
365
361
|
private socket;
|
|
362
|
+
private currentUrl;
|
|
363
|
+
private offMessage;
|
|
366
364
|
private msgHandlers;
|
|
367
365
|
private openHandlers;
|
|
368
366
|
private closeHandlers;
|
|
369
367
|
private errorHandlers;
|
|
370
|
-
|
|
368
|
+
constructor(options?: WebSocketTransportOptions);
|
|
371
369
|
connect(url: string): Promise<void>;
|
|
372
370
|
send(data: string): Promise<void>;
|
|
373
371
|
close(code?: number, reason?: string): Promise<void>;
|
|
@@ -390,12 +388,15 @@ declare class NetworkClient {
|
|
|
390
388
|
constructor(transport: NetworkTransport);
|
|
391
389
|
connect(url: string): Promise<void>;
|
|
392
390
|
isConnected(): boolean;
|
|
393
|
-
send<TType extends string, TPayload>(type: TType, payload: TPayload): void
|
|
391
|
+
send<TType extends string, TPayload>(type: TType, payload: TPayload): Promise<void>;
|
|
394
392
|
on<TPayload = any>(type: string, handler: Handler<NetMessage<string, TPayload>>): void;
|
|
395
393
|
off(type: string, handler: Handler): void;
|
|
396
394
|
call<TReq = any, TRes = any>(type: string, payload: TReq): Promise<TRes>;
|
|
397
395
|
private handleRawMessage;
|
|
398
396
|
close(): Promise<void>;
|
|
397
|
+
private extractRpcId;
|
|
398
|
+
private extractRpcValue;
|
|
399
|
+
private rejectPendingRpc;
|
|
399
400
|
}
|
|
400
401
|
interface StatePatch<TState> {
|
|
401
402
|
full?: TState;
|
|
@@ -409,11 +410,11 @@ declare class StateSyncClient<TState extends object> {
|
|
|
409
410
|
constructor(net: NetworkClient, initialState: TState, channels?: {
|
|
410
411
|
update: string;
|
|
411
412
|
patch: string;
|
|
412
|
-
});
|
|
413
|
+
} | undefined);
|
|
413
414
|
getState(): TState;
|
|
414
415
|
onChange(handler: (state: TState) => void): void;
|
|
415
416
|
private emitChange;
|
|
416
|
-
sendPatch(patch: Partial<TState>): void
|
|
417
|
+
sendPatch(patch: Partial<TState>): Promise<void>;
|
|
417
418
|
}
|
|
418
419
|
|
|
419
|
-
export { type AABB, AssetManager, type AssetRegistry, AudioManager, Camera2D, type Collider, type CollisionHandler, CollisionSystem, Game, type GameConfig, type GameContext, InputManager, type NetMessage, NetworkClient, type NetworkTransport, PhysicsBody, type PhysicsBodySettings, Scene, SceneLayer, SceneNode, Sprite, SpriteAnimation, SpriteLayer, SpriteNode, type SpriteOptions, SpriteSheet, type SpriteSheetConfig, type StatePatch, StateSyncClient, type TileMap, TileRenderer, type TileRendererOptions, type TileSet, WebSocketTransport, aabbOverlap, createSpritePhysicsCollider, getTileSourceRect, objectSize, tulon, worldToTileX, worldToTileY };
|
|
420
|
+
export { type AABB, AssetManager, type AssetRegistry, AudioManager, Camera2D, type Collider, type CollisionHandler, CollisionSystem, Game, type GameConfig, type GameContext, InputManager, type NetMessage, NetworkClient, type NetworkTransport, PhysicsBody, type PhysicsBodySettings, Scene, SceneLayer, SceneNode, Sprite, SpriteAnimation, SpriteLayer, SpriteNode, type SpriteOptions, SpriteSheet, type SpriteSheetConfig, type StatePatch, StateSyncClient, type TileMap, TileRenderer, type TileRendererOptions, type TileSet, WebSocketTransport, type WebSocketTransportOptions, aabbOverlap, createSpritePhysicsCollider, getTileSourceRect, objectSize, tulon, worldToTileX, worldToTileY };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var ouider=require('@ouidesigner/ouider');var et=Object.defineProperty,bt=Object.defineProperties;var Dt=Object.getOwnPropertyDescriptors;var tt=Object.getOwnPropertySymbols;var Ct=Object.prototype.hasOwnProperty,Et=Object.prototype.propertyIsEnumerable;var Y=(d,t,e)=>t in d?et(d,t,{enumerable:true,configurable:true,writable:true,value:e}):d[t]=e,st=(d,t)=>{for(var e in t||(t={}))Ct.call(t,e)&&Y(d,e,t[e]);if(tt)for(var e of tt(t))Et.call(t,e)&&Y(d,e,t[e]);return d},it=(d,t)=>bt(d,Dt(t)),c=(d,t)=>et(d,"name",{value:t,configurable:true});var i=(d,t,e)=>Y(d,typeof t!="symbol"?t+"":t,e);var z=class z{constructor(t,e,s,o,n=0){i(this,"image");i(this,"frameWidth");i(this,"frameHeight");i(this,"frameCount");i(this,"from");i(this,"columns");this.image=t,this.frameWidth=e,this.frameHeight=s,this.frameCount=o,this.from=n;let r=I(t);this.columns=Math.max(1,Math.floor(r.width/e));}};c(z,"SpriteSheet");var E=z,S=class S{constructor(t,e=10,s=true){i(this,"sheet");i(this,"fps");i(this,"loop");i(this,"time",0);this.sheet=t,this.fps=e,this.loop=s;}update(t){this.time+=t;let e=this.sheet.frameCount/this.fps;this.loop?this.time=this.time%e:this.time>e&&(this.time=e);}draw(t,e,s,o=1,n=1){let r=Math.floor(this.time*this.fps)+this.sheet.from,a=Math.min(r,this.sheet.frameCount+this.sheet.from-1),h=this.sheet.columns,l=a%h*this.sheet.frameWidth,p=Math.floor(a/h)*this.sheet.frameHeight,u=this.sheet.frameWidth*o,f=this.sheet.frameHeight*n;t.drawImage(this.sheet.image,l,p,this.sheet.frameWidth,this.sheet.frameHeight,e,s,u,f);}reset(){this.time=0;}};c(S,"SpriteAnimation");var O=S,L=class L{constructor(t){i(this,"animation");i(this,"width");i(this,"height");i(this,"visible");i(this,"tag");i(this,"data");var e,s,o,n;this.animation=t.animation,this.visible=(e=t.visible)!=null?e:true,this.tag=t.tag,this.data=(s=t.data)!=null?s:{},this.width=(o=t.width)!=null?o:this.animation.sheet.frameWidth,this.height=(n=t.height)!=null?n:this.animation.sheet.frameHeight;}update(t){this.animation.update(t);}draw(t,e=1,s=1){this.visible&&this.animation.draw(t,0,0,e,s);}};c(L,"Sprite");var ot=L,W=class W{constructor(){i(this,"sprites",[]);}add(t){return this.sprites.push(t),t}remove(t){let e=this.sprites.indexOf(t);e>=0&&this.sprites.splice(e,1);}clear(){this.sprites.length=0;}update(t){for(let e of this.sprites)e.update(t);}draw(t){for(let e of this.sprites)e.draw(t);}withTag(t){return this.sprites.filter(e=>e.tag===t)}};c(W,"SpriteLayer");var nt=W;var B=class B{constructor(){i(this,"imageDefs",new Map);i(this,"soundDefs",new Map);i(this,"sheetDefs",new Map);i(this,"images",new Map);i(this,"sounds",new Map);i(this,"sheets",new Map);}image(t,e){this.imageDefs.set(t,e);}sound(t,e){this.soundDefs.set(t,e);}spriteSheet(t,e){this.sheetDefs.set(t,e);}async loadAll(){var s,o;let t=[],e=[];for(let[n,r]of this.imageDefs.entries())t.push(this.loadImage(r).then(a=>{this.images.set(n,a);}));for(let[n,r]of this.soundDefs.entries())e.push(this.loadSound(r).then(a=>{this.sounds.set(n,a);}));await Promise.all([...t,...e]);for(let[n,r]of this.sheetDefs.entries()){let a=(s=this.getImage(r.image))!=null?s:this.images.get(r.image);if(!a)throw new Error(`SpriteSheet "${n}" missing image "${r.image}"`);let h=new E(a,r.frameWidth,r.frameHeight,r.frameCount,(o=r.from)!=null?o:0);this.sheets.set(n,h);}}async loadImage(t){let e=await ouider.ODOM.Image.new();if(!e)throw new Error(`Unable to create image for "${t}"`);let s=null,o=null,n=false,r=c(async()=>{let h=[s,o].filter(Boolean);s=null,o=null,await Promise.all(h.map(l=>e.dettachEventListener(l).catch(()=>{})));},"detach"),a=c(async()=>{let[h,l,p,u]=await Promise.all([e.naturalWidth(),e.naturalHeight(),e.width(),e.height()]);e.__local_size__={width:h||p||0,height:l||u||0};},"cacheSize");return new Promise(async(h,l)=>{let p=c(async()=>{if(!n)try{await a(),n=!0,await r(),h(e);}catch(f){await u(f);}},"done"),u=c(async f=>{n||(n=true,await r(),await e.release().catch(()=>{}),l(new Error(`Unable to load image "${t}"`)));},"fail");try{s=await e.addEventListener("load",()=>{p();},{options:{once:!0}}),o=await e.addEventListener("error",()=>{u();},{options:{once:!0}}),await e.setSrc(t);let[f,m]=await Promise.all([e.getProperty("complete").catch(()=>!1),e.naturalWidth().catch(()=>0)]);f&&m>0&&await p();}catch(f){await u(f);}})}async loadSound(t){return await OUID.fetch(t,{method:"GET",headers:{},body:null,credentials:"omit"},"buffer")}getImage(t){return this.images.get(t)}getSpriteSheet(t){return this.sheets.get(t)}createAnimation(t,e,s){let o=this.getSpriteSheet(t);if(!o)throw new Error(`SpriteSheet "${t}" not found`);return new O(o,e!=null?e:10,s!=null?s:true)}getSound(t){return this.sounds.get(t)}clean(){this.images.forEach(t=>t.release()),this.sounds.forEach(t=>t.release()),this.images.clear(),this.sounds.clear(),this.sheets.clear();}};c(B,"AssetManager");var H=B;var A=class A{constructor(t){i(this,"assets");i(this,"audioContext",null);i(this,"audioDestination",null);i(this,"decodedBuffers",new Map);this.assets=t;}async getContext(){if(this.audioContext)return this.audioContext;let t=await OUID.newObject("AudioContext");return t?(this.audioContext=ouider.ODOM.OObject.toInstance(t),this.audioContext):null}async getDestination(t){if(this.audioDestination)return this.audioDestination;let e=await t.getPropertyAndHold("destination");return e?(this.audioDestination=e,this.audioDestination):null}async getBuffer(t){if(this.decodedBuffers.has(t))return this.decodedBuffers.get(t);let e=this.assets.getSound(t);if(!e)return null;let s=await this.getContext();if(!s)return null;let o=await s.invokeAndHold("decodeAudioData",e.ref);return o&&this.decodedBuffers.set(t,o),o}clean(){this.decodedBuffers.forEach(t=>t.release()),this.decodedBuffers.clear(),this.audioDestination&&(this.audioDestination.release(),this.audioDestination=null),this.audioContext&&(this.audioContext.invoke("close"),this.audioContext.release(),this.audioContext=null);}async play(t,e){let s=await this.getContext();if(!s)return;let o=await this.getBuffer(t);if(!o)return;await s.invoke("resume");let n=await s.invokeAndHold("createBufferSource");if(!n)return;await n.setProperty("buffer",o.ref),(e==null?void 0:e.loop)!=null&&await n.setProperty("loop",e.loop);let r=await this.getDestination(s);if(!r){n.release();return}if((e==null?void 0:e.volume)!=null){let a=await s.invokeAndHold("createGain");if(a){let h=await a.getPropertyAndHold("gain");h&&(await h.setProperty("value",e.volume),h.release()),await n.invoke("connect",a.ref),await a.invoke("connect",r.ref),await n.invoke("start",0),a.release(),n.release();return}}await n.invoke("connect",r.ref),await n.invoke("start",0),n.release();}};c(A,"AudioManager");var X=A;var R=class R{constructor(t){i(this,"keysDown",new Set);i(this,"keysPressed",new Set);i(this,"buttonsPressed",new Set);i(this,"buttonsDown",new Set);i(this,"mouseX",-1);i(this,"mouseY",-1);i(this,"offsetX",-1);i(this,"offsetY",-1);i(this,"ids",[]);i(this,"disposed",false);this.track(OUID.addEventListener("window","keydown",e=>{this.keysDown.has(e.code)||this.keysPressed.add(e.code),this.keysDown.add(e.code);})),this.track(OUID.addEventListener("window","keyup",e=>{this.keysDown.delete(e.code);})),this.track(t.addEventListener("mousedown",e=>{let s=e.button===0?"MouseLeft":"MouseRight";this.buttonsDown.has(s)||this.buttonsPressed.add(s),this.buttonsDown.add(s),this.updateMousePosition(e);})),this.track(t.addEventListener("mouseup",e=>{let s=e.button===0?"MouseLeft":"MouseRight";this.buttonsDown.delete(s),this.updateMousePosition(e);})),this.track(t.addEventListener("mousemove",e=>{this.updateMousePosition(e);})),this.track(t.addEventListener("mouseleave",()=>{this.buttonsDown.clear();}));}update(){this.keysPressed.clear(),this.buttonsPressed.clear();}isDown(t){return this.keysDown.has(t)||this.buttonsDown.has(t)}isPressed(t){return this.keysPressed.has(t)||this.buttonsPressed.has(t)}clean(){this.disposed=true,this.ids.forEach(t=>{OUID.dettachEventListener(t);}),this.ids=[],this.keysDown.clear(),this.keysPressed.clear(),this.buttonsDown.clear(),this.buttonsPressed.clear();}track(t){t.then(e=>{this.disposed?OUID.dettachEventListener(e):this.ids.push(e);}).catch(()=>{});}updateMousePosition(t){var e,s,o,n;this.mouseX=(e=t.x)!=null?e:this.mouseX,this.mouseY=(s=t.y)!=null?s:this.mouseY,this.offsetX=(o=t.offsetX)!=null?o:this.offsetX,this.offsetY=(n=t.offsetY)!=null?n:this.offsetY;}};c(R,"InputManager");var T=R;var $=class ${constructor(t){i(this,"config");i(this,"canvas");i(this,"ctx2d");i(this,"assets");i(this,"audio");i(this,"input");i(this,"world",{});i(this,"running",false);i(this,"initialized",false);i(this,"lastTime",0);i(this,"frameId",null);i(this,"resizeListenerId",null);this.config=t,this.canvas=t.canvas,this.assets=new H,this.audio=new X(this.assets),this.input=new T(this.canvas);}buildContext(t){return {dt:t,canvas:this.canvas,ctx2d:this.ctx2d,world:this.world,assets:this.assets,audio:this.audio,input:this.input}}async updateLocalSize(){var s,o;let t=(s=await this.canvas.width())!=null?s:0,e=(o=await this.canvas.height())!=null?o:0;this.canvas.__local_size__=this.ctx2d.__local_size__={width:t,height:e};}async syncSize(){var a,h;let[t,e,s,o]=await Promise.all([this.canvas.getProperty("clientWidth"),this.canvas.getProperty("clientHeight"),this.canvas.width(),this.canvas.height()]),n=(a=t!=null?t:s)!=null?a:0,r=(h=e!=null?e:o)!=null?h:0;await this.canvas.setProperty("width",n),await this.canvas.setProperty("height",r);}async init(){if(this.initialized)return;let t=await this.canvas.getContext("2d");if(!t)throw new Error("Unable to create a context");this.ctx2d=t,this.resizeListenerId=await this.canvas.addEventListener("resize",()=>{this.syncSize().then(()=>this.updateLocalSize());}),this.config.preload&&(this.config.preload(this.assets),await this.assets.loadAll()),await this.syncSize(),await this.updateLocalSize(),this.config.setup&&this.config.setup(this.buildContext(0)),this.initialized=true;}start(){this.running||(this.running=true,this.lastTime=performance.now(),this.scheduleFrame());}stop(){var t;!this.running&&!this.initialized||(this.running=false,this.frameId!=null&&(OUID.cancelAnimationFrame(this.frameId),this.frameId=null),this.resizeListenerId&&(OUID.dettachEventListener(this.resizeListenerId),this.resizeListenerId=null),this.audio.clean(),this.assets.clean(),this.input.clean(),(t=this.ctx2d)==null||t.release(),this.initialized=false);}scheduleFrame(){OUID.requestAnimationFrame(t=>{this.frameId=null,this.tick(t);}).then(t=>{this.running?this.frameId=t:OUID.cancelAnimationFrame(t);});}async tick(t){var o,n,r,a;if(!this.running)return;let s=Math.min((t-this.lastTime)/1e3,.1);this.lastTime=t;try{let h=this.buildContext(s);(n=(o=this.config).update)==null||n.call(o,h),this.input.update(),await((a=(r=this.config).draw)==null?void 0:a.call(r,h)),await this.ctx2d.commit();}catch(h){console.error("[Toubani] game loop error",h);}finally{this.running&&this.scheduleFrame();}}};c($,"Game");var U=$;function Kt(d){return new U(d)}c(Kt,"tulon");function I(d){var t;return (t=d.__local_size__)!=null?t:{width:0,height:0}}c(I,"objectSize");function v(d,t){return Math.floor(d/t.tileWidth)}c(v,"worldToTileX");function M(d,t){return Math.floor(d/t.tileHeight)}c(M,"worldToTileY");function Ht(d,t){let{image:e,tileWidth:s,tileHeight:o,margin:n=0,spacing:r=0}=t,a=I(e),h=Math.max(1,Math.floor((a.width-2*n+r)/(s+r))),l=d-1,p=l%h,u=Math.floor(l/h),f=n+p*(s+r),m=n+u*(o+r);return {sx:f,sy:m,sw:s,sh:o}}c(Ht,"getTileSourceRect");var N=class N{constructor(t){i(this,"tileset");i(this,"sourceRectCache",new Map);this.tileset=t;}draw(t,e,s,o,n){let{tileWidth:r,tileHeight:a}=e,h=o*s.focusX,l=n*s.focusY,p=s.zoom,u=s.x-h/p,f=s.y-l/p,m=s.x+(o-h)/p,w=s.y+(n-l)/p,g=Math.max(0,Math.floor(u/r)),y=Math.min(e.width-1,Math.floor(m/r)),b=Math.max(0,Math.floor(f/a)),mt=Math.min(e.height-1,Math.floor(w/a));for(let D=b;D<=mt;D++)for(let C=g;C<=y;C++){let _=e.getTile(C,D);if(_<=0)continue;let{sx:xt,sy:yt,sw:vt,sh:Mt}=this.getSourceRect(_),kt=C*r,Pt=D*a;t.drawImage(this.tileset.image,xt,yt,vt,Mt,kt,Pt,r,a);}}getSourceRect(t){let e=this.sourceRectCache.get(t);return e||(e=Ht(t,this.tileset),this.sourceRectCache.set(t,e)),e}};c(N,"TileRenderer");var rt=N;var j=class j{constructor(t,e){i(this,"node");i(this,"settings");i(this,"vx",0);i(this,"vy",0);i(this,"onGround",false);this.node=t,this.settings=e;}update(t){var f,m,w,g,y;let e=this.settings,s=(f=e.gravityX)!=null?f:0,o=(m=e.gravityY)!=null?m:1e3,n=(w=e.maxFallSpeed)!=null?w:2e3,r=(g=e.friction)!=null?g:.8,a=(y=e.airFriction)!=null?y:.98;this.vx+=s*t,this.vy+=o*t,this.vy>n&&(this.vy=n);let h=Math.max(1,Math.min(e.tileMap.tileWidth,e.tileMap.tileHeight)*.5),l=Math.max(Math.abs(this.vx*t),Math.abs(this.vy*t)),p=Math.max(1,Math.ceil(l/h)),u=t/p;for(let b=0;b<p;b++)this.moveAndCollideX(u),this.moveAndCollideY(u);this.onGround?this.vx*=r:this.vx*=a;}moveAndCollideX(t){let e=this.settings.tileMap,s=this.settings.width,o=this.settings.height,n=this.node.x+this.vx*t,r=this.node.y,a=Math.sign(this.vx);if(a===0){this.node.x=n;return}let h=n,l=n+s,p=r,u=r+o,f=Math.max(0,M(p,e)),m=Math.min(e.height-1,M(u-1,e));if(a>0){let w=v(l,e);for(let g=f;g<=m;g++)if(this.isSolid(w,g)){l=w*e.tileWidth,h=l-s,this.vx=0;break}}else {let w=v(h,e);for(let g=f;g<=m;g++)if(this.isSolid(w,g)){h=(w+1)*e.tileWidth,l=h+s,this.vx=0;break}}this.node.x=h;}moveAndCollideY(t){let e=this.settings.tileMap,s=this.settings.width,o=this.settings.height,n=this.node.x,r=this.node.y+this.vy*t,a=Math.sign(this.vy);if(this.onGround=false,a===0){this.node.y=r;return}let h=r,l=r+o,p=n,u=n+s,f=Math.max(0,v(p,e)),m=Math.min(e.width-1,v(u-1,e));if(a>0){let w=M(l,e);for(let g=f;g<=m;g++)if(this.isSolid(g,w)){l=w*e.tileHeight,h=l-o,this.vy=0,this.onGround=true;break}}else {let w=M(h,e);for(let g=f;g<=m;g++)if(this.isSolid(g,w)){h=(w+1)*e.tileHeight,l=h+o,this.vy=0;break}}this.node.y=h;}isSolid(t,e){let s=this.settings.tileMap;return t<0||e<0||t>=s.width||e>=s.height?false:s.isSolid(t,e)}};c(j,"PhysicsBody");var at=j;var F=class F{constructor(){i(this,"x",0);i(this,"y",0);i(this,"zoom",1);i(this,"rotation",0);i(this,"followTarget",null);i(this,"followLerp",.1);i(this,"focusX",.5);i(this,"focusY",.5);}follow(t,e=.1){this.followTarget=t,this.followLerp=e;}unfollow(){this.followTarget=null;}update(t){if(!this.followTarget)return;let e=this.followTarget.x,s=this.followTarget.y;this.x+=(e-this.x)*this.followLerp,this.y+=(s-this.y)*this.followLerp;}begin(t,e,s){t.save();let o=e*this.focusX,n=s*this.focusY;t.translate(o,n),this.rotation!==0&&t.rotate(-this.rotation),this.zoom!==1&&t.scale(this.zoom,this.zoom),t.translate(-this.x,-this.y);}end(t){t.restore();}};c(F,"Camera2D");var ht=F;var J=class J{constructor(){i(this,"x",0);i(this,"y",0);i(this,"rotation",0);i(this,"scaleX",1);i(this,"scaleY",1);i(this,"visible",true);i(this,"parent",null);i(this,"children",[]);}add(t){return t.parent=this,this.children.push(t),t}remove(t){let e=this.children.indexOf(t);e>=0&&(this.children.splice(e,1),t.parent=null);}clear(){for(let t of this.children)t.parent=null;this.children.length=0;}update(t){}drawSelf(t){}draw(t){if(this.visible){t.save(),t.translate(this.x,this.y),this.rotation!==0&&t.rotate(this.rotation),(this.scaleX!==1||this.scaleY!==1)&&t.scale(this.scaleX,this.scaleY),this.drawSelf(t);for(let e of this.children)e.draw(t);t.restore();}}updateTree(t){this.update(t);for(let e of this.children)e.updateTree(t);}getWorldPosition(){let t=this.x,e=this.y,s=this.parent;for(;s;)t+=s.x,e+=s.y,s=s.parent;return {x:t,y:e}}};c(J,"SceneNode");var P=J,q=class q extends P{constructor(e){super();i(this,"sprite");this.sprite=e;}update(e){this.sprite.update(e);}drawSelf(e){this.sprite.draw(e);}getWorldBounds(){let e=this.getWorldPosition();return {x:e.x,y:e.y,width:this.sprite.width*this.scaleX,height:this.sprite.height*this.scaleY}}};c(q,"SpriteNode");var ct=q,K=class K extends P{constructor(e=1,s=1){super();i(this,"parallaxX",1);i(this,"parallaxY",1);this.parallaxX=e,this.parallaxY=s;}};c(K,"SceneLayer");var G=K,k=class k{constructor(){i(this,"id");i(this,"root",new P);i(this,"layers",[]);this.id=k.id++;}addLayer(t=1,e=1){let s=new G(t,e);return this.layers.push(s),this.root.add(s),s}update(t){this.root.updateTree(t);}draw(t){this.root.draw(t);}};c(k,"Scene"),i(k,"id",0);var lt=k;function dt(d,t){return !(d.x+d.width<=t.x||d.x>=t.x+t.width||d.y+d.height<=t.y||d.y>=t.y+t.height)}c(dt,"aabbOverlap");var Q=class Q{constructor(){i(this,"colliders",[]);i(this,"rules",[]);i(this,"rulesByPair",new Map);}add(t){this.colliders.push(t);}remove(t){let e=this.colliders.indexOf(t);e>=0&&this.colliders.splice(e,1);}clear(){this.colliders.length=0;}onCollision(t,e,s){var a;let o={tagA:t,tagB:e,handler:s};this.rules.push(o);let n=this.ruleKey(t,e),r=(a=this.rulesByPair.get(n))!=null?a:[];r.push(o),this.rulesByPair.set(n,r);}update(){var o;let t=new Map,e=new Map;for(let n of this.colliders){if(!n.isActive)continue;let r=(o=t.get(n.tag))!=null?o:[];r.push(n),t.set(n.tag,r);}let s=c(n=>{let r=e.get(n);return r||(r=n.getBounds(),e.set(n,r)),r},"getBounds");for(let[n,r]of this.rulesByPair.entries()){let[a,h]=n.split("\0"),l=t.get(a),p=t.get(h);if(!(!l||!p))if(a===h)for(let u=0;u<l.length;u++){let f=l[u],m=s(f);for(let w=u+1;w<l.length;w++){let g=l[w];dt(m,s(g))&&this.triggerRules(f,g,r);}}else for(let u of l){let f=s(u);for(let m of p)dt(f,s(m))&&this.triggerRules(u,m,r);}}}ruleKey(t,e){return t<=e?`${t}\0${e}`:`${e}\0${t}`}triggerRules(t,e,s){for(let o of s){let n=o.tagA===t.tag&&o.tagB===e.tag,r=o.tagA===e.tag&&o.tagB===t.tag;n?o.handler(t,e):r&&o.handler(e,t);}}};c(Q,"CollisionSystem");var ut=Q,Xt=0;function he(d,t,e,s){return {id:`col-${Xt++}`,tag:d,isActive:true,data:{node:t,body:e,sprite:s},getBounds(){let n=t.getWorldPosition(),r=e.settings.width,a=e.settings.height;return {x:n.x,y:n.y,width:r,height:a}}}}c(he,"createSpritePhysicsCollider");var x=class x{constructor(){i(this,"opened",false);i(this,"socket",null);i(this,"msgHandlers",[]);i(this,"openHandlers",[]);i(this,"closeHandlers",[]);i(this,"errorHandlers",[]);i(this,"ids",[]);}async connect(t){if(this.socket){let s=await this.socket.getProperty("readyState");if(s===x.OPEN||s===x.CONNECTING)return;await this.cleanupSocket(false);}let e=await ouider.OUID.newObject("WebSocket",t);if(!e)throw new Error(`Unable to create WebSocket for ${t}`);this.socket=ouider.ODOM.OObject.toInstance(e),this.ids.push(await this.socket.addEventListener("open",()=>{this.opened=true,this.openHandlers.forEach(s=>s());})),this.ids.push(await this.socket.addEventListener("message",s=>{let o=typeof s.data=="string"?s.data:"";this.msgHandlers.forEach(n=>n(o));})),this.ids.push(await this.socket.addEventListener("close",s=>{this.opened=false,this.closeHandlers.forEach(o=>o(s));})),this.ids.push(await this.socket.addEventListener("error",s=>{this.errorHandlers.forEach(o=>o(s));}));try{await new Promise((s,o)=>{let n=c(()=>{h(),s();},"onOpen"),r=c(l=>{h(),o(l);},"onError"),a=c(l=>{h(),o(l);},"onClose"),h=c(()=>{this.offOpen(n),this.offError(r),this.offClose(a);},"cleanup");this.onOpen(n),this.onError(r),this.onClose(a);});}catch(s){throw await this.cleanupSocket(false),s}}async send(t){this.socket&&await this.socket.getProperty("readyState")===x.OPEN&&await this.socket.invoke("send",t);}async close(t,e){await this.cleanupSocket(true,t,e);}isConnected(){return this.opened}onMessage(t){this.msgHandlers.push(t);}onOpen(t){this.openHandlers.push(t);}offOpen(t){this.openHandlers=this.openHandlers.filter(e=>e!==t);}onClose(t){this.closeHandlers.push(t);}offClose(t){this.closeHandlers=this.closeHandlers.filter(e=>e!==t);}onError(t){this.errorHandlers.push(t);}offError(t){this.errorHandlers=this.errorHandlers.filter(e=>e!==t);}async cleanupSocket(t,e,s){let o=this.socket;this.socket=null,this.opened=false;let n=this.ids.splice(0);if(await Promise.all(n.map(r=>ouider.OUID.dettachEventListener(r).catch(()=>{}))),!!o)try{if(t){let r=await o.getProperty("readyState").catch(()=>null);(r===x.OPEN||r===x.CONNECTING)&&await o.invoke("close",e,s);}}finally{await o.release().catch(()=>{});}}};c(x,"WebSocketTransport"),i(x,"OPEN",1),i(x,"CONNECTING",0);var pt=x,V=class V{constructor(t){i(this,"transport");i(this,"handlers",new Map);i(this,"rpcResolvers",new Map);i(this,"rpcCounter",0);i(this,"handleRawMessage",c(t=>{if(!t)return;let e;try{e=JSON.parse(t);}catch(o){return}if(e.payload&&typeof e.payload=="object"&&"rpcId"in e.payload){let o=e.payload.rpcId,n=this.rpcResolvers.get(o);n&&(n.resolve(e.payload),this.rpcResolvers.delete(o));}let s=this.handlers.get(e.type);if(s)for(let o of s)o(e);},"handleRawMessage"));this.transport=t,this.transport.onMessage(this.handleRawMessage);}async connect(t){await this.transport.connect(t);}isConnected(){return this.transport.isConnected()}send(t,e){let s={type:t,payload:e};this.transport.send(JSON.stringify(s));}on(t,e){var o;let s=(o=this.handlers.get(t))!=null?o:[];s.push(e),this.handlers.set(t,s);}off(t,e){let s=this.handlers.get(t);s&&this.handlers.set(t,s.filter(o=>o!==e));}call(t,e){let s=`rpc_${++this.rpcCounter}_${Date.now()}`,o=e&&typeof e=="object"?it(st({},e),{rpcId:s}):{value:e,rpcId:s},n={type:t,payload:o};return new Promise((r,a)=>{this.rpcResolvers.set(s,{resolve:r,reject:a}),this.transport.send(JSON.stringify(n)).catch(h=>{this.rpcResolvers.delete(s),a(h);});})}close(){return this.rpcResolvers.forEach(({reject:t})=>t(new Error("Network client closed"))),this.rpcResolvers.clear(),this.transport.close()}};c(V,"NetworkClient");var gt=V,Z=class Z{constructor(t,e,s){i(this,"net");i(this,"channels");i(this,"state");i(this,"onChangeHandlers",[]);var o;this.net=t,this.channels=s,this.state=e,this.net.on((o=s==null?void 0:s.update)!=null?o:"state_update",n=>{let{payload:r}=n;r.full?this.state=r.full:r.patch&&(this.state=Object.assign({},this.state,r.patch)),this.emitChange();});}getState(){return this.state}onChange(t){this.onChangeHandlers.push(t);}emitChange(){for(let t of this.onChangeHandlers)t(this.state);}sendPatch(t){var e,s;this.net.send((s=(e=this.channels)==null?void 0:e.patch)!=null?s:"state_patch",t);}};c(Z,"StateSyncClient");var wt=Z;exports.AssetManager=H;exports.AudioManager=X;exports.Camera2D=ht;exports.CollisionSystem=ut;exports.Game=U;exports.InputManager=T;exports.NetworkClient=gt;exports.PhysicsBody=at;exports.Scene=lt;exports.SceneLayer=G;exports.SceneNode=P;exports.Sprite=ot;exports.SpriteAnimation=O;exports.SpriteLayer=nt;exports.SpriteNode=ct;exports.SpriteSheet=E;exports.StateSyncClient=wt;exports.TileRenderer=rt;exports.WebSocketTransport=pt;exports.aabbOverlap=dt;exports.createSpritePhysicsCollider=he;exports.getTileSourceRect=Ht;exports.objectSize=I;exports.tulon=Kt;exports.worldToTileX=v;exports.worldToTileY=M;
|
|
1
|
+
'use strict';var ouider=require('@ouidesigner/ouider');require('@ouidesigner/ouider-device');var ouiderNetwork=require('@ouidesigner/ouider-network');var st=Object.defineProperty,Pt=Object.defineProperties;var bt=Object.getOwnPropertyDescriptors;var tt=Object.getOwnPropertySymbols;var Dt=Object.prototype.hasOwnProperty,Xt=Object.prototype.propertyIsEnumerable;var E=(l,t,s)=>t in l?st(l,t,{enumerable:true,configurable:true,writable:true,value:s}):l[t]=s,et=(l,t)=>{for(var s in t||(t={}))Dt.call(t,s)&&E(l,s,t[s]);if(tt)for(var s of tt(t))Xt.call(t,s)&&E(l,s,t[s]);return l},it=(l,t)=>Pt(l,bt(t)),c=(l,t)=>st(l,"name",{value:t,configurable:true});var i=(l,t,s)=>E(l,typeof t!="symbol"?t+"":t,s);var S=class S{constructor(t,s,e,r,o=0){i(this,"image");i(this,"frameWidth");i(this,"frameHeight");i(this,"frameCount");i(this,"from");i(this,"columns");this.image=t,this.frameWidth=s,this.frameHeight=e,this.frameCount=r,this.from=o;let n=H(t);this.columns=Math.max(1,Math.floor(n.width/s));}};c(S,"SpriteSheet");var D=S,C=class C{constructor(t,s=10,e=true){i(this,"sheet");i(this,"fps");i(this,"loop");i(this,"time",0);this.sheet=t,this.fps=s,this.loop=e;}update(t){this.time+=t;let s=this.sheet.frameCount/this.fps;this.loop?this.time=this.time%s:this.time>s&&(this.time=s);}draw(t,s,e,r=1,o=1){let n=Math.floor(this.time*this.fps)+this.sheet.from,a=Math.min(n,this.sheet.frameCount+this.sheet.from-1),h=this.sheet.columns,d=a%h*this.sheet.frameWidth,p=Math.floor(a/h)*this.sheet.frameHeight,u=this.sheet.frameWidth*r,f=this.sheet.frameHeight*o;t.drawImage(this.sheet.image,d,p,this.sheet.frameWidth,this.sheet.frameHeight,s,e,u,f);}reset(){this.time=0;}};c(C,"SpriteAnimation");var X=C,R=class R{constructor(t){i(this,"animation");i(this,"width");i(this,"height");i(this,"visible");i(this,"tag");i(this,"data");var s,e,r,o;this.animation=t.animation,this.visible=(s=t.visible)!=null?s:true,this.tag=t.tag,this.data=(e=t.data)!=null?e:{},this.width=(r=t.width)!=null?r:this.animation.sheet.frameWidth,this.height=(o=t.height)!=null?o:this.animation.sheet.frameHeight;}update(t){this.animation.update(t);}draw(t,s=1,e=1){this.visible&&this.animation.draw(t,0,0,s,e);}};c(R,"Sprite");var ot=R,W=class W{constructor(){i(this,"sprites",[]);}add(t){return this.sprites.push(t),t}remove(t){let s=this.sprites.indexOf(t);s>=0&&this.sprites.splice(s,1);}clear(){this.sprites.length=0;}update(t){for(let s of this.sprites)s.update(t);}draw(t){for(let s of this.sprites)s.draw(t);}withTag(t){return this.sprites.filter(s=>s.tag===t)}};c(W,"SpriteLayer");var rt=W;var L=class L{constructor(){i(this,"imageDefs",new Map);i(this,"soundDefs",new Map);i(this,"sheetDefs",new Map);i(this,"images",new Map);i(this,"sounds",new Map);i(this,"sheets",new Map);}image(t,s){this.imageDefs.set(t,s);}sound(t,s){this.soundDefs.set(t,s);}spriteSheet(t,s){this.sheetDefs.set(t,s);}async loadAll(){var e,r;let t=[],s=[];for(let[o,n]of this.imageDefs.entries())t.push(this.loadImage(n).then(a=>{this.images.set(o,a);}));for(let[o,n]of this.soundDefs.entries())s.push(this.loadSound(n).then(a=>{this.sounds.set(o,a);}));await Promise.all([...t,...s]);for(let[o,n]of this.sheetDefs.entries()){let a=(e=this.getImage(n.image))!=null?e:this.images.get(n.image);if(!a)throw new Error(`SpriteSheet "${o}" missing image "${n.image}"`);let h=new D(a,n.frameWidth,n.frameHeight,n.frameCount,(r=n.from)!=null?r:0);this.sheets.set(o,h);}}async loadImage(t){let s=await ouider.ODOM.Image.new();if(!s)throw new Error(`Unable to create image for "${t}"`);let e=null,r=null,o=false,n=c(async()=>{let h=[e,r].filter(Boolean);e=null,r=null,await Promise.all(h.map(d=>s.dettachEventListener(d).catch(()=>{})));},"detach"),a=c(async()=>{let[h,d,p,u]=await Promise.all([s.naturalWidth(),s.naturalHeight(),s.width(),s.height()]);s.__local_size__={width:h||p||0,height:d||u||0};},"cacheSize");return new Promise(async(h,d)=>{let p=c(async()=>{if(!o)try{await a(),o=!0,await n(),h(s);}catch(f){await u(f);}},"done"),u=c(async f=>{o||(o=true,await n(),await s.release().catch(()=>{}),d(new Error(`Unable to load image "${t}"`)));},"fail");try{e=await s.addEventListener("load",()=>{p();},{options:{once:!0}}),r=await s.addEventListener("error",()=>{u();},{options:{once:!0}}),await s.setSrc(t);let[f,m]=await Promise.all([s.getProperty("complete").catch(()=>!1),s.naturalWidth().catch(()=>0)]);f&&m>0&&await p();}catch(f){await u(f);}})}async loadSound(t){return await ouider.OUID.media.audio.decode(t),t}getImage(t){return this.images.get(t)}getSpriteSheet(t){return this.sheets.get(t)}createAnimation(t,s,e){let r=this.getSpriteSheet(t);if(!r)throw new Error(`SpriteSheet "${t}" not found`);return new X(r,s!=null?s:10,e!=null?e:true)}getSound(t){return this.sounds.get(t)}clean(){this.images.forEach(t=>t.release()),this.images.clear(),this.sounds.clear(),this.sheets.clear();}};c(L,"AssetManager");var Y=L;var O=class O{constructor(t){i(this,"assets");this.assets=t;}clean(){ouider.OUID.media.audio.close();}async play(t,s){let e=this.assets.getSound(t);e&&await ouider.OUID.media.audio.play(e,s);}};c(O,"AudioManager");var T=O;var U=class U{constructor(t){i(this,"keysDown",new Set);i(this,"keysPressed",new Set);i(this,"buttonsPressed",new Set);i(this,"buttonsDown",new Set);i(this,"mouseX",-1);i(this,"mouseY",-1);i(this,"offsetX",-1);i(this,"offsetY",-1);i(this,"ids",[]);i(this,"disposed",false);this.track(OUID.addEventListener("window","keydown",s=>{this.keysDown.has(s.code)||this.keysPressed.add(s.code),this.keysDown.add(s.code);})),this.track(OUID.addEventListener("window","keyup",s=>{this.keysDown.delete(s.code);})),this.track(t.addEventListener("mousedown",s=>{let e=s.button===0?"MouseLeft":"MouseRight";this.buttonsDown.has(e)||this.buttonsPressed.add(e),this.buttonsDown.add(e),this.updateMousePosition(s);})),this.track(t.addEventListener("mouseup",s=>{let e=s.button===0?"MouseLeft":"MouseRight";this.buttonsDown.delete(e),this.updateMousePosition(s);})),this.track(t.addEventListener("mousemove",s=>{this.updateMousePosition(s);})),this.track(t.addEventListener("mouseleave",()=>{this.buttonsDown.clear();}));}update(){this.keysPressed.clear(),this.buttonsPressed.clear();}isDown(t){return this.keysDown.has(t)||this.buttonsDown.has(t)}isPressed(t){return this.keysPressed.has(t)||this.buttonsPressed.has(t)}clean(){this.disposed=true,this.ids.forEach(t=>{OUID.dettachEventListener(t);}),this.ids=[],this.keysDown.clear(),this.keysPressed.clear(),this.buttonsDown.clear(),this.buttonsPressed.clear();}track(t){t.then(s=>{this.disposed?OUID.dettachEventListener(s):this.ids.push(s);}).catch(()=>{});}updateMousePosition(t){var s,e,r,o;this.mouseX=(s=t.x)!=null?s:this.mouseX,this.mouseY=(e=t.y)!=null?e:this.mouseY,this.offsetX=(r=t.offsetX)!=null?r:this.offsetX,this.offsetY=(o=t.offsetY)!=null?o:this.offsetY;}};c(U,"InputManager");var z=U;var B=class B{constructor(t){i(this,"config");i(this,"canvas");i(this,"ctx2d");i(this,"assets");i(this,"audio");i(this,"input");i(this,"world",{});i(this,"running",false);i(this,"initialized",false);i(this,"lastTime",0);i(this,"frameId",null);i(this,"resizeListenerId",null);this.config=t,this.canvas=t.canvas,this.assets=new Y,this.audio=new T(this.assets),this.input=new z(this.canvas);}buildContext(t){return {dt:t,canvas:this.canvas,ctx2d:this.ctx2d,world:this.world,assets:this.assets,audio:this.audio,input:this.input}}async updateLocalSize(){var e,r;let t=(e=await this.canvas.width())!=null?e:0,s=(r=await this.canvas.height())!=null?r:0;this.canvas.__local_size__=this.ctx2d.__local_size__={width:t,height:s};}async syncSize(){var a,h;let[t,s,e,r]=await Promise.all([this.canvas.getProperty("clientWidth"),this.canvas.getProperty("clientHeight"),this.canvas.width(),this.canvas.height()]),o=(a=t!=null?t:e)!=null?a:0,n=(h=s!=null?s:r)!=null?h:0;await this.canvas.setProperty("width",o),await this.canvas.setProperty("height",n);}async init(){if(this.initialized)return;let t=await this.canvas.getContext("2d");if(!t)throw new Error("Unable to create a context");this.ctx2d=t,this.resizeListenerId=await this.canvas.addEventListener("resize",()=>{this.syncSize().then(()=>this.updateLocalSize());}),this.config.preload&&(this.config.preload(this.assets),await this.assets.loadAll()),await this.syncSize(),await this.updateLocalSize(),this.config.setup&&this.config.setup(this.buildContext(0)),this.initialized=true;}start(){this.running||(this.running=true,this.lastTime=performance.now(),this.scheduleFrame());}stop(){var t;!this.running&&!this.initialized||(this.running=false,this.frameId!=null&&(OUID.cancelAnimationFrame(this.frameId),this.frameId=null),this.resizeListenerId&&(OUID.dettachEventListener(this.resizeListenerId),this.resizeListenerId=null),this.audio.clean(),this.assets.clean(),this.input.clean(),(t=this.ctx2d)==null||t.release(),this.initialized=false);}scheduleFrame(){OUID.requestAnimationFrame(t=>{this.frameId=null,this.tick(t);}).then(t=>{this.running?this.frameId=t:OUID.cancelAnimationFrame(t);});}async tick(t){var r,o,n,a;if(!this.running)return;let e=Math.min((t-this.lastTime)/1e3,.1);this.lastTime=t;try{let h=this.buildContext(e);(o=(r=this.config).update)==null||o.call(r,h),this.input.update(),await((a=(n=this.config).draw)==null?void 0:a.call(n,h)),await this.ctx2d.commit();}catch(h){console.error("[Toubani] game loop error",h);}finally{this.running&&this.scheduleFrame();}}};c(B,"Game");var A=B;function Kt(l){return new A(l)}c(Kt,"tulon");function H(l){var t;return (t=l.__local_size__)!=null?t:{width:0,height:0}}c(H,"objectSize");function y(l,t){return Math.floor(l/t.tileWidth)}c(y,"worldToTileX");function v(l,t){return Math.floor(l/t.tileHeight)}c(v,"worldToTileY");function Tt(l,t){let{image:s,tileWidth:e,tileHeight:r,margin:o=0,spacing:n=0}=t,a=H(s),h=Math.max(1,Math.floor((a.width-2*o+n)/(e+n))),d=l-1,p=d%h,u=Math.floor(d/h),f=o+p*(e+n),m=o+u*(r+n);return {sx:f,sy:m,sw:e,sh:r}}c(Tt,"getTileSourceRect");var $=class ${constructor(t){i(this,"tileset");i(this,"sourceRectCache",new Map);this.tileset=t;}draw(t,s,e,r,o){let{tileWidth:n,tileHeight:a}=s,h=r*e.focusX,d=o*e.focusY,p=e.zoom,u=e.x-h/p,f=e.y-d/p,m=e.x+(r-h)/p,w=e.y+(o-d)/p,g=Math.max(0,Math.floor(u/n)),x=Math.min(s.width-1,Math.floor(m/n)),I=Math.max(0,Math.floor(f/a)),mt=Math.min(s.height-1,Math.floor(w/a));for(let P=I;P<=mt;P++)for(let b=g;b<=x;b++){let Z=s.getTile(b,P);if(Z<=0)continue;let{sx:xt,sy:yt,sw:vt,sh:Mt}=this.getSourceRect(Z),kt=b*n,It=P*a;t.drawImage(this.tileset.image,xt,yt,vt,Mt,kt,It,n,a);}}getSourceRect(t){let s=this.sourceRectCache.get(t);return s||(s=Tt(t,this.tileset),this.sourceRectCache.set(t,s)),s}};c($,"TileRenderer");var at=$;var j=class j{constructor(t,s){i(this,"node");i(this,"settings");i(this,"vx",0);i(this,"vy",0);i(this,"onGround",false);this.node=t,this.settings=s;}update(t){var f,m,w,g,x;let s=this.settings,e=(f=s.gravityX)!=null?f:0,r=(m=s.gravityY)!=null?m:1e3,o=(w=s.maxFallSpeed)!=null?w:2e3,n=(g=s.friction)!=null?g:.8,a=(x=s.airFriction)!=null?x:.98;this.vx+=e*t,this.vy+=r*t,this.vy>o&&(this.vy=o);let h=Math.max(1,Math.min(s.tileMap.tileWidth,s.tileMap.tileHeight)*.5),d=Math.max(Math.abs(this.vx*t),Math.abs(this.vy*t)),p=Math.max(1,Math.ceil(d/h)),u=t/p;this.onGround=false;for(let I=0;I<p;I++)this.moveAndCollideX(u),this.moveAndCollideY(u);this.onGround?this.vx*=n:this.vx*=a;}moveAndCollideX(t){let s=this.settings.tileMap,e=this.settings.width,r=this.settings.height,o=this.node.x+this.vx*t,n=this.node.y,a=Math.sign(this.vx);if(a===0){this.node.x=o;return}let h=o,d=o+e,p=n,u=n+r,f=Math.max(0,v(p,s)),m=Math.min(s.height-1,v(u-1,s));if(a>0){let w=y(d,s);for(let g=f;g<=m;g++)if(this.isSolid(w,g)){d=w*s.tileWidth,h=d-e,this.vx=0;break}}else {let w=y(h,s);for(let g=f;g<=m;g++)if(this.isSolid(w,g)){h=(w+1)*s.tileWidth,d=h+e,this.vx=0;break}}this.node.x=h;}moveAndCollideY(t){let s=this.settings.tileMap,e=this.settings.width,r=this.settings.height,o=this.node.x,n=this.node.y+this.vy*t,a=Math.sign(this.vy);if(a===0){this.node.y=n;return}let h=n,d=n+r,p=o,u=o+e,f=Math.max(0,y(p,s)),m=Math.min(s.width-1,y(u-1,s));if(a>0){let w=v(d,s);for(let g=f;g<=m;g++)if(this.isSolid(g,w)){d=w*s.tileHeight,h=d-r,this.vy=0,this.onGround=true;break}}else {let w=v(h,s);for(let g=f;g<=m;g++)if(this.isSolid(g,w)){h=(w+1)*s.tileHeight,d=h+r,this.vy=0;break}}this.node.y=h;}isSolid(t,s){let e=this.settings.tileMap;return t<0||s<0||t>=e.width||s>=e.height?false:e.isSolid(t,s)}};c(j,"PhysicsBody");var ht=j;var F=class F{constructor(){i(this,"x",0);i(this,"y",0);i(this,"zoom",1);i(this,"rotation",0);i(this,"followTarget",null);i(this,"followLerp",.1);i(this,"focusX",.5);i(this,"focusY",.5);}follow(t,s=.1){this.followTarget=t,this.followLerp=s;}unfollow(){this.followTarget=null;}update(t){if(!this.followTarget)return;let s=this.followTarget.x,e=this.followTarget.y;this.x+=(s-this.x)*this.followLerp,this.y+=(e-this.y)*this.followLerp;}begin(t,s,e){t.save();let r=s*this.focusX,o=e*this.focusY;t.translate(r,o),this.rotation!==0&&t.rotate(-this.rotation),this.zoom!==1&&t.scale(this.zoom,this.zoom),t.translate(-this.x,-this.y);}end(t){t.restore();}};c(F,"Camera2D");var ct=F;var J=class J{constructor(){i(this,"x",0);i(this,"y",0);i(this,"rotation",0);i(this,"scaleX",1);i(this,"scaleY",1);i(this,"visible",true);i(this,"parent",null);i(this,"children",[]);}add(t){return t.parent=this,this.children.push(t),t}remove(t){let s=this.children.indexOf(t);s>=0&&(this.children.splice(s,1),t.parent=null);}clear(){for(let t of this.children)t.parent=null;this.children.length=0;}update(t){}drawSelf(t){}draw(t){if(this.visible){t.save(),t.translate(this.x,this.y),this.rotation!==0&&t.rotate(this.rotation),(this.scaleX!==1||this.scaleY!==1)&&t.scale(this.scaleX,this.scaleY),this.drawSelf(t);for(let s of this.children)s.draw(t);t.restore();}}updateTree(t){this.update(t);for(let s of this.children)s.updateTree(t);}getWorldPosition(){let t=this.x,s=this.y,e=this.parent;for(;e;)t+=e.x,s+=e.y,e=e.parent;return {x:t,y:s}}};c(J,"SceneNode");var k=J,G=class G extends k{constructor(s){super();i(this,"sprite");this.sprite=s;}update(s){this.sprite.update(s);}drawSelf(s){this.sprite.draw(s);}getWorldBounds(){let s=this.getWorldPosition();return {x:s.x,y:s.y,width:this.sprite.width*this.scaleX,height:this.sprite.height*this.scaleY}}};c(G,"SpriteNode");var lt=G,N=class N extends k{constructor(s=1,e=1){super();i(this,"parallaxX",1);i(this,"parallaxY",1);this.parallaxX=s,this.parallaxY=e;}};c(N,"SceneLayer");var _=N,M=class M{constructor(){i(this,"id");i(this,"root",new k);i(this,"layers",[]);this.id=M.id++;}addLayer(t=1,s=1){let e=new _(t,s);return this.layers.push(e),this.root.add(e),e}update(t){this.root.updateTree(t);}draw(t){this.root.draw(t);}};c(M,"Scene"),i(M,"id",0);var dt=M;function ut(l,t){return !(l.x+l.width<=t.x||l.x>=t.x+t.width||l.y+l.height<=t.y||l.y>=t.y+t.height)}c(ut,"aabbOverlap");var V=class V{constructor(){i(this,"colliders",[]);i(this,"rules",[]);i(this,"rulesByPair",new Map);}add(t){this.colliders.push(t);}remove(t){let s=this.colliders.indexOf(t);s>=0&&this.colliders.splice(s,1);}clear(){this.colliders.length=0;}onCollision(t,s,e){var a;let r={tagA:t,tagB:s,handler:e};this.rules.push(r);let o=this.ruleKey(t,s),n=(a=this.rulesByPair.get(o))!=null?a:[];n.push(r),this.rulesByPair.set(o,n);}update(){var r;let t=new Map,s=new Map;for(let o of this.colliders){if(!o.isActive)continue;let n=(r=t.get(o.tag))!=null?r:[];n.push(o),t.set(o.tag,n);}let e=c(o=>{let n=s.get(o);return n||(n=o.getBounds(),s.set(o,n)),n},"getBounds");for(let[o,n]of this.rulesByPair.entries()){let[a,h]=o.split("\0"),d=t.get(a),p=t.get(h);if(!(!d||!p))if(a===h)for(let u=0;u<d.length;u++){let f=d[u],m=e(f);for(let w=u+1;w<d.length;w++){let g=d[w];ut(m,e(g))&&this.triggerRules(f,g,n);}}else for(let u of d){let f=e(u);for(let m of p)ut(f,e(m))&&this.triggerRules(u,m,n);}}}ruleKey(t,s){return t<=s?`${t}\0${s}`:`${s}\0${t}`}triggerRules(t,s,e){for(let r of e){let o=r.tagA===t.tag&&r.tagB===s.tag,n=r.tagA===s.tag&&r.tagB===t.tag;o?r.handler(t,s):n&&r.handler(s,t);}}};c(V,"CollisionSystem");var ft=V,zt=0;function ls(l,t,s,e){return {id:`col-${zt++}`,tag:l,isActive:true,data:{node:t,body:s,sprite:e},getBounds(){let o=t.getWorldPosition(),n=s.settings.width,a=s.settings.height;return {x:o.x,y:o.y,width:n,height:a}}}}c(ls,"createSpritePhysicsCollider");var q=class q{constructor(t={}){i(this,"options");i(this,"opened",false);i(this,"socket",null);i(this,"currentUrl",null);i(this,"offMessage",null);i(this,"msgHandlers",[]);i(this,"openHandlers",[]);i(this,"closeHandlers",[]);i(this,"errorHandlers",[]);this.options=t;}async connect(t){if(this.socket){if(this.currentUrl===t&&this.socket.isConnected())return;if(this.currentUrl===t&&!this.opened){await this.socket.connect();return}await this.cleanupSocket(true);}let{protocols:s,connectTimeoutMs:e}=this.options,r=ouiderNetwork.OUIDNetwork.websocket(t,s,{connectTimeoutMs:e});this.socket=r,this.currentUrl=t,r.onOpen(()=>{this.opened=true,this.openHandlers.forEach(o=>o());}),this.offMessage=await r.onMessage(o=>{var a;let n=typeof o=="string"?o:(a=JSON.stringify(o))!=null?a:"";this.msgHandlers.forEach(h=>h(n));}),r.onClose(o=>{this.opened=false,this.closeHandlers.forEach(n=>n(o));}),r.onError(o=>{this.errorHandlers.forEach(n=>n(o));});try{await r.connect();}catch(o){throw this.socket===r&&await this.cleanupSocket(false),o}}async send(t){var s;await((s=this.socket)==null?void 0:s.send(t));}async close(t,s){await this.cleanupSocket(true,t,s);}isConnected(){return this.opened}onMessage(t){this.msgHandlers.push(t);}onOpen(t){this.openHandlers.push(t);}offOpen(t){this.openHandlers=this.openHandlers.filter(s=>s!==t);}onClose(t){this.closeHandlers.push(t);}offClose(t){this.closeHandlers=this.closeHandlers.filter(s=>s!==t);}onError(t){this.errorHandlers.push(t);}offError(t){this.errorHandlers=this.errorHandlers.filter(s=>s!==t);}async cleanupSocket(t,s,e){let r=this.socket;this.socket=null,this.currentUrl=null,this.opened=false;let o=this.offMessage;this.offMessage=null,await(o==null?void 0:o().catch(()=>{})),t&&await(r==null?void 0:r.close(s,e));}};c(q,"WebSocketTransport");var pt=q,K=class K{constructor(t){i(this,"transport");i(this,"handlers",new Map);i(this,"rpcResolvers",new Map);i(this,"rpcCounter",0);i(this,"handleRawMessage",c(t=>{if(!t)return;let s;try{s=JSON.parse(t);}catch(o){return}let e=this.extractRpcId(s);if(e){let o=this.rpcResolvers.get(e);o&&(o.resolve(this.extractRpcValue(s)),this.rpcResolvers.delete(e));}let r=this.handlers.get(s.type);if(r)for(let o of r)o(s);},"handleRawMessage"));this.transport=t,this.transport.onMessage(this.handleRawMessage),this.transport.onClose(()=>this.rejectPendingRpc(new Error("Network connection closed"))),this.transport.onError(s=>{this.rejectPendingRpc(s instanceof Error?s:new Error("Network connection error"));});}async connect(t){await this.transport.connect(t);}isConnected(){return this.transport.isConnected()}send(t,s){let e={type:t,payload:s};return this.transport.send(JSON.stringify(e))}on(t,s){var r;let e=(r=this.handlers.get(t))!=null?r:[];e.push(s),this.handlers.set(t,e);}off(t,s){let e=this.handlers.get(t);e&&this.handlers.set(t,e.filter(r=>r!==s));}call(t,s){let e=`rpc_${++this.rpcCounter}_${Date.now()}`,r=s&&typeof s=="object"?it(et({},s),{rpcId:e}):{value:s,rpcId:e},o={type:t,payload:r};return new Promise((n,a)=>{this.rpcResolvers.set(e,{resolve:n,reject:a}),this.transport.send(JSON.stringify(o)).catch(h=>{this.rpcResolvers.delete(e),a(h);});})}close(){return this.rejectPendingRpc(new Error("Network client closed")),this.transport.close()}extractRpcId(t){if(typeof t.rpcId=="string")return t.rpcId;if(t.payload&&typeof t.payload=="object"&&"rpcId"in t.payload){let s=t.payload.rpcId;return typeof s=="string"?s:void 0}}extractRpcValue(t){return "result"in t?t.result:t.payload}rejectPendingRpc(t){this.rpcResolvers.forEach(({reject:s})=>s(t)),this.rpcResolvers.clear();}};c(K,"NetworkClient");var gt=K,Q=class Q{constructor(t,s,e){i(this,"net");i(this,"channels");i(this,"state");i(this,"onChangeHandlers",[]);var r;this.net=t,this.channels=e,this.state=s,this.net.on((r=e==null?void 0:e.update)!=null?r:"state_update",o=>{let{payload:n}=o;n.full?this.state=n.full:n.patch&&(this.state=Object.assign({},this.state,n.patch)),this.emitChange();});}getState(){return this.state}onChange(t){this.onChangeHandlers.push(t);}emitChange(){for(let t of this.onChangeHandlers)t(this.state);}sendPatch(t){var s,e;return this.net.send((e=(s=this.channels)==null?void 0:s.patch)!=null?e:"state_patch",t)}};c(Q,"StateSyncClient");var wt=Q;exports.AssetManager=Y;exports.AudioManager=T;exports.Camera2D=ct;exports.CollisionSystem=ft;exports.Game=A;exports.InputManager=z;exports.NetworkClient=gt;exports.PhysicsBody=ht;exports.Scene=dt;exports.SceneLayer=_;exports.SceneNode=k;exports.Sprite=ot;exports.SpriteAnimation=X;exports.SpriteLayer=rt;exports.SpriteNode=lt;exports.SpriteSheet=D;exports.StateSyncClient=wt;exports.TileRenderer=at;exports.WebSocketTransport=pt;exports.aabbOverlap=ut;exports.createSpritePhysicsCollider=ls;exports.getTileSourceRect=Tt;exports.objectSize=H;exports.tulon=Kt;exports.worldToTileX=y;exports.worldToTileY=v;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {ODOM,OUID as OUID$1}from'@ouidesigner/ouider';var et=Object.defineProperty,bt=Object.defineProperties;var Dt=Object.getOwnPropertyDescriptors;var tt=Object.getOwnPropertySymbols;var Ct=Object.prototype.hasOwnProperty,Et=Object.prototype.propertyIsEnumerable;var Y=(d,t,e)=>t in d?et(d,t,{enumerable:true,configurable:true,writable:true,value:e}):d[t]=e,st=(d,t)=>{for(var e in t||(t={}))Ct.call(t,e)&&Y(d,e,t[e]);if(tt)for(var e of tt(t))Et.call(t,e)&&Y(d,e,t[e]);return d},it=(d,t)=>bt(d,Dt(t)),c=(d,t)=>et(d,"name",{value:t,configurable:true});var i=(d,t,e)=>Y(d,typeof t!="symbol"?t+"":t,e);var z=class z{constructor(t,e,s,o,n=0){i(this,"image");i(this,"frameWidth");i(this,"frameHeight");i(this,"frameCount");i(this,"from");i(this,"columns");this.image=t,this.frameWidth=e,this.frameHeight=s,this.frameCount=o,this.from=n;let r=I(t);this.columns=Math.max(1,Math.floor(r.width/e));}};c(z,"SpriteSheet");var E=z,S=class S{constructor(t,e=10,s=true){i(this,"sheet");i(this,"fps");i(this,"loop");i(this,"time",0);this.sheet=t,this.fps=e,this.loop=s;}update(t){this.time+=t;let e=this.sheet.frameCount/this.fps;this.loop?this.time=this.time%e:this.time>e&&(this.time=e);}draw(t,e,s,o=1,n=1){let r=Math.floor(this.time*this.fps)+this.sheet.from,a=Math.min(r,this.sheet.frameCount+this.sheet.from-1),h=this.sheet.columns,l=a%h*this.sheet.frameWidth,p=Math.floor(a/h)*this.sheet.frameHeight,u=this.sheet.frameWidth*o,f=this.sheet.frameHeight*n;t.drawImage(this.sheet.image,l,p,this.sheet.frameWidth,this.sheet.frameHeight,e,s,u,f);}reset(){this.time=0;}};c(S,"SpriteAnimation");var O=S,L=class L{constructor(t){i(this,"animation");i(this,"width");i(this,"height");i(this,"visible");i(this,"tag");i(this,"data");var e,s,o,n;this.animation=t.animation,this.visible=(e=t.visible)!=null?e:true,this.tag=t.tag,this.data=(s=t.data)!=null?s:{},this.width=(o=t.width)!=null?o:this.animation.sheet.frameWidth,this.height=(n=t.height)!=null?n:this.animation.sheet.frameHeight;}update(t){this.animation.update(t);}draw(t,e=1,s=1){this.visible&&this.animation.draw(t,0,0,e,s);}};c(L,"Sprite");var ot=L,W=class W{constructor(){i(this,"sprites",[]);}add(t){return this.sprites.push(t),t}remove(t){let e=this.sprites.indexOf(t);e>=0&&this.sprites.splice(e,1);}clear(){this.sprites.length=0;}update(t){for(let e of this.sprites)e.update(t);}draw(t){for(let e of this.sprites)e.draw(t);}withTag(t){return this.sprites.filter(e=>e.tag===t)}};c(W,"SpriteLayer");var nt=W;var B=class B{constructor(){i(this,"imageDefs",new Map);i(this,"soundDefs",new Map);i(this,"sheetDefs",new Map);i(this,"images",new Map);i(this,"sounds",new Map);i(this,"sheets",new Map);}image(t,e){this.imageDefs.set(t,e);}sound(t,e){this.soundDefs.set(t,e);}spriteSheet(t,e){this.sheetDefs.set(t,e);}async loadAll(){var s,o;let t=[],e=[];for(let[n,r]of this.imageDefs.entries())t.push(this.loadImage(r).then(a=>{this.images.set(n,a);}));for(let[n,r]of this.soundDefs.entries())e.push(this.loadSound(r).then(a=>{this.sounds.set(n,a);}));await Promise.all([...t,...e]);for(let[n,r]of this.sheetDefs.entries()){let a=(s=this.getImage(r.image))!=null?s:this.images.get(r.image);if(!a)throw new Error(`SpriteSheet "${n}" missing image "${r.image}"`);let h=new E(a,r.frameWidth,r.frameHeight,r.frameCount,(o=r.from)!=null?o:0);this.sheets.set(n,h);}}async loadImage(t){let e=await ODOM.Image.new();if(!e)throw new Error(`Unable to create image for "${t}"`);let s=null,o=null,n=false,r=c(async()=>{let h=[s,o].filter(Boolean);s=null,o=null,await Promise.all(h.map(l=>e.dettachEventListener(l).catch(()=>{})));},"detach"),a=c(async()=>{let[h,l,p,u]=await Promise.all([e.naturalWidth(),e.naturalHeight(),e.width(),e.height()]);e.__local_size__={width:h||p||0,height:l||u||0};},"cacheSize");return new Promise(async(h,l)=>{let p=c(async()=>{if(!n)try{await a(),n=!0,await r(),h(e);}catch(f){await u(f);}},"done"),u=c(async f=>{n||(n=true,await r(),await e.release().catch(()=>{}),l(new Error(`Unable to load image "${t}"`)));},"fail");try{s=await e.addEventListener("load",()=>{p();},{options:{once:!0}}),o=await e.addEventListener("error",()=>{u();},{options:{once:!0}}),await e.setSrc(t);let[f,m]=await Promise.all([e.getProperty("complete").catch(()=>!1),e.naturalWidth().catch(()=>0)]);f&&m>0&&await p();}catch(f){await u(f);}})}async loadSound(t){return await OUID.fetch(t,{method:"GET",headers:{},body:null,credentials:"omit"},"buffer")}getImage(t){return this.images.get(t)}getSpriteSheet(t){return this.sheets.get(t)}createAnimation(t,e,s){let o=this.getSpriteSheet(t);if(!o)throw new Error(`SpriteSheet "${t}" not found`);return new O(o,e!=null?e:10,s!=null?s:true)}getSound(t){return this.sounds.get(t)}clean(){this.images.forEach(t=>t.release()),this.sounds.forEach(t=>t.release()),this.images.clear(),this.sounds.clear(),this.sheets.clear();}};c(B,"AssetManager");var H=B;var A=class A{constructor(t){i(this,"assets");i(this,"audioContext",null);i(this,"audioDestination",null);i(this,"decodedBuffers",new Map);this.assets=t;}async getContext(){if(this.audioContext)return this.audioContext;let t=await OUID.newObject("AudioContext");return t?(this.audioContext=ODOM.OObject.toInstance(t),this.audioContext):null}async getDestination(t){if(this.audioDestination)return this.audioDestination;let e=await t.getPropertyAndHold("destination");return e?(this.audioDestination=e,this.audioDestination):null}async getBuffer(t){if(this.decodedBuffers.has(t))return this.decodedBuffers.get(t);let e=this.assets.getSound(t);if(!e)return null;let s=await this.getContext();if(!s)return null;let o=await s.invokeAndHold("decodeAudioData",e.ref);return o&&this.decodedBuffers.set(t,o),o}clean(){this.decodedBuffers.forEach(t=>t.release()),this.decodedBuffers.clear(),this.audioDestination&&(this.audioDestination.release(),this.audioDestination=null),this.audioContext&&(this.audioContext.invoke("close"),this.audioContext.release(),this.audioContext=null);}async play(t,e){let s=await this.getContext();if(!s)return;let o=await this.getBuffer(t);if(!o)return;await s.invoke("resume");let n=await s.invokeAndHold("createBufferSource");if(!n)return;await n.setProperty("buffer",o.ref),(e==null?void 0:e.loop)!=null&&await n.setProperty("loop",e.loop);let r=await this.getDestination(s);if(!r){n.release();return}if((e==null?void 0:e.volume)!=null){let a=await s.invokeAndHold("createGain");if(a){let h=await a.getPropertyAndHold("gain");h&&(await h.setProperty("value",e.volume),h.release()),await n.invoke("connect",a.ref),await a.invoke("connect",r.ref),await n.invoke("start",0),a.release(),n.release();return}}await n.invoke("connect",r.ref),await n.invoke("start",0),n.release();}};c(A,"AudioManager");var X=A;var R=class R{constructor(t){i(this,"keysDown",new Set);i(this,"keysPressed",new Set);i(this,"buttonsPressed",new Set);i(this,"buttonsDown",new Set);i(this,"mouseX",-1);i(this,"mouseY",-1);i(this,"offsetX",-1);i(this,"offsetY",-1);i(this,"ids",[]);i(this,"disposed",false);this.track(OUID.addEventListener("window","keydown",e=>{this.keysDown.has(e.code)||this.keysPressed.add(e.code),this.keysDown.add(e.code);})),this.track(OUID.addEventListener("window","keyup",e=>{this.keysDown.delete(e.code);})),this.track(t.addEventListener("mousedown",e=>{let s=e.button===0?"MouseLeft":"MouseRight";this.buttonsDown.has(s)||this.buttonsPressed.add(s),this.buttonsDown.add(s),this.updateMousePosition(e);})),this.track(t.addEventListener("mouseup",e=>{let s=e.button===0?"MouseLeft":"MouseRight";this.buttonsDown.delete(s),this.updateMousePosition(e);})),this.track(t.addEventListener("mousemove",e=>{this.updateMousePosition(e);})),this.track(t.addEventListener("mouseleave",()=>{this.buttonsDown.clear();}));}update(){this.keysPressed.clear(),this.buttonsPressed.clear();}isDown(t){return this.keysDown.has(t)||this.buttonsDown.has(t)}isPressed(t){return this.keysPressed.has(t)||this.buttonsPressed.has(t)}clean(){this.disposed=true,this.ids.forEach(t=>{OUID.dettachEventListener(t);}),this.ids=[],this.keysDown.clear(),this.keysPressed.clear(),this.buttonsDown.clear(),this.buttonsPressed.clear();}track(t){t.then(e=>{this.disposed?OUID.dettachEventListener(e):this.ids.push(e);}).catch(()=>{});}updateMousePosition(t){var e,s,o,n;this.mouseX=(e=t.x)!=null?e:this.mouseX,this.mouseY=(s=t.y)!=null?s:this.mouseY,this.offsetX=(o=t.offsetX)!=null?o:this.offsetX,this.offsetY=(n=t.offsetY)!=null?n:this.offsetY;}};c(R,"InputManager");var T=R;var $=class ${constructor(t){i(this,"config");i(this,"canvas");i(this,"ctx2d");i(this,"assets");i(this,"audio");i(this,"input");i(this,"world",{});i(this,"running",false);i(this,"initialized",false);i(this,"lastTime",0);i(this,"frameId",null);i(this,"resizeListenerId",null);this.config=t,this.canvas=t.canvas,this.assets=new H,this.audio=new X(this.assets),this.input=new T(this.canvas);}buildContext(t){return {dt:t,canvas:this.canvas,ctx2d:this.ctx2d,world:this.world,assets:this.assets,audio:this.audio,input:this.input}}async updateLocalSize(){var s,o;let t=(s=await this.canvas.width())!=null?s:0,e=(o=await this.canvas.height())!=null?o:0;this.canvas.__local_size__=this.ctx2d.__local_size__={width:t,height:e};}async syncSize(){var a,h;let[t,e,s,o]=await Promise.all([this.canvas.getProperty("clientWidth"),this.canvas.getProperty("clientHeight"),this.canvas.width(),this.canvas.height()]),n=(a=t!=null?t:s)!=null?a:0,r=(h=e!=null?e:o)!=null?h:0;await this.canvas.setProperty("width",n),await this.canvas.setProperty("height",r);}async init(){if(this.initialized)return;let t=await this.canvas.getContext("2d");if(!t)throw new Error("Unable to create a context");this.ctx2d=t,this.resizeListenerId=await this.canvas.addEventListener("resize",()=>{this.syncSize().then(()=>this.updateLocalSize());}),this.config.preload&&(this.config.preload(this.assets),await this.assets.loadAll()),await this.syncSize(),await this.updateLocalSize(),this.config.setup&&this.config.setup(this.buildContext(0)),this.initialized=true;}start(){this.running||(this.running=true,this.lastTime=performance.now(),this.scheduleFrame());}stop(){var t;!this.running&&!this.initialized||(this.running=false,this.frameId!=null&&(OUID.cancelAnimationFrame(this.frameId),this.frameId=null),this.resizeListenerId&&(OUID.dettachEventListener(this.resizeListenerId),this.resizeListenerId=null),this.audio.clean(),this.assets.clean(),this.input.clean(),(t=this.ctx2d)==null||t.release(),this.initialized=false);}scheduleFrame(){OUID.requestAnimationFrame(t=>{this.frameId=null,this.tick(t);}).then(t=>{this.running?this.frameId=t:OUID.cancelAnimationFrame(t);});}async tick(t){var o,n,r,a;if(!this.running)return;let s=Math.min((t-this.lastTime)/1e3,.1);this.lastTime=t;try{let h=this.buildContext(s);(n=(o=this.config).update)==null||n.call(o,h),this.input.update(),await((a=(r=this.config).draw)==null?void 0:a.call(r,h)),await this.ctx2d.commit();}catch(h){console.error("[Toubani] game loop error",h);}finally{this.running&&this.scheduleFrame();}}};c($,"Game");var U=$;function Kt(d){return new U(d)}c(Kt,"tulon");function I(d){var t;return (t=d.__local_size__)!=null?t:{width:0,height:0}}c(I,"objectSize");function v(d,t){return Math.floor(d/t.tileWidth)}c(v,"worldToTileX");function M(d,t){return Math.floor(d/t.tileHeight)}c(M,"worldToTileY");function Ht(d,t){let{image:e,tileWidth:s,tileHeight:o,margin:n=0,spacing:r=0}=t,a=I(e),h=Math.max(1,Math.floor((a.width-2*n+r)/(s+r))),l=d-1,p=l%h,u=Math.floor(l/h),f=n+p*(s+r),m=n+u*(o+r);return {sx:f,sy:m,sw:s,sh:o}}c(Ht,"getTileSourceRect");var N=class N{constructor(t){i(this,"tileset");i(this,"sourceRectCache",new Map);this.tileset=t;}draw(t,e,s,o,n){let{tileWidth:r,tileHeight:a}=e,h=o*s.focusX,l=n*s.focusY,p=s.zoom,u=s.x-h/p,f=s.y-l/p,m=s.x+(o-h)/p,w=s.y+(n-l)/p,g=Math.max(0,Math.floor(u/r)),y=Math.min(e.width-1,Math.floor(m/r)),b=Math.max(0,Math.floor(f/a)),mt=Math.min(e.height-1,Math.floor(w/a));for(let D=b;D<=mt;D++)for(let C=g;C<=y;C++){let _=e.getTile(C,D);if(_<=0)continue;let{sx:xt,sy:yt,sw:vt,sh:Mt}=this.getSourceRect(_),kt=C*r,Pt=D*a;t.drawImage(this.tileset.image,xt,yt,vt,Mt,kt,Pt,r,a);}}getSourceRect(t){let e=this.sourceRectCache.get(t);return e||(e=Ht(t,this.tileset),this.sourceRectCache.set(t,e)),e}};c(N,"TileRenderer");var rt=N;var j=class j{constructor(t,e){i(this,"node");i(this,"settings");i(this,"vx",0);i(this,"vy",0);i(this,"onGround",false);this.node=t,this.settings=e;}update(t){var f,m,w,g,y;let e=this.settings,s=(f=e.gravityX)!=null?f:0,o=(m=e.gravityY)!=null?m:1e3,n=(w=e.maxFallSpeed)!=null?w:2e3,r=(g=e.friction)!=null?g:.8,a=(y=e.airFriction)!=null?y:.98;this.vx+=s*t,this.vy+=o*t,this.vy>n&&(this.vy=n);let h=Math.max(1,Math.min(e.tileMap.tileWidth,e.tileMap.tileHeight)*.5),l=Math.max(Math.abs(this.vx*t),Math.abs(this.vy*t)),p=Math.max(1,Math.ceil(l/h)),u=t/p;for(let b=0;b<p;b++)this.moveAndCollideX(u),this.moveAndCollideY(u);this.onGround?this.vx*=r:this.vx*=a;}moveAndCollideX(t){let e=this.settings.tileMap,s=this.settings.width,o=this.settings.height,n=this.node.x+this.vx*t,r=this.node.y,a=Math.sign(this.vx);if(a===0){this.node.x=n;return}let h=n,l=n+s,p=r,u=r+o,f=Math.max(0,M(p,e)),m=Math.min(e.height-1,M(u-1,e));if(a>0){let w=v(l,e);for(let g=f;g<=m;g++)if(this.isSolid(w,g)){l=w*e.tileWidth,h=l-s,this.vx=0;break}}else {let w=v(h,e);for(let g=f;g<=m;g++)if(this.isSolid(w,g)){h=(w+1)*e.tileWidth,l=h+s,this.vx=0;break}}this.node.x=h;}moveAndCollideY(t){let e=this.settings.tileMap,s=this.settings.width,o=this.settings.height,n=this.node.x,r=this.node.y+this.vy*t,a=Math.sign(this.vy);if(this.onGround=false,a===0){this.node.y=r;return}let h=r,l=r+o,p=n,u=n+s,f=Math.max(0,v(p,e)),m=Math.min(e.width-1,v(u-1,e));if(a>0){let w=M(l,e);for(let g=f;g<=m;g++)if(this.isSolid(g,w)){l=w*e.tileHeight,h=l-o,this.vy=0,this.onGround=true;break}}else {let w=M(h,e);for(let g=f;g<=m;g++)if(this.isSolid(g,w)){h=(w+1)*e.tileHeight,l=h+o,this.vy=0;break}}this.node.y=h;}isSolid(t,e){let s=this.settings.tileMap;return t<0||e<0||t>=s.width||e>=s.height?false:s.isSolid(t,e)}};c(j,"PhysicsBody");var at=j;var F=class F{constructor(){i(this,"x",0);i(this,"y",0);i(this,"zoom",1);i(this,"rotation",0);i(this,"followTarget",null);i(this,"followLerp",.1);i(this,"focusX",.5);i(this,"focusY",.5);}follow(t,e=.1){this.followTarget=t,this.followLerp=e;}unfollow(){this.followTarget=null;}update(t){if(!this.followTarget)return;let e=this.followTarget.x,s=this.followTarget.y;this.x+=(e-this.x)*this.followLerp,this.y+=(s-this.y)*this.followLerp;}begin(t,e,s){t.save();let o=e*this.focusX,n=s*this.focusY;t.translate(o,n),this.rotation!==0&&t.rotate(-this.rotation),this.zoom!==1&&t.scale(this.zoom,this.zoom),t.translate(-this.x,-this.y);}end(t){t.restore();}};c(F,"Camera2D");var ht=F;var J=class J{constructor(){i(this,"x",0);i(this,"y",0);i(this,"rotation",0);i(this,"scaleX",1);i(this,"scaleY",1);i(this,"visible",true);i(this,"parent",null);i(this,"children",[]);}add(t){return t.parent=this,this.children.push(t),t}remove(t){let e=this.children.indexOf(t);e>=0&&(this.children.splice(e,1),t.parent=null);}clear(){for(let t of this.children)t.parent=null;this.children.length=0;}update(t){}drawSelf(t){}draw(t){if(this.visible){t.save(),t.translate(this.x,this.y),this.rotation!==0&&t.rotate(this.rotation),(this.scaleX!==1||this.scaleY!==1)&&t.scale(this.scaleX,this.scaleY),this.drawSelf(t);for(let e of this.children)e.draw(t);t.restore();}}updateTree(t){this.update(t);for(let e of this.children)e.updateTree(t);}getWorldPosition(){let t=this.x,e=this.y,s=this.parent;for(;s;)t+=s.x,e+=s.y,s=s.parent;return {x:t,y:e}}};c(J,"SceneNode");var P=J,q=class q extends P{constructor(e){super();i(this,"sprite");this.sprite=e;}update(e){this.sprite.update(e);}drawSelf(e){this.sprite.draw(e);}getWorldBounds(){let e=this.getWorldPosition();return {x:e.x,y:e.y,width:this.sprite.width*this.scaleX,height:this.sprite.height*this.scaleY}}};c(q,"SpriteNode");var ct=q,K=class K extends P{constructor(e=1,s=1){super();i(this,"parallaxX",1);i(this,"parallaxY",1);this.parallaxX=e,this.parallaxY=s;}};c(K,"SceneLayer");var G=K,k=class k{constructor(){i(this,"id");i(this,"root",new P);i(this,"layers",[]);this.id=k.id++;}addLayer(t=1,e=1){let s=new G(t,e);return this.layers.push(s),this.root.add(s),s}update(t){this.root.updateTree(t);}draw(t){this.root.draw(t);}};c(k,"Scene"),i(k,"id",0);var lt=k;function dt(d,t){return !(d.x+d.width<=t.x||d.x>=t.x+t.width||d.y+d.height<=t.y||d.y>=t.y+t.height)}c(dt,"aabbOverlap");var Q=class Q{constructor(){i(this,"colliders",[]);i(this,"rules",[]);i(this,"rulesByPair",new Map);}add(t){this.colliders.push(t);}remove(t){let e=this.colliders.indexOf(t);e>=0&&this.colliders.splice(e,1);}clear(){this.colliders.length=0;}onCollision(t,e,s){var a;let o={tagA:t,tagB:e,handler:s};this.rules.push(o);let n=this.ruleKey(t,e),r=(a=this.rulesByPair.get(n))!=null?a:[];r.push(o),this.rulesByPair.set(n,r);}update(){var o;let t=new Map,e=new Map;for(let n of this.colliders){if(!n.isActive)continue;let r=(o=t.get(n.tag))!=null?o:[];r.push(n),t.set(n.tag,r);}let s=c(n=>{let r=e.get(n);return r||(r=n.getBounds(),e.set(n,r)),r},"getBounds");for(let[n,r]of this.rulesByPair.entries()){let[a,h]=n.split("\0"),l=t.get(a),p=t.get(h);if(!(!l||!p))if(a===h)for(let u=0;u<l.length;u++){let f=l[u],m=s(f);for(let w=u+1;w<l.length;w++){let g=l[w];dt(m,s(g))&&this.triggerRules(f,g,r);}}else for(let u of l){let f=s(u);for(let m of p)dt(f,s(m))&&this.triggerRules(u,m,r);}}}ruleKey(t,e){return t<=e?`${t}\0${e}`:`${e}\0${t}`}triggerRules(t,e,s){for(let o of s){let n=o.tagA===t.tag&&o.tagB===e.tag,r=o.tagA===e.tag&&o.tagB===t.tag;n?o.handler(t,e):r&&o.handler(e,t);}}};c(Q,"CollisionSystem");var ut=Q,Xt=0;function he(d,t,e,s){return {id:`col-${Xt++}`,tag:d,isActive:true,data:{node:t,body:e,sprite:s},getBounds(){let n=t.getWorldPosition(),r=e.settings.width,a=e.settings.height;return {x:n.x,y:n.y,width:r,height:a}}}}c(he,"createSpritePhysicsCollider");var x=class x{constructor(){i(this,"opened",false);i(this,"socket",null);i(this,"msgHandlers",[]);i(this,"openHandlers",[]);i(this,"closeHandlers",[]);i(this,"errorHandlers",[]);i(this,"ids",[]);}async connect(t){if(this.socket){let s=await this.socket.getProperty("readyState");if(s===x.OPEN||s===x.CONNECTING)return;await this.cleanupSocket(false);}let e=await OUID$1.newObject("WebSocket",t);if(!e)throw new Error(`Unable to create WebSocket for ${t}`);this.socket=ODOM.OObject.toInstance(e),this.ids.push(await this.socket.addEventListener("open",()=>{this.opened=true,this.openHandlers.forEach(s=>s());})),this.ids.push(await this.socket.addEventListener("message",s=>{let o=typeof s.data=="string"?s.data:"";this.msgHandlers.forEach(n=>n(o));})),this.ids.push(await this.socket.addEventListener("close",s=>{this.opened=false,this.closeHandlers.forEach(o=>o(s));})),this.ids.push(await this.socket.addEventListener("error",s=>{this.errorHandlers.forEach(o=>o(s));}));try{await new Promise((s,o)=>{let n=c(()=>{h(),s();},"onOpen"),r=c(l=>{h(),o(l);},"onError"),a=c(l=>{h(),o(l);},"onClose"),h=c(()=>{this.offOpen(n),this.offError(r),this.offClose(a);},"cleanup");this.onOpen(n),this.onError(r),this.onClose(a);});}catch(s){throw await this.cleanupSocket(false),s}}async send(t){this.socket&&await this.socket.getProperty("readyState")===x.OPEN&&await this.socket.invoke("send",t);}async close(t,e){await this.cleanupSocket(true,t,e);}isConnected(){return this.opened}onMessage(t){this.msgHandlers.push(t);}onOpen(t){this.openHandlers.push(t);}offOpen(t){this.openHandlers=this.openHandlers.filter(e=>e!==t);}onClose(t){this.closeHandlers.push(t);}offClose(t){this.closeHandlers=this.closeHandlers.filter(e=>e!==t);}onError(t){this.errorHandlers.push(t);}offError(t){this.errorHandlers=this.errorHandlers.filter(e=>e!==t);}async cleanupSocket(t,e,s){let o=this.socket;this.socket=null,this.opened=false;let n=this.ids.splice(0);if(await Promise.all(n.map(r=>OUID$1.dettachEventListener(r).catch(()=>{}))),!!o)try{if(t){let r=await o.getProperty("readyState").catch(()=>null);(r===x.OPEN||r===x.CONNECTING)&&await o.invoke("close",e,s);}}finally{await o.release().catch(()=>{});}}};c(x,"WebSocketTransport"),i(x,"OPEN",1),i(x,"CONNECTING",0);var pt=x,V=class V{constructor(t){i(this,"transport");i(this,"handlers",new Map);i(this,"rpcResolvers",new Map);i(this,"rpcCounter",0);i(this,"handleRawMessage",c(t=>{if(!t)return;let e;try{e=JSON.parse(t);}catch(o){return}if(e.payload&&typeof e.payload=="object"&&"rpcId"in e.payload){let o=e.payload.rpcId,n=this.rpcResolvers.get(o);n&&(n.resolve(e.payload),this.rpcResolvers.delete(o));}let s=this.handlers.get(e.type);if(s)for(let o of s)o(e);},"handleRawMessage"));this.transport=t,this.transport.onMessage(this.handleRawMessage);}async connect(t){await this.transport.connect(t);}isConnected(){return this.transport.isConnected()}send(t,e){let s={type:t,payload:e};this.transport.send(JSON.stringify(s));}on(t,e){var o;let s=(o=this.handlers.get(t))!=null?o:[];s.push(e),this.handlers.set(t,s);}off(t,e){let s=this.handlers.get(t);s&&this.handlers.set(t,s.filter(o=>o!==e));}call(t,e){let s=`rpc_${++this.rpcCounter}_${Date.now()}`,o=e&&typeof e=="object"?it(st({},e),{rpcId:s}):{value:e,rpcId:s},n={type:t,payload:o};return new Promise((r,a)=>{this.rpcResolvers.set(s,{resolve:r,reject:a}),this.transport.send(JSON.stringify(n)).catch(h=>{this.rpcResolvers.delete(s),a(h);});})}close(){return this.rpcResolvers.forEach(({reject:t})=>t(new Error("Network client closed"))),this.rpcResolvers.clear(),this.transport.close()}};c(V,"NetworkClient");var gt=V,Z=class Z{constructor(t,e,s){i(this,"net");i(this,"channels");i(this,"state");i(this,"onChangeHandlers",[]);var o;this.net=t,this.channels=s,this.state=e,this.net.on((o=s==null?void 0:s.update)!=null?o:"state_update",n=>{let{payload:r}=n;r.full?this.state=r.full:r.patch&&(this.state=Object.assign({},this.state,r.patch)),this.emitChange();});}getState(){return this.state}onChange(t){this.onChangeHandlers.push(t);}emitChange(){for(let t of this.onChangeHandlers)t(this.state);}sendPatch(t){var e,s;this.net.send((s=(e=this.channels)==null?void 0:e.patch)!=null?s:"state_patch",t);}};c(Z,"StateSyncClient");var wt=Z;export{H as AssetManager,X as AudioManager,ht as Camera2D,ut as CollisionSystem,U as Game,T as InputManager,gt as NetworkClient,at as PhysicsBody,lt as Scene,G as SceneLayer,P as SceneNode,ot as Sprite,O as SpriteAnimation,nt as SpriteLayer,ct as SpriteNode,E as SpriteSheet,wt as StateSyncClient,rt as TileRenderer,pt as WebSocketTransport,dt as aabbOverlap,he as createSpritePhysicsCollider,Ht as getTileSourceRect,I as objectSize,Kt as tulon,v as worldToTileX,M as worldToTileY};
|
|
1
|
+
import {ODOM,OUID as OUID$1}from'@ouidesigner/ouider';import'@ouidesigner/ouider-device';import {OUIDNetwork}from'@ouidesigner/ouider-network';var st=Object.defineProperty,Pt=Object.defineProperties;var bt=Object.getOwnPropertyDescriptors;var tt=Object.getOwnPropertySymbols;var Dt=Object.prototype.hasOwnProperty,Xt=Object.prototype.propertyIsEnumerable;var E=(l,t,s)=>t in l?st(l,t,{enumerable:true,configurable:true,writable:true,value:s}):l[t]=s,et=(l,t)=>{for(var s in t||(t={}))Dt.call(t,s)&&E(l,s,t[s]);if(tt)for(var s of tt(t))Xt.call(t,s)&&E(l,s,t[s]);return l},it=(l,t)=>Pt(l,bt(t)),c=(l,t)=>st(l,"name",{value:t,configurable:true});var i=(l,t,s)=>E(l,typeof t!="symbol"?t+"":t,s);var S=class S{constructor(t,s,e,r,o=0){i(this,"image");i(this,"frameWidth");i(this,"frameHeight");i(this,"frameCount");i(this,"from");i(this,"columns");this.image=t,this.frameWidth=s,this.frameHeight=e,this.frameCount=r,this.from=o;let n=H(t);this.columns=Math.max(1,Math.floor(n.width/s));}};c(S,"SpriteSheet");var D=S,C=class C{constructor(t,s=10,e=true){i(this,"sheet");i(this,"fps");i(this,"loop");i(this,"time",0);this.sheet=t,this.fps=s,this.loop=e;}update(t){this.time+=t;let s=this.sheet.frameCount/this.fps;this.loop?this.time=this.time%s:this.time>s&&(this.time=s);}draw(t,s,e,r=1,o=1){let n=Math.floor(this.time*this.fps)+this.sheet.from,a=Math.min(n,this.sheet.frameCount+this.sheet.from-1),h=this.sheet.columns,d=a%h*this.sheet.frameWidth,p=Math.floor(a/h)*this.sheet.frameHeight,u=this.sheet.frameWidth*r,f=this.sheet.frameHeight*o;t.drawImage(this.sheet.image,d,p,this.sheet.frameWidth,this.sheet.frameHeight,s,e,u,f);}reset(){this.time=0;}};c(C,"SpriteAnimation");var X=C,R=class R{constructor(t){i(this,"animation");i(this,"width");i(this,"height");i(this,"visible");i(this,"tag");i(this,"data");var s,e,r,o;this.animation=t.animation,this.visible=(s=t.visible)!=null?s:true,this.tag=t.tag,this.data=(e=t.data)!=null?e:{},this.width=(r=t.width)!=null?r:this.animation.sheet.frameWidth,this.height=(o=t.height)!=null?o:this.animation.sheet.frameHeight;}update(t){this.animation.update(t);}draw(t,s=1,e=1){this.visible&&this.animation.draw(t,0,0,s,e);}};c(R,"Sprite");var ot=R,W=class W{constructor(){i(this,"sprites",[]);}add(t){return this.sprites.push(t),t}remove(t){let s=this.sprites.indexOf(t);s>=0&&this.sprites.splice(s,1);}clear(){this.sprites.length=0;}update(t){for(let s of this.sprites)s.update(t);}draw(t){for(let s of this.sprites)s.draw(t);}withTag(t){return this.sprites.filter(s=>s.tag===t)}};c(W,"SpriteLayer");var rt=W;var L=class L{constructor(){i(this,"imageDefs",new Map);i(this,"soundDefs",new Map);i(this,"sheetDefs",new Map);i(this,"images",new Map);i(this,"sounds",new Map);i(this,"sheets",new Map);}image(t,s){this.imageDefs.set(t,s);}sound(t,s){this.soundDefs.set(t,s);}spriteSheet(t,s){this.sheetDefs.set(t,s);}async loadAll(){var e,r;let t=[],s=[];for(let[o,n]of this.imageDefs.entries())t.push(this.loadImage(n).then(a=>{this.images.set(o,a);}));for(let[o,n]of this.soundDefs.entries())s.push(this.loadSound(n).then(a=>{this.sounds.set(o,a);}));await Promise.all([...t,...s]);for(let[o,n]of this.sheetDefs.entries()){let a=(e=this.getImage(n.image))!=null?e:this.images.get(n.image);if(!a)throw new Error(`SpriteSheet "${o}" missing image "${n.image}"`);let h=new D(a,n.frameWidth,n.frameHeight,n.frameCount,(r=n.from)!=null?r:0);this.sheets.set(o,h);}}async loadImage(t){let s=await ODOM.Image.new();if(!s)throw new Error(`Unable to create image for "${t}"`);let e=null,r=null,o=false,n=c(async()=>{let h=[e,r].filter(Boolean);e=null,r=null,await Promise.all(h.map(d=>s.dettachEventListener(d).catch(()=>{})));},"detach"),a=c(async()=>{let[h,d,p,u]=await Promise.all([s.naturalWidth(),s.naturalHeight(),s.width(),s.height()]);s.__local_size__={width:h||p||0,height:d||u||0};},"cacheSize");return new Promise(async(h,d)=>{let p=c(async()=>{if(!o)try{await a(),o=!0,await n(),h(s);}catch(f){await u(f);}},"done"),u=c(async f=>{o||(o=true,await n(),await s.release().catch(()=>{}),d(new Error(`Unable to load image "${t}"`)));},"fail");try{e=await s.addEventListener("load",()=>{p();},{options:{once:!0}}),r=await s.addEventListener("error",()=>{u();},{options:{once:!0}}),await s.setSrc(t);let[f,m]=await Promise.all([s.getProperty("complete").catch(()=>!1),s.naturalWidth().catch(()=>0)]);f&&m>0&&await p();}catch(f){await u(f);}})}async loadSound(t){return await OUID$1.media.audio.decode(t),t}getImage(t){return this.images.get(t)}getSpriteSheet(t){return this.sheets.get(t)}createAnimation(t,s,e){let r=this.getSpriteSheet(t);if(!r)throw new Error(`SpriteSheet "${t}" not found`);return new X(r,s!=null?s:10,e!=null?e:true)}getSound(t){return this.sounds.get(t)}clean(){this.images.forEach(t=>t.release()),this.images.clear(),this.sounds.clear(),this.sheets.clear();}};c(L,"AssetManager");var Y=L;var O=class O{constructor(t){i(this,"assets");this.assets=t;}clean(){OUID$1.media.audio.close();}async play(t,s){let e=this.assets.getSound(t);e&&await OUID$1.media.audio.play(e,s);}};c(O,"AudioManager");var T=O;var U=class U{constructor(t){i(this,"keysDown",new Set);i(this,"keysPressed",new Set);i(this,"buttonsPressed",new Set);i(this,"buttonsDown",new Set);i(this,"mouseX",-1);i(this,"mouseY",-1);i(this,"offsetX",-1);i(this,"offsetY",-1);i(this,"ids",[]);i(this,"disposed",false);this.track(OUID.addEventListener("window","keydown",s=>{this.keysDown.has(s.code)||this.keysPressed.add(s.code),this.keysDown.add(s.code);})),this.track(OUID.addEventListener("window","keyup",s=>{this.keysDown.delete(s.code);})),this.track(t.addEventListener("mousedown",s=>{let e=s.button===0?"MouseLeft":"MouseRight";this.buttonsDown.has(e)||this.buttonsPressed.add(e),this.buttonsDown.add(e),this.updateMousePosition(s);})),this.track(t.addEventListener("mouseup",s=>{let e=s.button===0?"MouseLeft":"MouseRight";this.buttonsDown.delete(e),this.updateMousePosition(s);})),this.track(t.addEventListener("mousemove",s=>{this.updateMousePosition(s);})),this.track(t.addEventListener("mouseleave",()=>{this.buttonsDown.clear();}));}update(){this.keysPressed.clear(),this.buttonsPressed.clear();}isDown(t){return this.keysDown.has(t)||this.buttonsDown.has(t)}isPressed(t){return this.keysPressed.has(t)||this.buttonsPressed.has(t)}clean(){this.disposed=true,this.ids.forEach(t=>{OUID.dettachEventListener(t);}),this.ids=[],this.keysDown.clear(),this.keysPressed.clear(),this.buttonsDown.clear(),this.buttonsPressed.clear();}track(t){t.then(s=>{this.disposed?OUID.dettachEventListener(s):this.ids.push(s);}).catch(()=>{});}updateMousePosition(t){var s,e,r,o;this.mouseX=(s=t.x)!=null?s:this.mouseX,this.mouseY=(e=t.y)!=null?e:this.mouseY,this.offsetX=(r=t.offsetX)!=null?r:this.offsetX,this.offsetY=(o=t.offsetY)!=null?o:this.offsetY;}};c(U,"InputManager");var z=U;var B=class B{constructor(t){i(this,"config");i(this,"canvas");i(this,"ctx2d");i(this,"assets");i(this,"audio");i(this,"input");i(this,"world",{});i(this,"running",false);i(this,"initialized",false);i(this,"lastTime",0);i(this,"frameId",null);i(this,"resizeListenerId",null);this.config=t,this.canvas=t.canvas,this.assets=new Y,this.audio=new T(this.assets),this.input=new z(this.canvas);}buildContext(t){return {dt:t,canvas:this.canvas,ctx2d:this.ctx2d,world:this.world,assets:this.assets,audio:this.audio,input:this.input}}async updateLocalSize(){var e,r;let t=(e=await this.canvas.width())!=null?e:0,s=(r=await this.canvas.height())!=null?r:0;this.canvas.__local_size__=this.ctx2d.__local_size__={width:t,height:s};}async syncSize(){var a,h;let[t,s,e,r]=await Promise.all([this.canvas.getProperty("clientWidth"),this.canvas.getProperty("clientHeight"),this.canvas.width(),this.canvas.height()]),o=(a=t!=null?t:e)!=null?a:0,n=(h=s!=null?s:r)!=null?h:0;await this.canvas.setProperty("width",o),await this.canvas.setProperty("height",n);}async init(){if(this.initialized)return;let t=await this.canvas.getContext("2d");if(!t)throw new Error("Unable to create a context");this.ctx2d=t,this.resizeListenerId=await this.canvas.addEventListener("resize",()=>{this.syncSize().then(()=>this.updateLocalSize());}),this.config.preload&&(this.config.preload(this.assets),await this.assets.loadAll()),await this.syncSize(),await this.updateLocalSize(),this.config.setup&&this.config.setup(this.buildContext(0)),this.initialized=true;}start(){this.running||(this.running=true,this.lastTime=performance.now(),this.scheduleFrame());}stop(){var t;!this.running&&!this.initialized||(this.running=false,this.frameId!=null&&(OUID.cancelAnimationFrame(this.frameId),this.frameId=null),this.resizeListenerId&&(OUID.dettachEventListener(this.resizeListenerId),this.resizeListenerId=null),this.audio.clean(),this.assets.clean(),this.input.clean(),(t=this.ctx2d)==null||t.release(),this.initialized=false);}scheduleFrame(){OUID.requestAnimationFrame(t=>{this.frameId=null,this.tick(t);}).then(t=>{this.running?this.frameId=t:OUID.cancelAnimationFrame(t);});}async tick(t){var r,o,n,a;if(!this.running)return;let e=Math.min((t-this.lastTime)/1e3,.1);this.lastTime=t;try{let h=this.buildContext(e);(o=(r=this.config).update)==null||o.call(r,h),this.input.update(),await((a=(n=this.config).draw)==null?void 0:a.call(n,h)),await this.ctx2d.commit();}catch(h){console.error("[Toubani] game loop error",h);}finally{this.running&&this.scheduleFrame();}}};c(B,"Game");var A=B;function Kt(l){return new A(l)}c(Kt,"tulon");function H(l){var t;return (t=l.__local_size__)!=null?t:{width:0,height:0}}c(H,"objectSize");function y(l,t){return Math.floor(l/t.tileWidth)}c(y,"worldToTileX");function v(l,t){return Math.floor(l/t.tileHeight)}c(v,"worldToTileY");function Tt(l,t){let{image:s,tileWidth:e,tileHeight:r,margin:o=0,spacing:n=0}=t,a=H(s),h=Math.max(1,Math.floor((a.width-2*o+n)/(e+n))),d=l-1,p=d%h,u=Math.floor(d/h),f=o+p*(e+n),m=o+u*(r+n);return {sx:f,sy:m,sw:e,sh:r}}c(Tt,"getTileSourceRect");var $=class ${constructor(t){i(this,"tileset");i(this,"sourceRectCache",new Map);this.tileset=t;}draw(t,s,e,r,o){let{tileWidth:n,tileHeight:a}=s,h=r*e.focusX,d=o*e.focusY,p=e.zoom,u=e.x-h/p,f=e.y-d/p,m=e.x+(r-h)/p,w=e.y+(o-d)/p,g=Math.max(0,Math.floor(u/n)),x=Math.min(s.width-1,Math.floor(m/n)),I=Math.max(0,Math.floor(f/a)),mt=Math.min(s.height-1,Math.floor(w/a));for(let P=I;P<=mt;P++)for(let b=g;b<=x;b++){let Z=s.getTile(b,P);if(Z<=0)continue;let{sx:xt,sy:yt,sw:vt,sh:Mt}=this.getSourceRect(Z),kt=b*n,It=P*a;t.drawImage(this.tileset.image,xt,yt,vt,Mt,kt,It,n,a);}}getSourceRect(t){let s=this.sourceRectCache.get(t);return s||(s=Tt(t,this.tileset),this.sourceRectCache.set(t,s)),s}};c($,"TileRenderer");var at=$;var j=class j{constructor(t,s){i(this,"node");i(this,"settings");i(this,"vx",0);i(this,"vy",0);i(this,"onGround",false);this.node=t,this.settings=s;}update(t){var f,m,w,g,x;let s=this.settings,e=(f=s.gravityX)!=null?f:0,r=(m=s.gravityY)!=null?m:1e3,o=(w=s.maxFallSpeed)!=null?w:2e3,n=(g=s.friction)!=null?g:.8,a=(x=s.airFriction)!=null?x:.98;this.vx+=e*t,this.vy+=r*t,this.vy>o&&(this.vy=o);let h=Math.max(1,Math.min(s.tileMap.tileWidth,s.tileMap.tileHeight)*.5),d=Math.max(Math.abs(this.vx*t),Math.abs(this.vy*t)),p=Math.max(1,Math.ceil(d/h)),u=t/p;this.onGround=false;for(let I=0;I<p;I++)this.moveAndCollideX(u),this.moveAndCollideY(u);this.onGround?this.vx*=n:this.vx*=a;}moveAndCollideX(t){let s=this.settings.tileMap,e=this.settings.width,r=this.settings.height,o=this.node.x+this.vx*t,n=this.node.y,a=Math.sign(this.vx);if(a===0){this.node.x=o;return}let h=o,d=o+e,p=n,u=n+r,f=Math.max(0,v(p,s)),m=Math.min(s.height-1,v(u-1,s));if(a>0){let w=y(d,s);for(let g=f;g<=m;g++)if(this.isSolid(w,g)){d=w*s.tileWidth,h=d-e,this.vx=0;break}}else {let w=y(h,s);for(let g=f;g<=m;g++)if(this.isSolid(w,g)){h=(w+1)*s.tileWidth,d=h+e,this.vx=0;break}}this.node.x=h;}moveAndCollideY(t){let s=this.settings.tileMap,e=this.settings.width,r=this.settings.height,o=this.node.x,n=this.node.y+this.vy*t,a=Math.sign(this.vy);if(a===0){this.node.y=n;return}let h=n,d=n+r,p=o,u=o+e,f=Math.max(0,y(p,s)),m=Math.min(s.width-1,y(u-1,s));if(a>0){let w=v(d,s);for(let g=f;g<=m;g++)if(this.isSolid(g,w)){d=w*s.tileHeight,h=d-r,this.vy=0,this.onGround=true;break}}else {let w=v(h,s);for(let g=f;g<=m;g++)if(this.isSolid(g,w)){h=(w+1)*s.tileHeight,d=h+r,this.vy=0;break}}this.node.y=h;}isSolid(t,s){let e=this.settings.tileMap;return t<0||s<0||t>=e.width||s>=e.height?false:e.isSolid(t,s)}};c(j,"PhysicsBody");var ht=j;var F=class F{constructor(){i(this,"x",0);i(this,"y",0);i(this,"zoom",1);i(this,"rotation",0);i(this,"followTarget",null);i(this,"followLerp",.1);i(this,"focusX",.5);i(this,"focusY",.5);}follow(t,s=.1){this.followTarget=t,this.followLerp=s;}unfollow(){this.followTarget=null;}update(t){if(!this.followTarget)return;let s=this.followTarget.x,e=this.followTarget.y;this.x+=(s-this.x)*this.followLerp,this.y+=(e-this.y)*this.followLerp;}begin(t,s,e){t.save();let r=s*this.focusX,o=e*this.focusY;t.translate(r,o),this.rotation!==0&&t.rotate(-this.rotation),this.zoom!==1&&t.scale(this.zoom,this.zoom),t.translate(-this.x,-this.y);}end(t){t.restore();}};c(F,"Camera2D");var ct=F;var J=class J{constructor(){i(this,"x",0);i(this,"y",0);i(this,"rotation",0);i(this,"scaleX",1);i(this,"scaleY",1);i(this,"visible",true);i(this,"parent",null);i(this,"children",[]);}add(t){return t.parent=this,this.children.push(t),t}remove(t){let s=this.children.indexOf(t);s>=0&&(this.children.splice(s,1),t.parent=null);}clear(){for(let t of this.children)t.parent=null;this.children.length=0;}update(t){}drawSelf(t){}draw(t){if(this.visible){t.save(),t.translate(this.x,this.y),this.rotation!==0&&t.rotate(this.rotation),(this.scaleX!==1||this.scaleY!==1)&&t.scale(this.scaleX,this.scaleY),this.drawSelf(t);for(let s of this.children)s.draw(t);t.restore();}}updateTree(t){this.update(t);for(let s of this.children)s.updateTree(t);}getWorldPosition(){let t=this.x,s=this.y,e=this.parent;for(;e;)t+=e.x,s+=e.y,e=e.parent;return {x:t,y:s}}};c(J,"SceneNode");var k=J,G=class G extends k{constructor(s){super();i(this,"sprite");this.sprite=s;}update(s){this.sprite.update(s);}drawSelf(s){this.sprite.draw(s);}getWorldBounds(){let s=this.getWorldPosition();return {x:s.x,y:s.y,width:this.sprite.width*this.scaleX,height:this.sprite.height*this.scaleY}}};c(G,"SpriteNode");var lt=G,N=class N extends k{constructor(s=1,e=1){super();i(this,"parallaxX",1);i(this,"parallaxY",1);this.parallaxX=s,this.parallaxY=e;}};c(N,"SceneLayer");var _=N,M=class M{constructor(){i(this,"id");i(this,"root",new k);i(this,"layers",[]);this.id=M.id++;}addLayer(t=1,s=1){let e=new _(t,s);return this.layers.push(e),this.root.add(e),e}update(t){this.root.updateTree(t);}draw(t){this.root.draw(t);}};c(M,"Scene"),i(M,"id",0);var dt=M;function ut(l,t){return !(l.x+l.width<=t.x||l.x>=t.x+t.width||l.y+l.height<=t.y||l.y>=t.y+t.height)}c(ut,"aabbOverlap");var V=class V{constructor(){i(this,"colliders",[]);i(this,"rules",[]);i(this,"rulesByPair",new Map);}add(t){this.colliders.push(t);}remove(t){let s=this.colliders.indexOf(t);s>=0&&this.colliders.splice(s,1);}clear(){this.colliders.length=0;}onCollision(t,s,e){var a;let r={tagA:t,tagB:s,handler:e};this.rules.push(r);let o=this.ruleKey(t,s),n=(a=this.rulesByPair.get(o))!=null?a:[];n.push(r),this.rulesByPair.set(o,n);}update(){var r;let t=new Map,s=new Map;for(let o of this.colliders){if(!o.isActive)continue;let n=(r=t.get(o.tag))!=null?r:[];n.push(o),t.set(o.tag,n);}let e=c(o=>{let n=s.get(o);return n||(n=o.getBounds(),s.set(o,n)),n},"getBounds");for(let[o,n]of this.rulesByPair.entries()){let[a,h]=o.split("\0"),d=t.get(a),p=t.get(h);if(!(!d||!p))if(a===h)for(let u=0;u<d.length;u++){let f=d[u],m=e(f);for(let w=u+1;w<d.length;w++){let g=d[w];ut(m,e(g))&&this.triggerRules(f,g,n);}}else for(let u of d){let f=e(u);for(let m of p)ut(f,e(m))&&this.triggerRules(u,m,n);}}}ruleKey(t,s){return t<=s?`${t}\0${s}`:`${s}\0${t}`}triggerRules(t,s,e){for(let r of e){let o=r.tagA===t.tag&&r.tagB===s.tag,n=r.tagA===s.tag&&r.tagB===t.tag;o?r.handler(t,s):n&&r.handler(s,t);}}};c(V,"CollisionSystem");var ft=V,zt=0;function ls(l,t,s,e){return {id:`col-${zt++}`,tag:l,isActive:true,data:{node:t,body:s,sprite:e},getBounds(){let o=t.getWorldPosition(),n=s.settings.width,a=s.settings.height;return {x:o.x,y:o.y,width:n,height:a}}}}c(ls,"createSpritePhysicsCollider");var q=class q{constructor(t={}){i(this,"options");i(this,"opened",false);i(this,"socket",null);i(this,"currentUrl",null);i(this,"offMessage",null);i(this,"msgHandlers",[]);i(this,"openHandlers",[]);i(this,"closeHandlers",[]);i(this,"errorHandlers",[]);this.options=t;}async connect(t){if(this.socket){if(this.currentUrl===t&&this.socket.isConnected())return;if(this.currentUrl===t&&!this.opened){await this.socket.connect();return}await this.cleanupSocket(true);}let{protocols:s,connectTimeoutMs:e}=this.options,r=OUIDNetwork.websocket(t,s,{connectTimeoutMs:e});this.socket=r,this.currentUrl=t,r.onOpen(()=>{this.opened=true,this.openHandlers.forEach(o=>o());}),this.offMessage=await r.onMessage(o=>{var a;let n=typeof o=="string"?o:(a=JSON.stringify(o))!=null?a:"";this.msgHandlers.forEach(h=>h(n));}),r.onClose(o=>{this.opened=false,this.closeHandlers.forEach(n=>n(o));}),r.onError(o=>{this.errorHandlers.forEach(n=>n(o));});try{await r.connect();}catch(o){throw this.socket===r&&await this.cleanupSocket(false),o}}async send(t){var s;await((s=this.socket)==null?void 0:s.send(t));}async close(t,s){await this.cleanupSocket(true,t,s);}isConnected(){return this.opened}onMessage(t){this.msgHandlers.push(t);}onOpen(t){this.openHandlers.push(t);}offOpen(t){this.openHandlers=this.openHandlers.filter(s=>s!==t);}onClose(t){this.closeHandlers.push(t);}offClose(t){this.closeHandlers=this.closeHandlers.filter(s=>s!==t);}onError(t){this.errorHandlers.push(t);}offError(t){this.errorHandlers=this.errorHandlers.filter(s=>s!==t);}async cleanupSocket(t,s,e){let r=this.socket;this.socket=null,this.currentUrl=null,this.opened=false;let o=this.offMessage;this.offMessage=null,await(o==null?void 0:o().catch(()=>{})),t&&await(r==null?void 0:r.close(s,e));}};c(q,"WebSocketTransport");var pt=q,K=class K{constructor(t){i(this,"transport");i(this,"handlers",new Map);i(this,"rpcResolvers",new Map);i(this,"rpcCounter",0);i(this,"handleRawMessage",c(t=>{if(!t)return;let s;try{s=JSON.parse(t);}catch(o){return}let e=this.extractRpcId(s);if(e){let o=this.rpcResolvers.get(e);o&&(o.resolve(this.extractRpcValue(s)),this.rpcResolvers.delete(e));}let r=this.handlers.get(s.type);if(r)for(let o of r)o(s);},"handleRawMessage"));this.transport=t,this.transport.onMessage(this.handleRawMessage),this.transport.onClose(()=>this.rejectPendingRpc(new Error("Network connection closed"))),this.transport.onError(s=>{this.rejectPendingRpc(s instanceof Error?s:new Error("Network connection error"));});}async connect(t){await this.transport.connect(t);}isConnected(){return this.transport.isConnected()}send(t,s){let e={type:t,payload:s};return this.transport.send(JSON.stringify(e))}on(t,s){var r;let e=(r=this.handlers.get(t))!=null?r:[];e.push(s),this.handlers.set(t,e);}off(t,s){let e=this.handlers.get(t);e&&this.handlers.set(t,e.filter(r=>r!==s));}call(t,s){let e=`rpc_${++this.rpcCounter}_${Date.now()}`,r=s&&typeof s=="object"?it(et({},s),{rpcId:e}):{value:s,rpcId:e},o={type:t,payload:r};return new Promise((n,a)=>{this.rpcResolvers.set(e,{resolve:n,reject:a}),this.transport.send(JSON.stringify(o)).catch(h=>{this.rpcResolvers.delete(e),a(h);});})}close(){return this.rejectPendingRpc(new Error("Network client closed")),this.transport.close()}extractRpcId(t){if(typeof t.rpcId=="string")return t.rpcId;if(t.payload&&typeof t.payload=="object"&&"rpcId"in t.payload){let s=t.payload.rpcId;return typeof s=="string"?s:void 0}}extractRpcValue(t){return "result"in t?t.result:t.payload}rejectPendingRpc(t){this.rpcResolvers.forEach(({reject:s})=>s(t)),this.rpcResolvers.clear();}};c(K,"NetworkClient");var gt=K,Q=class Q{constructor(t,s,e){i(this,"net");i(this,"channels");i(this,"state");i(this,"onChangeHandlers",[]);var r;this.net=t,this.channels=e,this.state=s,this.net.on((r=e==null?void 0:e.update)!=null?r:"state_update",o=>{let{payload:n}=o;n.full?this.state=n.full:n.patch&&(this.state=Object.assign({},this.state,n.patch)),this.emitChange();});}getState(){return this.state}onChange(t){this.onChangeHandlers.push(t);}emitChange(){for(let t of this.onChangeHandlers)t(this.state);}sendPatch(t){var s,e;return this.net.send((e=(s=this.channels)==null?void 0:s.patch)!=null?e:"state_patch",t)}};c(Q,"StateSyncClient");var wt=Q;export{Y as AssetManager,T as AudioManager,ct as Camera2D,ft as CollisionSystem,A as Game,z as InputManager,gt as NetworkClient,ht as PhysicsBody,dt as Scene,_ as SceneLayer,k as SceneNode,ot as Sprite,X as SpriteAnimation,rt as SpriteLayer,lt as SpriteNode,D as SpriteSheet,wt as StateSyncClient,at as TileRenderer,pt as WebSocketTransport,ut as aabbOverlap,ls as createSpritePhysicsCollider,Tt as getTileSourceRect,H as objectSize,Kt as tulon,y as worldToTileX,v as worldToTileY};
|
package/docs/ai/index.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# AI Guide for @ouidesigner/toubani
|
|
2
|
+
|
|
3
|
+
Use this file when an AI coding agent adds Toubani game or interactive-scene behavior to an Ouider mini app.
|
|
4
|
+
|
|
5
|
+
## Package Role
|
|
6
|
+
|
|
7
|
+
`@ouidesigner/toubani` provides game-oriented helpers for Ouider projects, including:
|
|
8
|
+
|
|
9
|
+
- game loop and scene structure;
|
|
10
|
+
- input handling;
|
|
11
|
+
- sprites, tiles, camera, and collision helpers;
|
|
12
|
+
- audio and asset helpers;
|
|
13
|
+
- optional network helpers through `@ouidesigner/ouider-network`.
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- Keep the Ouider mini-app bundle contract intact: main app code stays separate from WebView preload code.
|
|
18
|
+
- Do not use direct DOM APIs in main app code unless the project explicitly runs only in unified WebView mode.
|
|
19
|
+
- If network helpers are used, read `node_modules/@ouidesigner/ouider-network/docs/ai/index.md`.
|
|
20
|
+
- If audio, media, or other native host capabilities are used, update `manifest.json` with the matching permission.
|
|
21
|
+
- Keep game loops disposable. Stop timers, animation loops, listeners, and network transports when the route or scene is destroyed.
|
|
22
|
+
|
|
23
|
+
## Verification
|
|
24
|
+
|
|
25
|
+
Run the project typecheck/build after changes and test on the intended host if the feature depends on input, audio, network, or frame timing.
|
|
26
|
+
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouidesigner/toubani",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"clean": "rm -rf dist",
|
|
8
|
-
"build": "npm run clean
|
|
8
|
+
"build": "npm run clean && tsup",
|
|
9
9
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [],
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"type": "commonjs",
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"@ouidesigner/ouider": "^0.3.
|
|
16
|
+
"@ouidesigner/ouider": "^0.3.2",
|
|
17
|
+
"@ouidesigner/ouider-network": "^0.1.3",
|
|
18
|
+
"@ouidesigner/ouider-device": "^0.0.7"
|
|
17
19
|
},
|
|
18
20
|
"devDependencies": {
|
|
19
21
|
"@types/node": "^24.10.1",
|
|
@@ -27,7 +29,8 @@
|
|
|
27
29
|
},
|
|
28
30
|
"types": "dist/index.d.ts",
|
|
29
31
|
"files": [
|
|
30
|
-
"/dist"
|
|
32
|
+
"/dist",
|
|
33
|
+
"/docs/ai"
|
|
31
34
|
],
|
|
32
35
|
"publishConfig": {
|
|
33
36
|
"access": "public"
|