diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..30cf57e
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml
new file mode 100644
index 0000000..48f4f77
--- /dev/null
+++ b/.idea/dictionaries/project.xml
@@ -0,0 +1,7 @@
+
+
+
+ keys
+
+
+
\ No newline at end of file
diff --git a/.idea/examples.iml b/.idea/examples.iml
new file mode 100644
index 0000000..c956989
--- /dev/null
+++ b/.idea/examples.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..03d9549
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..704ff0e
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/prettier.xml b/.idea/prettier.xml
new file mode 100644
index 0000000..b0c1c68
--- /dev/null
+++ b/.idea/prettier.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/raid-survival/.dockerignore b/raid-survival/.dockerignore
new file mode 100644
index 0000000..c2658d7
--- /dev/null
+++ b/raid-survival/.dockerignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/raid-survival/.gitignore b/raid-survival/.gitignore
new file mode 100644
index 0000000..ba6da87
--- /dev/null
+++ b/raid-survival/.gitignore
@@ -0,0 +1,134 @@
+### VisualStudioCode template
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### JetBrains template
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# File-based project format
+*.iws
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+### Node template
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+### NanoForge template
+# Compilation directories
+.nanoforge/client
+.nanoforge/server
diff --git a/raid-survival/.idea/.gitignore b/raid-survival/.idea/.gitignore
new file mode 100644
index 0000000..30cf57e
--- /dev/null
+++ b/raid-survival/.idea/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/raid-survival/.idea/inspectionProfiles/Project_Default.xml b/raid-survival/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..03d9549
--- /dev/null
+++ b/raid-survival/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/raid-survival/.idea/modules.xml b/raid-survival/.idea/modules.xml
new file mode 100644
index 0000000..40cdb67
--- /dev/null
+++ b/raid-survival/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/raid-survival/.idea/prettier.xml b/raid-survival/.idea/prettier.xml
new file mode 100644
index 0000000..b0c1c68
--- /dev/null
+++ b/raid-survival/.idea/prettier.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/raid-survival/.idea/raid-survival.iml b/raid-survival/.idea/raid-survival.iml
new file mode 100644
index 0000000..2155317
--- /dev/null
+++ b/raid-survival/.idea/raid-survival.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/raid-survival/.idea/vcs.xml b/raid-survival/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/raid-survival/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/raid-survival/.nanoforge/client.save.json b/raid-survival/.nanoforge/client.save.json
new file mode 100644
index 0000000..a1b3e67
--- /dev/null
+++ b/raid-survival/.nanoforge/client.save.json
@@ -0,0 +1,77 @@
+{
+ "libraries": [
+ {
+ "id": "assetManagerLibrary",
+ "type": "asset-manager",
+ "name": "AssetManagerLibrary",
+ "path": "@nanoforge-dev/asset-manager",
+ "params": []
+ },
+ {
+ "id": "ecsLibrary",
+ "type": "component-system",
+ "name": "ECSClientLibrary",
+ "path": "@nanoforge-dev/ecs-client",
+ "params": []
+ },
+ {
+ "id": "graphicsLibrary",
+ "type": "graphics",
+ "name": "Graphics2DLibrary",
+ "path": "@nanoforge-dev/graphics-2d",
+ "params": []
+ },
+ {
+ "id": "inputLibrary",
+ "type": "input",
+ "name": "InputLibrary",
+ "path": "@nanoforge-dev/input",
+ "params": []
+ },
+ {
+ "id": "musicLibrary",
+ "type": "music",
+ "name": "MusicLibrary",
+ "path": "@nanoforge-dev/music",
+ "params": []
+ },
+ {
+ "id": "networkLibrary",
+ "type": "network",
+ "name": "NetworkClientLibrary",
+ "path": "@nanoforge-dev/network-client",
+ "params": []
+ },
+ {
+ "id": "soundLibrary",
+ "type": "sound",
+ "name": "SoundLibrary",
+ "path": "@nanoforge-dev/sound",
+ "params": []
+ }
+ ],
+ "components": [
+ {
+ "name": "ExampleComponent",
+ "path": "./components/example.component.ts",
+ "paramsNames": ["paramA", "paramB", "paramC"]
+ }
+ ],
+ "systems": [
+ {
+ "name": "exampleSystem",
+ "path": "./systems/example.system.ts"
+ }
+ ],
+ "entities": [
+ {
+ "id": "exampleEntity",
+ "components": {
+ "ExampleComponent": {
+ "paramA": "example",
+ "paramB": 10
+ }
+ }
+ }
+ ]
+}
diff --git a/raid-survival/.nanoforge/server.save.json b/raid-survival/.nanoforge/server.save.json
new file mode 100644
index 0000000..56f214b
--- /dev/null
+++ b/raid-survival/.nanoforge/server.save.json
@@ -0,0 +1,49 @@
+{
+ "libraries": [
+ {
+ "id": "assetManagerLibrary",
+ "type": "asset-manager",
+ "name": "AssetManagerLibrary",
+ "path": "@nanoforge-dev/asset-manager",
+ "params": []
+ },
+ {
+ "id": "ecsLibrary",
+ "type": "component-system",
+ "name": "ECSServerLibrary",
+ "path": "@nanoforge-dev/ecs-server",
+ "params": []
+ },
+ {
+ "id": "networkLibrary",
+ "type": "network",
+ "name": "NetworkServerLibrary",
+ "path": "@nanoforge-dev/network-server",
+ "params": []
+ }
+ ],
+ "components": [
+ {
+ "name": "ExampleComponent",
+ "path": "./components/example.component.ts",
+ "paramsNames": ["paramA", "paramB", "paramC"]
+ }
+ ],
+ "systems": [
+ {
+ "name": "exampleSystem",
+ "path": "./systems/example.system.ts"
+ }
+ ],
+ "entities": [
+ {
+ "id": "exampleEntity",
+ "components": {
+ "ExampleComponent": {
+ "paramA": "example",
+ "paramB": 10
+ }
+ }
+ }
+ ]
+}
diff --git a/raid-survival/.prettierignore b/raid-survival/.prettierignore
new file mode 100644
index 0000000..2f0ede7
--- /dev/null
+++ b/raid-survival/.prettierignore
@@ -0,0 +1,8 @@
+# Ignore files for PNPM, NPM and YARN
+pnpm-lock.yaml
+package-lock.json
+yarn.lock
+bun.lock
+
+/node_modules
+/.nanoforge
diff --git a/raid-survival/Dockerfile b/raid-survival/Dockerfile
new file mode 100644
index 0000000..4c4cd10
--- /dev/null
+++ b/raid-survival/Dockerfile
@@ -0,0 +1,18 @@
+FROM node:25-slim
+
+ENV PNPM_HOME="/pnpm"
+ENV PATH="$PNPM_HOME:$PATH"
+RUN npm install -g pnpm
+
+WORKDIR /app
+
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+RUN pnpm install --frozen-lockfile --allow-build=bun
+
+COPY . .
+
+RUN pnpm run build
+
+CMD ["pnpm", "run", "start"]
diff --git a/raid-survival/README.md b/raid-survival/README.md
new file mode 100644
index 0000000..b1a3d4e
--- /dev/null
+++ b/raid-survival/README.md
@@ -0,0 +1,158 @@
+# raid-survival
+
+---
+
+## Prerequisites
+
+- [Node.js](https://nodejs.org/) 18+
+- Package manager: **pnpm**
+- [NanoForge CLI](https://www.npmjs.com/package/@nanoforge-dev/cli): `pnpm install -g @nanoforge-dev/cli`
+
+---
+
+## Installation
+
+```bash
+pnpm install
+```
+
+---
+
+## Development
+
+Start the game in watch mode with hot reload:
+
+```bash
+nf dev
+```
+
+---
+
+## Project structure
+
+```
+raid-survival/
+├── .nanoforge/ # NanoForge internal save files (do not edit manually)
+├── client/ # Client-side code (runs in the browser)
+│ ├── static/ # Static assets
+│ ├── components/ # ECS components
+│ ├── systems/ # ECS systems
+├── server/ # Server-side code
+│ ├── static/
+│ ├── components/
+│ ├── systems/
+├── package.json
+├── tsconfig.json
+└── nanoforge.config.json # Game configuration
+```
+
+---
+
+## Modifying the game
+
+### Add a component
+
+```bash
+nf generate component --part client
+```
+
+A component is a plain class that holds data for an entity. Edit the generated file in `client/components/`:
+
+```ts
+export class MyComponent {
+ constructor(public speed: number) {}
+}
+
+export default MyComponent.name;
+```
+
+### Add a system
+
+```bash
+nf generate system --part client
+```
+
+A system is a function that runs every tick and operates on entities that have specific components. Edit the generated file in `client/systems/`:
+
+```ts
+import { type Context } from "@nanoforge-dev/common";
+import { type Registry } from "@nanoforge-dev/ecs-client";
+import { MyComponent } from "../components/my.component";
+
+export const mySystem = (registry: Registry, ctx: Context) => {
+ const entities = registry.getZipper([MyComponent]);
+
+ entities.forEach((entity) => {
+ entity.MyComponent.speed += 1;
+ });
+};
+
+export default mySystem.name;
+```
+
+
+### Server-side code
+
+Server components and systems live in `server/` and are imported from `@nanoforge-dev/ecs-server`. They follow the same pattern as the client side.
+
+Configure the server connection in `.env`:
+
+| Variable | Default | Description |
+|---------------------------------------|-------------|---------------------------------|
+| `NANOFORGE_CLIENT_SERVER_ADDRESS` | `127.0.0.1` | Server address (client-side) |
+| `NANOFORGE_CLIENT_SERVER_TCP_PORT` | `4444` | TCP port the client connects to |
+| `NANOFORGE_CLIENT_SERVER_UDP_PORT` | `4445` | UDP port the client connects to |
+| `NANOFORGE_SERVER_LISTENING_TCP_PORT` | `4444` | TCP port the server listens on |
+| `NANOFORGE_SERVER_LISTENING_UDP_PORT` | `4445` | UDP port the server listens on |
+
+---
+
+## Build
+
+Compile the game for production:
+
+```bash
+pnpm run build
+# or
+nf build
+```
+
+Output is written to the `.nanoforge/` directory.
+
+---
+
+## Run
+
+Start the game server and open it in a browser:
+
+```bash
+pnpm run start
+# or
+nf start
+```
+
+---
+
+## Code quality
+
+```bash
+# Check formatting and linting
+pnpm run lint
+
+# Auto-fix formatting and linting
+pnpm run format
+```
+
+---
+
+## CLI reference
+
+| Command | Description |
+|--------------------------------|-------------------------------------------|
+| `nf dev` | Start in development mode with hot reload |
+| `nf build` | Build for production |
+| `nf start` | Run the built game |
+| `nf generate component ` | Scaffold a new component |
+| `nf generate system ` | Scaffold a new system |
+
+Full CLI documentation:
diff --git a/raid-survival/client/components/children.component.ts b/raid-survival/client/components/children.component.ts
new file mode 100644
index 0000000..42c7a05
--- /dev/null
+++ b/raid-survival/client/components/children.component.ts
@@ -0,0 +1,16 @@
+import {Vector2d} from "@nanoforge-dev/graphics-2d";
+
+export interface ChildrenOptions {
+ LocalTransform?: Vector2d;
+}
+
+export class ChildrenComponent {
+ name = this.constructor.name;
+ parentId: number;
+ options: ChildrenOptions;
+
+ constructor(parentId: number, options: ChildrenOptions) {
+ this.parentId = parentId;
+ this.options = options;
+ }
+}
diff --git a/raid-survival/client/components/direction-rotator.component.ts b/raid-survival/client/components/direction-rotator.component.ts
new file mode 100644
index 0000000..2327f48
--- /dev/null
+++ b/raid-survival/client/components/direction-rotator.component.ts
@@ -0,0 +1,10 @@
+export class DirectionRotatorComponent {
+ name = this.constructor.name;
+ enable: boolean = true;
+ offset: number;
+
+ constructor(offset: number = 0, enable: boolean = true) {
+ this.offset = offset;
+ this.enable = enable;
+ }
+}
diff --git a/raid-survival/client/components/direction.component.ts b/raid-survival/client/components/direction.component.ts
new file mode 100644
index 0000000..2c14047
--- /dev/null
+++ b/raid-survival/client/components/direction.component.ts
@@ -0,0 +1,11 @@
+export class Direction {
+ name = this.constructor.name;
+
+ constructor(
+ public x: number,
+ public y: number,
+ ) {}
+}
+
+// * Required to generate code
+export default Direction.name;
diff --git a/raid-survival/client/components/essentials/transform.component.ts b/raid-survival/client/components/essentials/transform.component.ts
new file mode 100644
index 0000000..6fef173
--- /dev/null
+++ b/raid-survival/client/components/essentials/transform.component.ts
@@ -0,0 +1,12 @@
+export class TransformComponent {
+ name = this.constructor.name;
+
+ constructor(
+ public x: number = 0,
+ public y: number = 0,
+ public rotation: number = 0,
+ ) {}
+}
+
+// * Required to generate code
+export default TransformComponent.name;
diff --git a/raid-survival/client/components/essentials/velocity.component.ts b/raid-survival/client/components/essentials/velocity.component.ts
new file mode 100644
index 0000000..d9afbfb
--- /dev/null
+++ b/raid-survival/client/components/essentials/velocity.component.ts
@@ -0,0 +1,11 @@
+export class Velocity {
+ name = this.constructor.name;
+
+ constructor(
+ public x: number,
+ public y: number,
+ ) {}
+}
+
+// * Required to generate code
+export default Velocity.name;
diff --git a/raid-survival/client/components/essentials/z-index.component.ts b/raid-survival/client/components/essentials/z-index.component.ts
new file mode 100644
index 0000000..0f673a4
--- /dev/null
+++ b/raid-survival/client/components/essentials/z-index.component.ts
@@ -0,0 +1,7 @@
+export class ZIndexComponent {
+ name = this.constructor.name;
+
+ constructor(
+ public value: number = 0
+ ) {}
+}
\ No newline at end of file
diff --git a/raid-survival/client/components/health-bar-fill.component.ts b/raid-survival/client/components/health-bar-fill.component.ts
new file mode 100644
index 0000000..624fac1
--- /dev/null
+++ b/raid-survival/client/components/health-bar-fill.component.ts
@@ -0,0 +1,12 @@
+export class HealthBarFill {
+ name = this.constructor.name;
+
+ // The fill's cavity-left-edge X, in its parent's local coordinate space (frameLocalX + the
+ // cavity's own inset). Constant per health bar instance regardless of current health -
+ // needed to recompute LocalPosition.x on a hit without also needing to know the parent's
+ // sprite width (player vs lobby) again.
+ constructor(public cavityLocalX: number) {}
+}
+
+// * Required to generate code
+export default HealthBarFill.name;
diff --git a/raid-survival/client/components/health.component.ts b/raid-survival/client/components/health.component.ts
new file mode 100644
index 0000000..fd2451d
--- /dev/null
+++ b/raid-survival/client/components/health.component.ts
@@ -0,0 +1,11 @@
+export class Health {
+ name = this.constructor.name;
+
+ constructor(
+ public current: number,
+ public max: number,
+ ) {}
+}
+
+// * Required to generate code
+export default Health.name;
diff --git a/raid-survival/client/components/lobby/lobby-status.ts b/raid-survival/client/components/lobby/lobby-status.ts
new file mode 100644
index 0000000..48814a2
--- /dev/null
+++ b/raid-survival/client/components/lobby/lobby-status.ts
@@ -0,0 +1,24 @@
+export interface LobbyPlayer {
+ id: string;
+ username: string;
+}
+
+export enum LobbyState {
+ UNJOINED,
+ LOADING,
+ JOINED,
+}
+
+export enum LobbyAction {
+ EMPTY,
+ JOIN_LOBBY,
+ START_GAME
+}
+
+export class LobbyStatusComponent {
+ name = this.constructor.name;
+ username = "";
+ state: LobbyState = LobbyState.UNJOINED;
+ action: LobbyAction = LobbyAction.EMPTY;
+ players: LobbyPlayer[] = [];
+}
\ No newline at end of file
diff --git a/raid-survival/client/components/lobby/lobby.component.ts b/raid-survival/client/components/lobby/lobby.component.ts
new file mode 100644
index 0000000..87cc323
--- /dev/null
+++ b/raid-survival/client/components/lobby/lobby.component.ts
@@ -0,0 +1,6 @@
+export class Lobby {
+ name = this.constructor.name;
+}
+
+// * Required to generate code
+export default Lobby.name;
diff --git a/raid-survival/client/components/move-controller.component.ts b/raid-survival/client/components/move-controller.component.ts
new file mode 100644
index 0000000..f42aef5
--- /dev/null
+++ b/raid-survival/client/components/move-controller.component.ts
@@ -0,0 +1,23 @@
+import { InputEnum } from "@nanoforge-dev/input";
+
+export class MoveController {
+ name = this.constructor.name;
+ public keyUp: InputEnum;
+ public keyDown: InputEnum;
+ public keyLeft: InputEnum;
+ public keyRight: InputEnum;
+ public movingUp: boolean = false;
+ public movingDown: boolean = false;
+ public movingLeft: boolean = false;
+ public movingRight: boolean = false;
+ public lastMoveKeys: string[] = []
+
+ constructor(clientConfig: {
+ keybinds: { up: InputEnum; left: InputEnum; down: InputEnum; right: InputEnum };
+ } = { keybinds: { up: InputEnum.KeyW, left: InputEnum.KeyA, down: InputEnum.KeyS, right: InputEnum.KeyD } }) {
+ this.keyUp = clientConfig.keybinds.up;
+ this.keyDown = clientConfig.keybinds.down;
+ this.keyLeft = clientConfig.keybinds.left;
+ this.keyRight = clientConfig.keybinds.right;
+ }
+}
diff --git a/raid-survival/client/components/network-id.component.ts b/raid-survival/client/components/network-id.component.ts
new file mode 100644
index 0000000..0c91a82
--- /dev/null
+++ b/raid-survival/client/components/network-id.component.ts
@@ -0,0 +1,10 @@
+export class NetworkId {
+ name = this.constructor.name;
+
+ constructor(
+ public id: number,
+ ) {}
+}
+
+// * Required to generate code
+export default NetworkId.name;
diff --git a/raid-survival/client/components/renderable/group.component.ts b/raid-survival/client/components/renderable/group.component.ts
new file mode 100644
index 0000000..8b1c934
--- /dev/null
+++ b/raid-survival/client/components/renderable/group.component.ts
@@ -0,0 +1,15 @@
+import { GroupConfig, Group, Container } from "@nanoforge-dev/graphics-2d";
+import { SpriteComponent } from "./sprite.component";
+
+export class GroupComponent {
+ name = this.constructor.name;
+ group: Group;
+
+ constructor(parent: Container, options: GroupConfig) {
+ this.group = new Group(options);
+ parent.add(this.group);
+ }
+}
+
+// * Required to generate code
+export default SpriteComponent.name;
diff --git a/raid-survival/client/components/renderable/rect.component.ts b/raid-survival/client/components/renderable/rect.component.ts
new file mode 100644
index 0000000..28a84fa
--- /dev/null
+++ b/raid-survival/client/components/renderable/rect.component.ts
@@ -0,0 +1,15 @@
+import { Container, Rect, RectConfig } from "@nanoforge-dev/graphics-2d";
+import { SpriteComponent } from "./sprite.component";
+
+export class RectComponent {
+ name = this.constructor.name;
+ rect: Rect;
+
+ constructor(parent: Container, options: RectConfig) {
+ this.rect = new Rect(options);
+ parent.add(this.rect);
+ }
+}
+
+// * Required to generate code
+export default SpriteComponent.name;
\ No newline at end of file
diff --git a/raid-survival/client/components/renderable/sprite.component.ts b/raid-survival/client/components/renderable/sprite.component.ts
new file mode 100644
index 0000000..402b9aa
--- /dev/null
+++ b/raid-survival/client/components/renderable/sprite.component.ts
@@ -0,0 +1,65 @@
+import { Layer, Sprite, Vector2d } from "@nanoforge-dev/graphics-2d";
+
+interface SpriteComponentOptions {
+ animationsKey?: string;
+ layer?: Layer;
+ scale?: Vector2d;
+ currentAnimation?: string;
+}
+
+export class SpriteComponent {
+ name = this.constructor.name;
+ spriteKey: string;
+ sprite: Sprite | undefined;
+ animationsKey?: string;
+ layer: Layer | undefined;
+ loading: boolean = false;
+
+ private _scale: Vector2d = { x: 1, y: 1 };
+ private _currentAnimation: string = "idle";
+ private _flipped: boolean = false;
+
+ constructor(spriteKey: string, options?: SpriteComponentOptions) {
+ this.spriteKey = spriteKey;
+ if (options?.animationsKey) this.animationsKey = options.animationsKey;
+ if (options?.scale) this._scale = options.scale;
+ if (options?.currentAnimation) this._currentAnimation = options.currentAnimation;
+ if (options?.layer) this.layer = options.layer;
+ }
+
+ getAnimation(): string {
+ return this._currentAnimation;
+ }
+
+ setAnimation(animation: string) {
+ if (animation === this._currentAnimation) return;
+ this._currentAnimation = animation;
+ this.sprite?.animation(animation);
+ }
+
+ getScale(): Vector2d {
+ return this._scale;
+ }
+
+ setScale(scale: Vector2d) {
+ this._scale = scale;
+ this.sprite?.scale(scale);
+ }
+
+ isFlipped() {
+ return this._flipped;
+ }
+
+ flip() {
+ this._flipped = true;
+ this.sprite?.scaleX(this._scale.x * -1);
+ }
+
+ unflip() {
+ this._flipped = false;
+ this.sprite?.scaleX(this._scale.x);
+ }
+}
+
+// * Required to generate code
+export default SpriteComponent.name;
\ No newline at end of file
diff --git a/raid-survival/client/components/renderable/text.component.ts b/raid-survival/client/components/renderable/text.component.ts
new file mode 100644
index 0000000..986ce2a
--- /dev/null
+++ b/raid-survival/client/components/renderable/text.component.ts
@@ -0,0 +1,14 @@
+import { Container, Text, TextConfig } from "@nanoforge-dev/graphics-2d";
+
+export class TextComponent {
+ name = this.constructor.name;
+ text: Text;
+
+ constructor(parent: Container, options: TextConfig) {
+ this.text = new Text(options);
+ parent.add(this.text);
+ }
+}
+
+// * Required to generate code
+export default TextComponent.name;
\ No newline at end of file
diff --git a/raid-survival/client/components/renderable/textarea.component.ts b/raid-survival/client/components/renderable/textarea.component.ts
new file mode 100644
index 0000000..a170413
--- /dev/null
+++ b/raid-survival/client/components/renderable/textarea.component.ts
@@ -0,0 +1,134 @@
+import {
+ Container,
+ document,
+ Group,
+ Layer,
+ Stage,
+ Text,
+ TextConfig,
+} from "@nanoforge-dev/graphics-2d";
+
+export class TextAreaComponent {
+ name = this.constructor.name;
+ text: Text;
+ wrapper: HTMLDivElement;
+ textarea: HTMLTextAreaElement;
+ value: string = "";
+ private readonly clipAncestor: Group | Layer | Stage | null;
+
+ constructor(parent: Container, options: TextConfig) {
+ this.text = new Text(options);
+ parent.add(this.text);
+ this.text.hide();
+
+ this.clipAncestor = this.findClipAncestor(parent);
+
+ this.wrapper = document.createElement("div");
+ this.wrapper.style.position = "absolute";
+ this.wrapper.style.overflow = "hidden";
+ this.wrapper.style.pointerEvents = "none";
+ document.body.appendChild(this.wrapper);
+
+ this.textarea = document.createElement("textarea");
+ this.textarea.value = this.text.text();
+ this.textarea.style.position = "absolute";
+ this.textarea.style.border = "none";
+ this.textarea.style.padding = "0px";
+ this.textarea.style.margin = "0px";
+ this.textarea.style.overflow = "hidden";
+ this.textarea.style.background = "none";
+ this.textarea.style.outline = "none";
+ this.textarea.style.resize = "none";
+ this.textarea.style.transformOrigin = "left top";
+ this.textarea.style.pointerEvents = "auto";
+ this.applyTextStyle();
+ this.wrapper.appendChild(this.textarea);
+
+ this.textarea.addEventListener("input", () => {
+ this.text.text(this.textarea.value);
+ });
+
+ this.sync();
+ }
+
+ private applyTextStyle(): void {
+ this.textarea.style.fontSize = this.text.fontSize() + "px";
+ this.textarea.style.lineHeight = this.text.lineHeight().toString();
+ this.textarea.style.fontFamily = this.text.fontFamily();
+ this.textarea.style.textAlign = this.text.align();
+ this.textarea.style.color = (this.text.fill() as string) ?? "#000";
+ this.textarea.style.width = this.text.width() - this.text.padding() * 2 + "px";
+ this.textarea.style.height = this.text.height() - this.text.padding() * 2 + 5 + "px";
+ }
+
+ private findClipAncestor(node: Container): Group | Layer | Stage | null {
+ let current: Container | null = node;
+ while (current) {
+ const clipWidth = current.clipWidth?.();
+ if (clipWidth) return current as Group | Layer | Stage;
+ current = current.getParent();
+ }
+ return null;
+ }
+
+ sync(): void {
+ const stage = this.text.getStage();
+ if (!stage) return;
+
+ const containerRect = stage.container().getBoundingClientRect();
+ const textAbs = this.text.getAbsolutePosition();
+ const scale = stage.getAbsoluteScale();
+
+ const pageX = containerRect.left + textAbs.x * scale.x;
+ const pageY = containerRect.top + textAbs.y * scale.y;
+
+ if (this.clipAncestor) {
+ const clipRect = this.getAbsoluteClipRect(this.clipAncestor, containerRect, scale);
+
+ this.wrapper.style.left = clipRect.x + "px";
+ this.wrapper.style.top = clipRect.y + "px";
+ this.wrapper.style.width = clipRect.width + "px";
+ this.wrapper.style.height = clipRect.height + "px";
+
+ this.textarea.style.left = pageX - clipRect.x + "px";
+ this.textarea.style.top = pageY - clipRect.y + "px";
+ } else {
+ this.wrapper.style.left = "0px";
+ this.wrapper.style.top = "0px";
+ this.wrapper.style.width = "100vw";
+ this.wrapper.style.height = "100vh";
+ this.textarea.style.left = pageX + "px";
+ this.textarea.style.top = pageY + "px";
+ }
+
+ this.value = this.textarea.value;
+ }
+
+ private getAbsoluteClipRect(
+ node: Group | Layer | Stage,
+ containerRect: DOMRect,
+ scale: { x: number; y: number },
+ ): { x: number; y: number; width: number; height: number } {
+ const n = node;
+ const cx = n.clipX?.() ?? 0;
+ const cy = n.clipY?.() ?? 0;
+ const cw = n.clipWidth?.() ?? node.width();
+ const ch = n.clipHeight?.() ?? node.height();
+
+ const topLeft = node.getAbsoluteTransform().point({ x: cx, y: cy });
+ const nodeScale = node.getAbsoluteScale();
+
+ return {
+ x: containerRect.left + topLeft.x * scale.x,
+ y: containerRect.top + topLeft.y * scale.y,
+ width: cw * nodeScale.x,
+ height: ch * nodeScale.y,
+ };
+ }
+
+ destroy(): void {
+ this.text.destroy();
+ this.textarea.remove();
+ this.wrapper.remove();
+ }
+}
diff --git a/raid-survival/client/components/shoot.controller.ts b/raid-survival/client/components/shoot.controller.ts
new file mode 100644
index 0000000..c2ac238
--- /dev/null
+++ b/raid-survival/client/components/shoot.controller.ts
@@ -0,0 +1,36 @@
+import { Vector2d } from "@nanoforge-dev/graphics-2d";
+import { InputEnum } from "@nanoforge-dev/input";
+
+export class ShootController {
+ name = this.constructor.name;
+ public position: Vector2d = { x: 0, y: 0 };
+
+ public aimingMode: "mouse" | "arrows" | "joystick";
+ public keyShootMainWeapon: InputEnum;
+ public keyShootSecondWeapon: InputEnum;
+ public mainWeaponShooting: boolean = false;
+ public secondWeaponShooting: boolean = false;
+
+ constructor(
+ clientConfig: {
+ keybinds: {
+ aimingMode: "mouse" | "arrows" | "joystick";
+ shootMainWeapon: InputEnum;
+ shootSecondWeapon: InputEnum;
+ };
+ } = {
+ keybinds: {
+ aimingMode: "mouse",
+ shootMainWeapon: InputEnum.MouseLeft,
+ shootSecondWeapon: InputEnum.MouseRight,
+ },
+ },
+ ) {
+ this.aimingMode = clientConfig.keybinds.aimingMode;
+ this.keyShootMainWeapon = clientConfig.keybinds.shootMainWeapon;
+ this.keyShootSecondWeapon = clientConfig.keybinds.shootSecondWeapon;
+ }
+}
+
+// * Required to generate code
+export default ShootController.name;
diff --git a/raid-survival/client/main.ts b/raid-survival/client/main.ts
new file mode 100644
index 0000000..1980fa5
--- /dev/null
+++ b/raid-survival/client/main.ts
@@ -0,0 +1,111 @@
+import { type IRunOptions } from "@nanoforge-dev/common";
+import { NanoforgeFactory } from "@nanoforge-dev/core";
+
+import { AssetManagerLibrary } from "@nanoforge-dev/asset-manager";
+import { ECSClientLibrary } from "@nanoforge-dev/ecs-client";
+import { Graphics2DLibrary } from "@nanoforge-dev/graphics-2d";
+import { InputEnum, InputLibrary } from "@nanoforge-dev/input";
+import { MusicLibrary } from "@nanoforge-dev/music";
+import { NetworkClientLibrary } from "@nanoforge-dev/network-client";
+import { SoundLibrary } from "@nanoforge-dev/sound";
+import { moveSystem } from "./systems/move.system";
+import { spriteAnimator } from "./systems/renderable/sprite-animator.system";
+import { spriteSystem } from "./systems/renderable/sprite.system";
+import { cameraFollowSystem } from "./systems/camera-follow.system";
+import { moveControl } from "./systems/move-control.system";
+import { shootControl } from "./systems/shoot-control.system";
+import { sendMoveControl } from "./systems/packet-senders/move-control.senders.system";
+import { sendShootControl } from "./systems/packet-senders/shoot-control.sender.system";
+import { packetHandler } from "./systems/packet-handler.system";
+import {SceneManager} from "./scenes/SceneManager";
+import {MenuScene} from "./scenes/MenuScene";
+import {textareaSystem} from "./systems/renderable/textarea";
+import { sceneSystem } from "./systems/scene";
+import { lobbyActionSenders } from "./systems/packet-senders/lobby-action.senders";
+import { transformChildrenToParentSystem } from "./systems/transform-children-to-parent.system";
+import { rotateToDirectionSystem } from "./systems/rotate-to-direction.system";
+import { zOrderSystem } from "./systems/essentials/z-order.system";
+
+export let clientConfig: {
+ keybinds: {
+ aimingMode: "mouse" | "arrows" | "joystick";
+ shootMainWeapon: InputEnum;
+ shootSecondWeapon: InputEnum;
+ up: InputEnum;
+ left: InputEnum;
+ down: InputEnum;
+ right: InputEnum;
+ };
+ login: string;
+};
+
+export let sceneManager: SceneManager;
+export let playerId: number;
+
+export function setPlayerId(id: number) {
+ playerId = id;
+}
+
+export async function main(options: IRunOptions) {
+ const app = NanoforgeFactory.createClient();
+
+ const assetManagerLibrary = new AssetManagerLibrary();
+ const ecsLibrary = new ECSClientLibrary();
+ const graphicsLibrary = new Graphics2DLibrary();
+ const inputLibrary = new InputLibrary();
+ const musicLibrary = new MusicLibrary();
+ const networkLibrary = new NetworkClientLibrary();
+ const soundLibrary = new SoundLibrary();
+
+ app.useAssetManager(assetManagerLibrary);
+ app.useComponentSystem(ecsLibrary);
+ app.useGraphics(graphicsLibrary);
+ app.useInput(inputLibrary);
+ app.use(Symbol("music"), musicLibrary);
+ app.useNetwork(networkLibrary);
+ app.useSound(soundLibrary);
+
+ await app.init(options);
+
+ const registry = ecsLibrary.registry;
+
+ registry.addSystem(sceneSystem);
+ registry.addSystem(moveControl);
+ registry.addSystem(textareaSystem);
+ registry.addSystem(shootControl);
+ registry.addSystem(spriteAnimator);
+ registry.addSystem(cameraFollowSystem);
+ registry.addSystem(moveSystem);
+ registry.addSystem(spriteSystem);
+ registry.addSystem(transformChildrenToParentSystem);
+ registry.addSystem(rotateToDirectionSystem);
+ registry.addSystem(zOrderSystem);
+
+ registry.addSystem(packetHandler);
+
+ registry.addSystem(sendMoveControl);
+ registry.addSystem(sendShootControl);
+ registry.addSystem(lobbyActionSenders);
+
+ async function waitForConnection(): Promise {
+ if (networkLibrary.tcp?.isConnected()) return;
+
+ return new Promise((resolve) => {
+ const check = () => {
+ if (networkLibrary.tcp.isConnected()) {
+ resolve();
+ } else {
+ setTimeout(check, 50);
+ }
+ };
+ check();
+ });
+ }
+
+ await waitForConnection();
+
+ sceneManager = new SceneManager(registry, graphicsLibrary.stage);
+ sceneManager.switchTo(new MenuScene());
+
+ await app.run();
+}
diff --git a/raid-survival/client/scenes/GameScene.ts b/raid-survival/client/scenes/GameScene.ts
new file mode 100644
index 0000000..dcf897a
--- /dev/null
+++ b/raid-survival/client/scenes/GameScene.ts
@@ -0,0 +1,33 @@
+import { Scene } from "./Scene";
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { Layer, Stage } from "@nanoforge-dev/graphics-2d";
+import { TransformComponent } from "../components/essentials/transform.component";
+import { SpriteComponent } from "../components/renderable/sprite.component";
+
+export class GameScene implements Scene {
+ readonly name = "GameScene";
+ layer: Layer | undefined;
+
+ private stage!: Stage;
+
+ load(registry: Registry, stage: Stage): void {
+ this.stage = stage;
+
+
+ this.layer = new Layer();
+ this.layer?.scale({x: 3, y: 3});
+ const context = this.layer.getCanvas()._canvas.getContext("2d");
+ if (context) context.imageSmoothingEnabled = false;
+ this.stage.add(this.layer);
+
+ const map = registry.spawnEntity();
+ registry.addComponent(map, new TransformComponent(0, 0));
+ registry.addComponent(map, new SpriteComponent("map.png", { layer: this.layer }));
+ }
+
+ unload() {
+ this.layer?.destroy();
+ }
+
+ tick(): void {}
+}
\ No newline at end of file
diff --git a/raid-survival/client/scenes/MenuScene.ts b/raid-survival/client/scenes/MenuScene.ts
new file mode 100644
index 0000000..9cc2869
--- /dev/null
+++ b/raid-survival/client/scenes/MenuScene.ts
@@ -0,0 +1,330 @@
+import { Scene } from "./Scene";
+import { Container, Easings, Group, Layer, Stage, Tween } from "@nanoforge-dev/graphics-2d";
+import { Registry } from "@nanoforge-dev/ecs-client";
+
+import { RectComponent } from "../components/renderable/rect.component";
+import { TextAreaComponent } from "../components/renderable/textarea.component";
+import { GroupComponent } from "../components/renderable/group.component";
+import { TextComponent } from "../components/renderable/text.component";
+import { LobbyAction, LobbyState, LobbyStatusComponent } from "../components/lobby/lobby-status";
+
+export class MenuScene implements Scene {
+ readonly name = "menu";
+ layer: Layer | undefined;
+ private stage!: Stage;
+
+ private lobbyStatusComponent: LobbyStatusComponent | undefined;
+
+ private joinLobbyGroup: Group | undefined;
+ private lobbyGroup: Group | undefined;
+
+ private menu: "User" | "Lobby" = "User";
+ private playerCases: TextComponent[] = [];
+
+ load(registry: Registry, stage: Stage): void {
+ const lobbyStatus = registry.spawnEntity();
+ this.lobbyStatusComponent = new LobbyStatusComponent();
+ registry.addComponent(lobbyStatus, this.lobbyStatusComponent);
+
+ this.stage = stage;
+
+ this.layer = new Layer();
+ this.stage.add(this.layer);
+
+ const background = registry.spawnEntity();
+ registry.addComponent(
+ background,
+ new RectComponent(this.layer, {
+ x: 0,
+ y: 0,
+ width: window.innerWidth,
+ height: window.innerHeight,
+ fill: "#0B2E1A",
+ }),
+ );
+
+ const mainWidgetSize = { width: 500, height: 600 };
+ const mainWidgetGroup = registry.spawnEntity();
+ const mainWidgetGroupComponent = new GroupComponent(this.layer, {
+ x: window.innerWidth / 2 - mainWidgetSize.width / 2,
+ y: window.innerHeight / 2 - mainWidgetSize.height / 2,
+ width: mainWidgetSize.width,
+ height: mainWidgetSize.height,
+ clip: {
+ x: 0,
+ y: 0,
+ width: mainWidgetSize.width,
+ height: mainWidgetSize.height,
+ },
+ });
+ registry.addComponent(mainWidgetGroup, mainWidgetGroupComponent);
+
+ const mainWidgetBackground = registry.spawnEntity();
+ registry.addComponent(
+ mainWidgetBackground,
+ new RectComponent(mainWidgetGroupComponent.group, {
+ x: 0,
+ y: 0,
+ width: mainWidgetGroupComponent.group.width(),
+ height: mainWidgetGroupComponent.group.height(),
+ fill: "#104522",
+ cornerRadius: 5,
+ }),
+ );
+
+ this.joinLobbyGroup = this.buildJoinLobbyWidget(registry, mainWidgetGroupComponent.group).group;
+ this.lobbyGroup = this.buildLobbyWidget(registry, mainWidgetGroupComponent.group).group;
+ }
+
+ unload(): void {
+ this.layer?.destroy();
+ }
+
+ tick(registry: Registry) {
+ const entities: { LobbyStatusComponent: LobbyStatusComponent }[] = registry.getZipper([
+ LobbyStatusComponent,
+ ]);
+ const firstLobbyStatus = entities[0];
+
+ if (!firstLobbyStatus) return;
+
+ if (
+ firstLobbyStatus.LobbyStatusComponent.state === LobbyState.JOINED &&
+ this.menu !== "Lobby"
+ ) {
+ this.menu = "Lobby";
+ if (this.joinLobbyGroup && this.lobbyGroup) {
+ new Tween({
+ node: this.joinLobbyGroup,
+ duration: 0.4,
+ x: -this.joinLobbyGroup.width(),
+ easing: Easings.EaseInOut,
+ }).play();
+ new Tween({
+ node: this.lobbyGroup,
+ duration: 0.4,
+ x: 0,
+ easing: Easings.EaseInOut,
+ }).play();
+ }
+ }
+
+ for (let i = 0; i < firstLobbyStatus.LobbyStatusComponent.players.length; i += 1) {
+ const textPlayerCase = this.playerCases[i];
+ const player = firstLobbyStatus.LobbyStatusComponent.players[i];
+ if (!textPlayerCase || !player) return;
+
+ textPlayerCase.text.text(player.username);
+ }
+ }
+
+ private buildJoinLobbyWidget(registry: Registry, parent: Container): GroupComponent {
+ const joinLobbyGroup = registry.spawnEntity();
+ const joinLobbyGroupComponent = new GroupComponent(parent, {
+ x: 0,
+ y: 0,
+ width: parent.width(),
+ height: parent.height(),
+ });
+ registry.addComponent(joinLobbyGroup, joinLobbyGroupComponent);
+
+ const pseudoTextSize = { width: 300, height: 30 };
+ const pseudoTextBackground = registry.spawnEntity();
+ registry.addComponent(
+ pseudoTextBackground,
+ new RectComponent(joinLobbyGroupComponent.group, {
+ x: joinLobbyGroupComponent.group.width() / 2 - pseudoTextSize.width / 2,
+ y: joinLobbyGroupComponent.group.height() / 2 - pseudoTextSize.height / 2,
+ width: pseudoTextSize.width,
+ height: pseudoTextSize.height,
+ fill: "#FFFFFF",
+ }),
+ );
+ const pseudoText = registry.spawnEntity();
+ const pseudoTextComponent = new TextAreaComponent(joinLobbyGroupComponent.group, {
+ text: "Player" + Math.floor(Math.random() * 10000).toString(),
+ x: joinLobbyGroupComponent.group.width() / 2 - pseudoTextSize.width / 2,
+ y: joinLobbyGroupComponent.group.height() / 2 - pseudoTextSize.height / 2,
+ width: pseudoTextSize.width,
+ height: pseudoTextSize.height,
+ fontSize: 24,
+ verticalAlign: "middle",
+ padding: 2,
+ });
+ registry.addComponent(pseudoText, pseudoTextComponent);
+
+ const joinLobbyButtonSize = { width: 300, height: 50 };
+ const joinLobbyButton = registry.spawnEntity();
+ const joinLobbyButtonComponent = new RectComponent(joinLobbyGroupComponent.group, {
+ x: joinLobbyGroupComponent.group.width() / 2 - joinLobbyButtonSize.width / 2,
+ y:
+ joinLobbyGroupComponent.group.height() / 2 -
+ joinLobbyButtonSize.height / 2 +
+ pseudoTextSize.height +
+ 20,
+ width: joinLobbyButtonSize.width,
+ height: joinLobbyButtonSize.height,
+ cornerRadius: 10,
+ fill: "#1F6B45",
+ stroke: "#134e2c",
+ strokeWidth: 2,
+ shadowEnabled: false,
+ shadowOffsetX: 1,
+ shadowOffsetY: 1,
+ shadowBlur: 2,
+ });
+ registry.addComponent(joinLobbyButton, joinLobbyButtonComponent);
+ joinLobbyButtonComponent.rect.on("mouseover", () => {
+ joinLobbyButtonComponent.rect.shadowEnabled(true);
+ if (this.stage) this.stage.container().style.cursor = "pointer";
+ });
+ joinLobbyButtonComponent.rect.on("mouseout", () => {
+ joinLobbyButtonComponent.rect.shadowEnabled(false);
+ if (this.stage) this.stage.container().style.cursor = "default";
+ });
+ joinLobbyButtonComponent.rect.on("click", () => {
+ if (this.lobbyStatusComponent) {
+ this.lobbyStatusComponent.username = pseudoTextComponent.value;
+ this.lobbyStatusComponent.action = LobbyAction.JOIN_LOBBY;
+ }
+ });
+
+ const textJoinLobbyButton = registry.spawnEntity();
+ const textJoinLobbyButtonComponent = new TextComponent(joinLobbyGroupComponent.group, {
+ text: "Join Lobby",
+ x: joinLobbyGroupComponent.group.width() / 2 - joinLobbyButtonSize.width / 2,
+ y:
+ joinLobbyGroupComponent.group.height() / 2 -
+ joinLobbyButtonSize.height / 2 +
+ pseudoTextSize.height +
+ 20,
+ width: joinLobbyButtonSize.width,
+ height: joinLobbyButtonSize.height,
+ fontSize: 24,
+ verticalAlign: "middle",
+ align: "center",
+ fill: "#F5F2E9",
+ fontStyle: "bold",
+ listening: false,
+ });
+ registry.addComponent(textJoinLobbyButton, textJoinLobbyButtonComponent);
+
+ return joinLobbyGroupComponent;
+ }
+
+ private buildLobbyWidget(registry: Registry, parent: Container): GroupComponent {
+ const lobbyGroup = registry.spawnEntity();
+ const lobbyGroupComponent = new GroupComponent(parent, {
+ x: parent.width(),
+ y: 0,
+ width: parent.width(),
+ height: parent.height(),
+ });
+ registry.addComponent(lobbyGroup, lobbyGroupComponent);
+
+ const lobbyGroupPadding = 10;
+ const playerPadding = 10;
+ const GRID_COLS = 2;
+ const GRID_ROWS = 2;
+
+ const availableWidth = lobbyGroupComponent.group.width() - lobbyGroupPadding * 2;
+
+ const cellSize = (availableWidth - playerPadding * (GRID_COLS + 1)) / GRID_COLS;
+
+ const gridWidth = cellSize * GRID_COLS + playerPadding * (GRID_COLS + 1);
+ const gridHeight = cellSize * GRID_ROWS + playerPadding * (GRID_ROWS + 1);
+
+ const gridX = lobbyGroupPadding;
+ const gridY = lobbyGroupPadding;
+
+ const playersBackground = registry.spawnEntity();
+ const playersBackgroundComponent = new RectComponent(lobbyGroupComponent.group, {
+ x: gridX,
+ y: gridY,
+ width: gridWidth,
+ height: gridHeight,
+ fill: "#1F6B45",
+ cornerRadius: 5,
+ });
+ registry.addComponent(playersBackground, playersBackgroundComponent);
+
+ for (let i = 0; i < GRID_COLS * GRID_ROWS; i++) {
+ const col = i % GRID_COLS;
+ const row = Math.floor(i / GRID_COLS);
+
+ const newPlayer = registry.spawnEntity();
+ const newPlayerBackground = new RectComponent(lobbyGroupComponent.group, {
+ x: gridX + playerPadding + col * (cellSize + playerPadding),
+ y: gridY + playerPadding + row * (cellSize + playerPadding),
+ width: cellSize,
+ height: cellSize,
+ fill: "#000000",
+ cornerRadius: 5,
+ });
+ registry.addComponent(newPlayer, newPlayerBackground);
+
+ const newPlayerUsername = registry.spawnEntity();
+ const newPlayerUsernameTextComponent = new TextComponent(lobbyGroupComponent.group, {
+ x: gridX + playerPadding + col * (cellSize + playerPadding),
+ y: gridY + playerPadding + row * (cellSize + playerPadding) + (cellSize * 3) / 4,
+ width: cellSize,
+ height: cellSize / 4,
+ fill: "#FFFFFF",
+ align: "center",
+ });
+ this.playerCases.push(newPlayerUsernameTextComponent);
+ registry.addComponent(newPlayerUsername, newPlayerUsernameTextComponent);
+ }
+
+ const buttonSize = { width: gridWidth, height: 50 };
+ const buttonGap = 20;
+ const buttonY = gridY + gridHeight + buttonGap;
+
+ const startButton = registry.spawnEntity();
+ const startButtonComponent = new RectComponent(lobbyGroupComponent.group, {
+ x: gridX,
+ y: buttonY,
+ width: buttonSize.width,
+ height: buttonSize.height,
+ cornerRadius: 10,
+ fill: "#1F6B45",
+ stroke: "#5E8C61",
+ strokeWidth: 2,
+ shadowEnabled: false,
+ shadowOffsetX: 2,
+ shadowOffsetY: 2,
+ });
+ registry.addComponent(startButton, startButtonComponent);
+
+ startButtonComponent.rect.on("mouseover", () => {
+ startButtonComponent.rect.shadowEnabled(true);
+ this.stage.container().style.cursor = "pointer";
+ });
+ startButtonComponent.rect.on("mouseout", () => {
+ startButtonComponent.rect.shadowEnabled(false);
+ this.stage.container().style.cursor = "default";
+ });
+ startButtonComponent.rect.on("click", () => {
+ if (this.lobbyStatusComponent) this.lobbyStatusComponent.action = LobbyAction.START_GAME;
+ this.stage.container().style.cursor = "default";
+ });
+
+ const startButtonText = registry.spawnEntity();
+ const startButtonTextComponent = new TextComponent(lobbyGroupComponent.group, {
+ text: "Start",
+ x: gridX,
+ y: buttonY,
+ width: buttonSize.width,
+ height: buttonSize.height,
+ fontSize: 24,
+ verticalAlign: "middle",
+ align: "center",
+ fill: "#F5F2E9",
+ fontStyle: "bold",
+ listening: false,
+ });
+ registry.addComponent(startButtonText, startButtonTextComponent);
+
+ return lobbyGroupComponent;
+ }
+}
\ No newline at end of file
diff --git a/raid-survival/client/scenes/Scene.ts b/raid-survival/client/scenes/Scene.ts
new file mode 100644
index 0000000..2eb7d2d
--- /dev/null
+++ b/raid-survival/client/scenes/Scene.ts
@@ -0,0 +1,12 @@
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { Layer, Stage } from "@nanoforge-dev/graphics-2d";
+import {Context} from "@nanoforge-dev/common";
+
+export interface Scene {
+ readonly name: string;
+ layer: Layer | undefined;
+
+ load(registry: Registry, stage: Stage): void;
+ unload?(registry: Registry, stage: Stage): void;
+ tick(registry: Registry, ctx: Context): void;
+}
diff --git a/raid-survival/client/scenes/SceneManager.ts b/raid-survival/client/scenes/SceneManager.ts
new file mode 100644
index 0000000..7e4c3fe
--- /dev/null
+++ b/raid-survival/client/scenes/SceneManager.ts
@@ -0,0 +1,25 @@
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { Scene } from "./Scene";
+import { Stage } from "@nanoforge-dev/graphics-2d";
+
+export class SceneManager {
+ private current: Scene | null = null;
+
+ constructor(
+ private registry: Registry,
+ private stage: Stage,
+ ) {}
+
+ switchTo(scene: Scene) {
+ this.current?.unload?.(this.registry, this.stage);
+ this.registry.clearEntities();
+ this.stage.clear();
+
+ this.current = scene;
+ scene.load(this.registry, this.stage);
+ }
+
+ getScene(): Scene | null {
+ return this.current;
+ }
+}
diff --git a/raid-survival/client/static/GZ2-Character_1_24px.png b/raid-survival/client/static/GZ2-Character_1_24px.png
new file mode 100644
index 0000000..9b97f4f
Binary files /dev/null and b/raid-survival/client/static/GZ2-Character_1_24px.png differ
diff --git a/raid-survival/client/static/GZ2-Character_2_24px.png b/raid-survival/client/static/GZ2-Character_2_24px.png
new file mode 100644
index 0000000..ded0ef2
Binary files /dev/null and b/raid-survival/client/static/GZ2-Character_2_24px.png differ
diff --git a/raid-survival/client/static/GZ2-Character_3_24px.png b/raid-survival/client/static/GZ2-Character_3_24px.png
new file mode 100644
index 0000000..7ec5660
Binary files /dev/null and b/raid-survival/client/static/GZ2-Character_3_24px.png differ
diff --git a/raid-survival/client/static/GZ2-Weapons_16px.png b/raid-survival/client/static/GZ2-Weapons_16px.png
new file mode 100644
index 0000000..a9b66b1
Binary files /dev/null and b/raid-survival/client/static/GZ2-Weapons_16px.png differ
diff --git a/raid-survival/client/static/GZ2-Zombie-Heavy.png b/raid-survival/client/static/GZ2-Zombie-Heavy.png
new file mode 100644
index 0000000..a137f39
Binary files /dev/null and b/raid-survival/client/static/GZ2-Zombie-Heavy.png differ
diff --git a/raid-survival/client/static/GZ2-Zombie-Puncher.png b/raid-survival/client/static/GZ2-Zombie-Puncher.png
new file mode 100644
index 0000000..44594c4
Binary files /dev/null and b/raid-survival/client/static/GZ2-Zombie-Puncher.png differ
diff --git a/raid-survival/client/static/client-config.json b/raid-survival/client/static/client-config.json
new file mode 100644
index 0000000..e36d9a4
--- /dev/null
+++ b/raid-survival/client/static/client-config.json
@@ -0,0 +1,12 @@
+{
+ "keybinds": {
+ "up": "KeyW",
+ "left": "KeyA",
+ "down": "KeyS",
+ "right": "KeyD",
+ "aimingMode": "Mouse",
+ "shootMainWeapon": "MouseLeft",
+ "shootSecondWeapon": "KeyF",
+ "inspectEntity": "MouseRight"
+ }
+}
diff --git a/raid-survival/client/static/hand.png b/raid-survival/client/static/hand.png
new file mode 100644
index 0000000..28634c1
Binary files /dev/null and b/raid-survival/client/static/hand.png differ
diff --git a/raid-survival/client/static/hands.png b/raid-survival/client/static/hands.png
new file mode 100644
index 0000000..884a5f0
Binary files /dev/null and b/raid-survival/client/static/hands.png differ
diff --git a/raid-survival/client/static/health-bar-fill-animations.txt b/raid-survival/client/static/health-bar-fill-animations.txt
new file mode 100644
index 0000000..1e16325
--- /dev/null
+++ b/raid-survival/client/static/health-bar-fill-animations.txt
@@ -0,0 +1 @@
+idle: 34,7,12,2
diff --git a/raid-survival/client/static/health-bar-frame-animations.txt b/raid-survival/client/static/health-bar-frame-animations.txt
new file mode 100644
index 0000000..65b6933
--- /dev/null
+++ b/raid-survival/client/static/health-bar-frame-animations.txt
@@ -0,0 +1 @@
+idle: 4,5,23,6
diff --git a/raid-survival/client/static/map.png b/raid-survival/client/static/map.png
new file mode 100644
index 0000000..3e824ea
Binary files /dev/null and b/raid-survival/client/static/map.png differ
diff --git a/raid-survival/client/static/objects-animations.txt b/raid-survival/client/static/objects-animations.txt
new file mode 100644
index 0000000..c923191
--- /dev/null
+++ b/raid-survival/client/static/objects-animations.txt
@@ -0,0 +1 @@
+idle: 16,193,187,143
diff --git a/raid-survival/client/static/objects.png b/raid-survival/client/static/objects.png
new file mode 100644
index 0000000..7347dfb
Binary files /dev/null and b/raid-survival/client/static/objects.png differ
diff --git a/raid-survival/client/static/player-animations.txt b/raid-survival/client/static/player-animations.txt
new file mode 100644
index 0000000..095cfac
--- /dev/null
+++ b/raid-survival/client/static/player-animations.txt
@@ -0,0 +1,3 @@
+idle: 0,0,24,24 24,0,24,24 48,0,24,24 72,0,24,24
+walk: 0,24,24,24 24,24,24,24 48,24,24,24 72,24,24,24 96,24,24,24 120,24,24,24 144,24,24,24 168,24,24,24
+death: 0,48,24,24 24,48,24,24 48,48,24,24 72,48,24,24 96,48,24,24 120,48,24,24 144,48,24,24 168,48,24,24 192,48,24,24
diff --git a/raid-survival/client/static/player1.png b/raid-survival/client/static/player1.png
new file mode 100644
index 0000000..9b97f4f
Binary files /dev/null and b/raid-survival/client/static/player1.png differ
diff --git a/raid-survival/client/static/player2.png b/raid-survival/client/static/player2.png
new file mode 100644
index 0000000..ded0ef2
Binary files /dev/null and b/raid-survival/client/static/player2.png differ
diff --git a/raid-survival/client/static/player3.png b/raid-survival/client/static/player3.png
new file mode 100644
index 0000000..7ec5660
Binary files /dev/null and b/raid-survival/client/static/player3.png differ
diff --git a/raid-survival/client/static/player4.png b/raid-survival/client/static/player4.png
new file mode 100644
index 0000000..3864bca
Binary files /dev/null and b/raid-survival/client/static/player4.png differ
diff --git a/raid-survival/client/static/ui.png b/raid-survival/client/static/ui.png
new file mode 100644
index 0000000..83291f5
Binary files /dev/null and b/raid-survival/client/static/ui.png differ
diff --git a/raid-survival/client/static/zombie_cop.png b/raid-survival/client/static/zombie_cop.png
new file mode 100644
index 0000000..7472ba4
Binary files /dev/null and b/raid-survival/client/static/zombie_cop.png differ
diff --git a/raid-survival/client/systems/camera-follow.system.ts b/raid-survival/client/systems/camera-follow.system.ts
new file mode 100644
index 0000000..ce4363f
--- /dev/null
+++ b/raid-survival/client/systems/camera-follow.system.ts
@@ -0,0 +1,31 @@
+import { type Registry } from "@nanoforge-dev/ecs-client";
+import { TransformComponent } from "../components/essentials/transform.component";
+import { MoveController } from "../components/move-controller.component";
+import { sceneManager } from "../main";
+
+const CAMERA_SMOOTHING = 0.1;
+
+export const cameraFollowSystem = (registry: Registry) => {
+ const entities: { MoveController: MoveController; TransformComponent: TransformComponent }[] =
+ registry.getZipper([MoveController, TransformComponent]);
+
+ const scene = sceneManager.getScene()
+
+ if (!scene || !scene.layer) return;
+
+ for (const entity of entities) {
+ const viewWidth = scene.layer.width();
+ const viewHeight = scene.layer.height();
+
+ const scale = scene.layer.scaleX();
+
+ const targetX = viewWidth / 2 - entity.TransformComponent.x * scale;
+ const targetY = viewHeight / 2 - entity.TransformComponent.y * scale;
+
+ const current = scene.layer.position();
+ scene.layer.position({
+ x: current.x + (targetX - current.x) * CAMERA_SMOOTHING,
+ y: current.y + (targetY - current.y) * CAMERA_SMOOTHING
+ });
+ }
+}
diff --git a/raid-survival/client/systems/essentials/z-order.system.ts b/raid-survival/client/systems/essentials/z-order.system.ts
new file mode 100644
index 0000000..c787a24
--- /dev/null
+++ b/raid-survival/client/systems/essentials/z-order.system.ts
@@ -0,0 +1,19 @@
+import { ZIndexComponent } from "../../components/essentials/z-index.component";
+import { SpriteComponent } from "../../components/renderable/sprite.component";
+import { Registry } from "@nanoforge-dev/ecs-client";
+
+let lastOrder: number[] = [];
+
+export function zOrderSystem(registry: Registry) {
+ const entities: {ZIndexComponent: ZIndexComponent, SpriteComponent: SpriteComponent}[] = registry.getZipper([ZIndexComponent, SpriteComponent]);
+ const sorted = [...entities].sort((a, b) => a.ZIndexComponent.value - b.ZIndexComponent.value);
+
+ const currentOrder = sorted.map((e) => e.SpriteComponent.sprite?._id ?? -1);
+ const changed =
+ currentOrder.length !== lastOrder.length || currentOrder.some((id, i) => id !== lastOrder[i]);
+
+ if (!changed) return;
+
+ sorted.forEach(({ SpriteComponent: sc }) => sc.sprite?.moveToTop());
+ lastOrder = currentOrder;
+}
\ No newline at end of file
diff --git a/raid-survival/client/systems/move-control.system.ts b/raid-survival/client/systems/move-control.system.ts
new file mode 100644
index 0000000..b2b70d9
--- /dev/null
+++ b/raid-survival/client/systems/move-control.system.ts
@@ -0,0 +1,19 @@
+import { type Context } from "@nanoforge-dev/common";
+import { type Registry } from "@nanoforge-dev/ecs-client";
+import { type InputLibrary } from "@nanoforge-dev/input";
+
+import { MoveController } from "../components/move-controller.component";
+
+export function moveControl(registry: Registry, ctx: Context) {
+ const entities = registry.getZipper([MoveController]);
+ const input = ctx.libs.getInput();
+
+ entities.forEach(({ MoveController }) => {
+ MoveController.movingUp = input.isKeyPressed(MoveController.keyUp);
+ MoveController.movingDown = input.isKeyPressed(MoveController.keyDown);
+ MoveController.movingLeft = input.isKeyPressed(MoveController.keyLeft);
+ MoveController.movingRight = input.isKeyPressed(MoveController.keyRight);
+ });
+}
+// * Required to generate code
+export default moveControl.name;
diff --git a/raid-survival/client/systems/move.system.ts b/raid-survival/client/systems/move.system.ts
new file mode 100644
index 0000000..1eff07e
--- /dev/null
+++ b/raid-survival/client/systems/move.system.ts
@@ -0,0 +1,16 @@
+import { type Context } from "@nanoforge-dev/common";
+import { type Registry } from "@nanoforge-dev/ecs-client";
+
+import { TransformComponent } from "../components/essentials/transform.component";
+import { Velocity } from "../components/essentials/velocity.component";
+
+export function moveSystem(registry: Registry, ctx: Context) {
+ const entities = registry.getZipper([TransformComponent, Velocity]);
+
+ entities.forEach(({ TransformComponent, Velocity }) => {
+ TransformComponent.x += (Velocity.x * ctx.app.delta) / 1000;
+ TransformComponent.y += (Velocity.y * ctx.app.delta) / 1000;
+ });
+}
+// * Required to generate code
+export default moveSystem.name;
diff --git a/raid-survival/client/systems/packet-handler.system.ts b/raid-survival/client/systems/packet-handler.system.ts
new file mode 100644
index 0000000..57a9860
--- /dev/null
+++ b/raid-survival/client/systems/packet-handler.system.ts
@@ -0,0 +1,38 @@
+import { type Registry } from "@nanoforge-dev/ecs-client";
+import { NetworkClientLibrary } from "@nanoforge-dev/network-client";
+import { Context } from "@nanoforge-dev/common";
+import { movePacketHandler } from "./packet-handlers/move-packet.handler";
+import { spawnPacketHandler } from "./packet-handlers/spawn-packet.handler";
+import { directionPacketHandler } from "./packet-handlers/direction-packet.handler";
+import { killPacketHandler } from "./packet-handlers/kill-packet.handler";
+import { hitPacketHandler } from "./packet-handlers/hit-packet.handler";
+import { joinLobbyPacketHandler } from "./packet-handlers/join-lobby-packet.handler";
+import { lobbyInfoPacketHandler } from "./packet-handlers/lobby-info-packet.handler";
+import {startGamePacketHandler} from "./packet-handlers/start-game-packet.handler";
+
+export type PacketHandler = (packet: unknown, registry: Registry, ctx: Context) => unknown;
+
+export const packetHandlers: Map = new Map([
+ ["move", movePacketHandler],
+ ["spawn", spawnPacketHandler],
+ ["direction", directionPacketHandler],
+ ["kill", killPacketHandler],
+ ["hit", hitPacketHandler],
+ ["joinLobby", joinLobbyPacketHandler],
+ ["lobbyInfo", lobbyInfoPacketHandler],
+ ["startGame", startGamePacketHandler],
+]);
+
+export function packetHandler(registry: Registry, ctx: Context) {
+ const network = ctx.libs.getNetwork();
+ const jsonPackets = network.tcp
+ .getReceivedPackets()
+ .map((packet: AllowSharedBufferSource | undefined) => {
+ return JSON.parse(new TextDecoder().decode(packet));
+ });
+
+ if (!jsonPackets || jsonPackets.length === 0) return;
+ jsonPackets.forEach((packet) => {
+ packetHandlers.get(packet.type)?.(packet, registry, ctx);
+ });
+}
diff --git a/raid-survival/client/systems/packet-handlers/direction-packet.handler.ts b/raid-survival/client/systems/packet-handlers/direction-packet.handler.ts
new file mode 100644
index 0000000..c92da57
--- /dev/null
+++ b/raid-survival/client/systems/packet-handlers/direction-packet.handler.ts
@@ -0,0 +1,13 @@
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { NetworkId } from "../../components/network-id.component";
+import { Direction } from "../../components/direction.component";
+
+export function directionPacketHandler(packet: any, registry: Registry): void {
+ const zipper = registry.getZipper([NetworkId, Direction]);
+ const it = zipper.find((entity) => {
+ return entity.NetworkId.id === packet.id;
+ });
+ if (!it) return;
+ it.Direction.x = packet.direction.x;
+ it.Direction.y = packet.direction.y;
+}
diff --git a/raid-survival/client/systems/packet-handlers/hit-packet.handler.ts b/raid-survival/client/systems/packet-handlers/hit-packet.handler.ts
new file mode 100644
index 0000000..cf99127
--- /dev/null
+++ b/raid-survival/client/systems/packet-handlers/hit-packet.handler.ts
@@ -0,0 +1,42 @@
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { NetworkId } from "../../components/network-id.component";
+import { Health } from "../../components/health.component";
+import { ChildrenComponent } from "../../components/children.component";
+import { SpriteComponent } from "../../components/renderable/sprite.component";
+import { HealthBarFill } from "../../components/health-bar-fill.component";
+
+// Must match the health bar geometry it was built with
+// (client/systems/packet-handlers/start-game-packet.handler.ts).
+const HEALTH_BAR_FILL_WIDTH = 12;
+const HEALTH_BAR_FILL_CAVITY_WIDTH = 19;
+const HEALTH_BAR_FILL_MAX_SCALE_X = HEALTH_BAR_FILL_CAVITY_WIDTH / HEALTH_BAR_FILL_WIDTH;
+
+export function hitPacketHandler(packet: any, registry: Registry): void {
+ const targets: { id: number; NetworkId: NetworkId; Health: Health }[] = registry.getIndexedZipper([
+ NetworkId,
+ Health,
+ ]);
+ const target = targets.find((entity) => entity.NetworkId.id === packet.id);
+ if (!target) return;
+
+ target.Health.current = Math.max(0, Math.min(target.Health.max, target.Health.current - packet.damage));
+ const fraction = target.Health.max > 0 ? target.Health.current / target.Health.max : 0;
+
+ const fills: {
+ ChildrenComponent: ChildrenComponent;
+ SpriteComponent: SpriteComponent;
+ HealthBarFill: HealthBarFill;
+ }[] = registry.getZipper([ChildrenComponent, SpriteComponent, HealthBarFill]);
+ const fill = fills.find((entity) => entity.ChildrenComponent.parentId === target.id);
+ if (!fill) return;
+
+ const fillScaleX = HEALTH_BAR_FILL_MAX_SCALE_X * fraction;
+ // Same anchor compensation used when the bar was first built: SpriteComponent scales around
+ // its own center, so the LocalPosition has to shift back the other way to keep the fill's
+ // left edge pinned to the cavity's left edge instead of shrinking symmetrically.
+ fill.ChildrenComponent.options.LocalPosition = {
+ x: fill.HealthBarFill.cavityLocalX - (HEALTH_BAR_FILL_WIDTH / 2) * (1 - fillScaleX),
+ y: fill.ChildrenComponent.options.LocalPosition?.y ?? 0,
+ };
+ fill.SpriteComponent.setScale({ x: fillScaleX, y: 1 });
+}
diff --git a/raid-survival/client/systems/packet-handlers/join-lobby-packet.handler.ts b/raid-survival/client/systems/packet-handlers/join-lobby-packet.handler.ts
new file mode 100644
index 0000000..75fa51d
--- /dev/null
+++ b/raid-survival/client/systems/packet-handlers/join-lobby-packet.handler.ts
@@ -0,0 +1,31 @@
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { LobbyState, LobbyStatusComponent } from "../../components/lobby/lobby-status";
+import {setPlayerId} from "../../main";
+
+export function joinLobbyPacketHandler(packet: any, registry: Registry): void {
+ const entities: { LobbyStatusComponent: LobbyStatusComponent }[] = registry.getZipper([
+ LobbyStatusComponent,
+ ]);
+ const firstLobbyStatus = entities[0];
+
+ if (!firstLobbyStatus) return;
+
+ if (packet.result === "success") {
+ firstLobbyStatus.LobbyStatusComponent.state = LobbyState.JOINED;
+ firstLobbyStatus.LobbyStatusComponent.players = [];
+ for (const player of packet.players) {
+ firstLobbyStatus.LobbyStatusComponent.players.push({
+ id: player.id,
+ username: player.username,
+ });
+ if (player.username === firstLobbyStatus.LobbyStatusComponent.username)
+ setPlayerId(player.id);
+ }
+ } else if (packet.result === "full") {
+ console.error("lobby is full");
+ } else if (packet.result === "in game") {
+ console.error("lobby is full");
+ } else {
+ console.log(packet);
+ }
+}
diff --git a/raid-survival/client/systems/packet-handlers/kill-packet.handler.ts b/raid-survival/client/systems/packet-handlers/kill-packet.handler.ts
new file mode 100644
index 0000000..288bbb9
--- /dev/null
+++ b/raid-survival/client/systems/packet-handlers/kill-packet.handler.ts
@@ -0,0 +1,11 @@
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { NetworkId } from "../../components/network-id.component";
+
+export function killPacketHandler(packet: any, registry: Registry): void {
+ const zipper = registry.getIndexedZipper([NetworkId]);
+ const it = zipper.find((entity) => {
+ return entity.NetworkId.id === packet.id;
+ });
+ if (!it) return;
+ registry.killEntity(it.id)
+}
diff --git a/raid-survival/client/systems/packet-handlers/lobby-info-packet.handler.ts b/raid-survival/client/systems/packet-handlers/lobby-info-packet.handler.ts
new file mode 100644
index 0000000..d411627
--- /dev/null
+++ b/raid-survival/client/systems/packet-handlers/lobby-info-packet.handler.ts
@@ -0,0 +1,17 @@
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { LobbyStatusComponent } from "../../components/lobby/lobby-status";
+
+export function lobbyInfoPacketHandler(packet: any, registry: Registry): void {
+ const entities: { LobbyStatusComponent: LobbyStatusComponent }[] = registry.getZipper([
+ LobbyStatusComponent,
+ ]);
+ const firstLobbyStatus = entities[0];
+
+ if (!firstLobbyStatus) return;
+
+ firstLobbyStatus.LobbyStatusComponent.players = [];
+
+ for (const player of packet.players) {
+ firstLobbyStatus.LobbyStatusComponent.players.push({id: player.id, username: player.username})
+ }
+}
diff --git a/raid-survival/client/systems/packet-handlers/move-packet.handler.ts b/raid-survival/client/systems/packet-handlers/move-packet.handler.ts
new file mode 100644
index 0000000..59e9b04
--- /dev/null
+++ b/raid-survival/client/systems/packet-handlers/move-packet.handler.ts
@@ -0,0 +1,17 @@
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { TransformComponent } from "../../components/essentials/transform.component";
+import { Velocity } from "../../components/essentials/velocity.component";
+import { NetworkId } from "../../components/network-id.component";
+
+export function movePacketHandler(packet: any, registry: Registry): void {
+ const zipper = registry.getZipper([NetworkId, TransformComponent, Velocity]);
+ const it = zipper.find((entity) => {
+ return entity.NetworkId.id === packet.id;
+ });
+ if (!it) return;
+
+ it.TransformComponent.x = packet.position.x;
+ it.TransformComponent.y = packet.position.y;
+ it.Velocity.x = packet.velocity.x;
+ it.Velocity.y = packet.velocity.y;
+}
diff --git a/raid-survival/client/systems/packet-handlers/spawn-packet.handler.ts b/raid-survival/client/systems/packet-handlers/spawn-packet.handler.ts
new file mode 100644
index 0000000..51cf1e2
--- /dev/null
+++ b/raid-survival/client/systems/packet-handlers/spawn-packet.handler.ts
@@ -0,0 +1,70 @@
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { NetworkId } from "../../components/network-id.component";
+import { TransformComponent } from "../../components/essentials/transform.component";
+import { Velocity } from "../../components/essentials/velocity.component";
+import { SpriteComponent } from "../../components/renderable/sprite.component";
+import { Direction } from "../../components/direction.component";
+import { clientConfig } from "../../main";
+import { ShootController } from "../../components/shoot.controller";
+import { MoveController } from "../../components/move-controller.component";
+import { Entity } from "@nanoforge-dev/ecs-server";
+import { ChildrenComponent } from "../../components/children.component";
+
+function buildPlayer(newEnt: Entity, packet: any, registry: Registry) {
+ if (packet.login === clientConfig.login) {
+ registry.addComponent(newEnt, new MoveController(clientConfig));
+ registry.addComponent(newEnt, new ShootController(clientConfig));
+ }
+
+ registry.addComponent(newEnt, new Direction(packet.direction.x, packet.direction.y));
+ registry.addComponent(newEnt, new Velocity(packet.velocity.x, packet.velocity.y));
+ registry.addComponent(
+ newEnt,
+ new SpriteComponent("player.png", {
+ animationsKey: "player-animations.txt",
+ scale: { x: 3, y: 3 },
+ }),
+ );
+
+ const hand = registry.spawnEntity();
+ registry.addComponent(hand, new TransformComponent(packet.position.x, packet.position.y));
+ registry.addComponent(
+ hand,
+ new SpriteComponent("hands.png", {
+ scale: { x: 3, y: 3 },
+ }),
+ );
+ registry.addComponent(
+ hand,
+ new ChildrenComponent(newEnt.getId(), {})
+ )
+}
+
+export function spawnPacketHandler(packet: any, registry: Registry): void {
+ const zipper = registry.getZipper([NetworkId]);
+ const it = zipper.find(({ NetworkId }) => {
+ return NetworkId.id === packet.id;
+ });
+ if (it) console.error("entity with networkId already exist: ", packet.networkId);
+ const newEnt = registry.spawnEntity();
+ registry.addComponent(newEnt, new TransformComponent(packet.position.x, packet.position.y));
+ if (packet.id !== undefined) {
+ registry.addComponent(newEnt, new NetworkId(packet.id));
+ }
+ switch (packet.entityType) {
+ case "player":
+ buildPlayer(newEnt, packet, registry);
+ break;
+ case "zombie":
+ registry.addComponent(newEnt, new Velocity(packet.velocity.x, packet.velocity.y));
+ registry.addComponent(newEnt, new Direction(packet.direction.x, packet.direction.y));
+ break;
+ case "map":
+ registry.addComponent(newEnt, new SpriteComponent("map.png"));
+ break;
+ case "nexus":
+ break;
+ default:
+ console.error("entity type unknow: ", packet.entityType);
+ }
+}
diff --git a/raid-survival/client/systems/packet-handlers/start-game-packet.handler.ts b/raid-survival/client/systems/packet-handlers/start-game-packet.handler.ts
new file mode 100644
index 0000000..13f0c70
--- /dev/null
+++ b/raid-survival/client/systems/packet-handlers/start-game-packet.handler.ts
@@ -0,0 +1,166 @@
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { LobbyStatusComponent } from "../../components/lobby/lobby-status";
+import { playerId, sceneManager } from "../../main";
+import { GameScene } from "../../scenes/GameScene";
+import { SpriteComponent } from "../../components/renderable/sprite.component";
+import { TransformComponent } from "../../components/essentials/transform.component";
+import { Velocity } from "../../components/essentials/velocity.component";
+import { MoveController } from "../../components/move-controller.component";
+import { Layer } from "@nanoforge-dev/graphics-2d";
+import { NetworkId } from "../../components/network-id.component";
+import { Direction } from "../../components/direction.component";
+import { ShootController } from "../../components/shoot.controller";
+import { ChildrenComponent } from "../../components/children.component";
+import { Scene } from "../../scenes/Scene";
+import { DirectionRotatorComponent } from "../../components/direction-rotator.component";
+import { Lobby } from "../../components/lobby/lobby.component";
+import { Health } from "../../components/health.component";
+import { ZIndexComponent } from "../../components/essentials/z-index.component";
+import { HealthBarFill } from "../../components/health-bar-fill.component";
+
+// Native player sprite size (player-animations.txt, unscaled).
+const PLAYER_SPRITE_SIZE = { width: 24, height: 24 };
+
+// Native size of the truck+house crop defined in objects-animations.txt's "idle" frame.
+const LOBBY_SPRITE_SIZE = { width: 187, height: 143 };
+
+// ui.png health bar sprites (see health-bar-frame-animations.txt / health-bar-fill-animations.txt).
+const HEALTH_BAR_FRAME_SIZE = { width: 23, height: 6 };
+const HEALTH_BAR_FILL_SIZE = { width: 12, height: 2 };
+// Where the fill sits inside the frame, in frame-local pixels (the frame's black/white border
+// leaves this gray cavity for the fill to sit in).
+const HEALTH_BAR_FILL_CAVITY = { x: 2, y: 2, width: 19, height: 2 };
+// Scale needed to stretch the fill sprite's native width up to the cavity's width at 100% health.
+const HEALTH_BAR_FILL_MAX_SCALE_X = HEALTH_BAR_FILL_CAVITY.width / HEALTH_BAR_FILL_SIZE.width;
+// Vertical gap between the health bar and the top of whatever it's attached to.
+const HEALTH_BAR_GAP_ABOVE = 10;
+
+function buildHealthBar(
+ scene: Scene,
+ registry: Registry,
+ parentEntity: ReturnType,
+ parentWidth: number,
+ health: { current: number; max: number },
+) {
+ const layer = scene.layer || new Layer();
+ const frameLocalX = (parentWidth - HEALTH_BAR_FRAME_SIZE.width) / 2;
+ const frameLocalY = -HEALTH_BAR_GAP_ABOVE;
+
+ const frame = registry.spawnEntity();
+ registry.addComponent(frame, new TransformComponent(0, 0));
+ registry.addComponent(
+ frame,
+ new SpriteComponent("ui.png", { layer, animationsKey: "health-bar-frame-animations.txt" }),
+ );
+ registry.addComponent(
+ frame,
+ new ChildrenComponent(parentEntity.getId(), { LocalTransform: { x: frameLocalX, y: frameLocalY } }),
+ );
+ registry.addComponent(frame, new Direction(0, 0));
+
+ const fraction = health.max > 0 ? health.current / health.max : 0;
+ const fillScaleX = HEALTH_BAR_FILL_MAX_SCALE_X * fraction;
+ // SpriteComponent scales around the sprite's own center, so shrinking it would eat into both
+ // edges symmetrically instead of draining from the right. Compensate the local X so the
+ // fill's left edge stays anchored to the cavity's left edge regardless of scale.
+ const cavityLocalX = frameLocalX + HEALTH_BAR_FILL_CAVITY.x;
+ const fillLocalX = cavityLocalX - (HEALTH_BAR_FILL_SIZE.width / 2) * (1 - fillScaleX);
+ const fillLocalY = frameLocalY + HEALTH_BAR_FILL_CAVITY.y;
+
+ const fill = registry.spawnEntity();
+ registry.addComponent(fill, new TransformComponent(0, 0));
+ registry.addComponent(
+ fill,
+ new SpriteComponent("ui.png", {
+ layer,
+ animationsKey: "health-bar-fill-animations.txt",
+ scale: { x: fillScaleX, y: 1 },
+ }),
+ );
+ registry.addComponent(
+ fill,
+ new ChildrenComponent(parentEntity.getId(), { LocalTransform: { x: fillLocalX, y: fillLocalY } }),
+ );
+ registry.addComponent(fill, new Direction(0, 0));
+ registry.addComponent(fill, new HealthBarFill(cavityLocalX));
+}
+
+function buildPlayer(scene: Scene, playerPacket: any, registry: Registry) {
+ if (!scene.layer) return;
+
+ const playerEntity = registry.spawnEntity();
+ registry.addComponent(playerEntity, new NetworkId(playerPacket.id));
+ registry.addComponent(playerEntity, new Direction(0, 0));
+ registry.addComponent(playerEntity, new ZIndexComponent(10));
+ registry.addComponent(
+ playerEntity,
+ new TransformComponent(playerPacket.position.x, playerPacket.position.y),
+ );
+ registry.addComponent(playerEntity, new Velocity(0, 0));
+ registry.addComponent(
+ playerEntity,
+ new SpriteComponent("player1.png", {
+ layer: scene.layer,
+ animationsKey: "player-animations.txt",
+ }),
+ );
+ if (playerId === playerPacket.id) {
+ registry.addComponent(playerEntity, new MoveController());
+ registry.addComponent(playerEntity, new ShootController());
+ }
+ registry.addComponent(playerEntity, new Health(playerPacket.health.current, playerPacket.health.max));
+ buildHealthBar(scene, registry, playerEntity, PLAYER_SPRITE_SIZE.width, playerPacket.health);
+
+ const hand = registry.spawnEntity();
+ registry.addComponent(hand, new TransformComponent(playerPacket.position.x, playerPacket.position.y));
+ registry.addComponent(
+ hand,
+ new SpriteComponent("hand.png", {
+ layer: scene.layer || new Layer(),
+ }),
+ );
+ registry.addComponent(hand, new ChildrenComponent(playerEntity.getId(), { LocalTransform: {x: 6, y: 12} }));
+ registry.addComponent(hand, new Direction(0, 0));
+ registry.addComponent(hand, new DirectionRotatorComponent(-90));
+ registry.addComponent(hand, new ZIndexComponent(20));
+}
+
+function buildLobby(scene: Scene, lobbyPacket: any, registry: Registry) {
+ const lobbyEntity = registry.spawnEntity();
+ registry.addComponent(lobbyEntity, new NetworkId(lobbyPacket.id));
+ registry.addComponent(
+ lobbyEntity,
+ new TransformComponent(lobbyPacket.position.x, lobbyPacket.position.y),
+ );
+ registry.addComponent(
+ lobbyEntity,
+ new SpriteComponent("objects.png", {
+ layer: scene.layer || new Layer(),
+ animationsKey: "objects-animations.txt",
+ }),
+ );
+ registry.addComponent(lobbyEntity, new Lobby());
+ registry.addComponent(lobbyEntity, new Health(lobbyPacket.health.current, lobbyPacket.health.max));
+ buildHealthBar(scene, registry, lobbyEntity, LOBBY_SPRITE_SIZE.width, lobbyPacket.health);
+}
+
+function launchGame(packet: any, registry: Registry) {
+ const newScene = new GameScene();
+ sceneManager.switchTo(newScene);
+
+ buildLobby(newScene, packet.lobby, registry);
+
+ for (const player of packet.players) {
+ buildPlayer(newScene, player, registry);
+ }
+}
+
+export function startGamePacketHandler(packet: any, registry: Registry): void {
+ const entities: { LobbyStatusComponent: LobbyStatusComponent }[] = registry.getZipper([
+ LobbyStatusComponent,
+ ]);
+ const firstLobbyStatus = entities[0];
+
+ if (!firstLobbyStatus) return;
+ launchGame(packet, registry);
+}
\ No newline at end of file
diff --git a/raid-survival/client/systems/packet-senders/lobby-action.senders.ts b/raid-survival/client/systems/packet-senders/lobby-action.senders.ts
new file mode 100644
index 0000000..126c7e8
--- /dev/null
+++ b/raid-survival/client/systems/packet-senders/lobby-action.senders.ts
@@ -0,0 +1,30 @@
+import type { Registry } from "@nanoforge-dev/ecs-client";
+import type { Context } from "@nanoforge-dev/common";
+import { NetworkClientLibrary } from "@nanoforge-dev/network-client";
+import { LobbyAction, LobbyStatusComponent } from "../../components/lobby/lobby-status";
+
+export function lobbyActionSenders(registry: Registry, ctx: Context) {
+ const entities: {LobbyStatusComponent: LobbyStatusComponent}[] = registry.getZipper([LobbyStatusComponent]);
+ const network = ctx.libs.getNetwork();
+
+ const firstLobbyStatus = entities[0];
+ if (!firstLobbyStatus) return;
+
+ switch (firstLobbyStatus.LobbyStatusComponent.action) {
+ case LobbyAction.JOIN_LOBBY:
+ network.tcp.sendData(
+ new TextEncoder().encode(JSON.stringify({ type: "joinLobby", username: firstLobbyStatus.LobbyStatusComponent.username })),
+ );
+ break;
+ case LobbyAction.START_GAME:
+ network.tcp.sendData(
+ new TextEncoder().encode(
+ JSON.stringify({
+ type: "startGame"
+ }),
+ ),
+ );
+ break;
+ }
+ firstLobbyStatus.LobbyStatusComponent.action = LobbyAction.EMPTY;
+}
\ No newline at end of file
diff --git a/raid-survival/client/systems/packet-senders/move-control.senders.system.ts b/raid-survival/client/systems/packet-senders/move-control.senders.system.ts
new file mode 100644
index 0000000..42abce5
--- /dev/null
+++ b/raid-survival/client/systems/packet-senders/move-control.senders.system.ts
@@ -0,0 +1,30 @@
+import { type Context } from "@nanoforge-dev/common";
+import { type Registry } from "@nanoforge-dev/ecs-client";
+
+import { MoveController } from "../../components/move-controller.component";
+import { Velocity } from "../../components/essentials/velocity.component";
+import { NetworkClientLibrary } from "@nanoforge-dev/network-client";
+
+function compareStringArrays(array1: string[], array2: string[]) {
+ if (array1.length !== array2.length) return false;
+ for (let i = 0; i < array1.length; i++) if (array1[i] !== array2[i]) return false;
+ return true;
+}
+
+export function sendMoveControl(registry: Registry, ctx: Context) {
+ const entities = registry.getZipper([MoveController, Velocity]);
+ const network = ctx.libs.getNetwork();
+
+ entities.forEach(({ MoveController }) => {
+ const keys: { type: string; moveKeys: string[] } = { type: "input", moveKeys: [] };
+ if (MoveController.movingUp) keys.moveKeys.push("up");
+ if (MoveController.movingDown) keys.moveKeys.push("down");
+ if (MoveController.movingLeft) keys.moveKeys.push("left");
+ if (MoveController.movingRight) keys.moveKeys.push("right");
+ if (!compareStringArrays(keys.moveKeys, MoveController.lastMoveKeys))
+ network.tcp.sendData(new TextEncoder().encode(JSON.stringify(keys)));
+ MoveController.lastMoveKeys = keys.moveKeys;
+ });
+}
+// * Required to generate code
+export default sendMoveControl.name;
diff --git a/raid-survival/client/systems/packet-senders/shoot-control.sender.system.ts b/raid-survival/client/systems/packet-senders/shoot-control.sender.system.ts
new file mode 100644
index 0000000..d12aed5
--- /dev/null
+++ b/raid-survival/client/systems/packet-senders/shoot-control.sender.system.ts
@@ -0,0 +1,32 @@
+import { type Context } from "@nanoforge-dev/common";
+import { type Registry } from "@nanoforge-dev/ecs-client";
+
+import { Velocity } from "../../components/essentials/velocity.component";
+import { NetworkClientLibrary } from "@nanoforge-dev/network-client";
+import { ShootController } from "../../components/shoot.controller";
+import { Direction } from "../../components/direction.component";
+import { Vector2d } from "@nanoforge-dev/graphics-2d";
+
+export function sendShootControl(registry: Registry, ctx: Context) {
+ const entities: { ShootController: ShootController, Velocity: Vector2d, Direction: Direction }[] = registry.getZipper([
+ ShootController,
+ Velocity,
+ Direction,
+ ]);
+ const network = ctx.libs.getNetwork()
+
+ entities.forEach(({ ShootController, Direction }) => {
+ if (ShootController.mainWeaponShooting) network.tcp.sendData(new TextEncoder().encode(JSON.stringify({ type: "input", key: "mainWeaponShooting" })));
+ if (ShootController.secondWeaponShooting) network.tcp.sendData(new TextEncoder().encode(JSON.stringify({ type: "input", key: "secondWeaponShooting" })));
+ network.tcp.sendData(
+ new TextEncoder().encode(
+ JSON.stringify({
+ type: "input",
+ direction: { x: Direction.x, y: Direction.y },
+ }),
+ ),
+ );
+ });
+}
+// * Required to generate code
+export default sendShootControl.name;
diff --git a/raid-survival/client/systems/renderable/sprite-animator.system.ts b/raid-survival/client/systems/renderable/sprite-animator.system.ts
new file mode 100644
index 0000000..a0959cb
--- /dev/null
+++ b/raid-survival/client/systems/renderable/sprite-animator.system.ts
@@ -0,0 +1,23 @@
+import { type Registry } from "@nanoforge-dev/ecs-client";
+import { Velocity } from "../../components/essentials/velocity.component";
+import { SpriteComponent } from "../../components/renderable/sprite.component";
+import { Direction } from "../../components/direction.component";
+
+export const spriteAnimator = (registry: Registry) => {
+ const entities: {Direction: Direction, SpriteComponent: SpriteComponent, Velocity: Velocity}[] = registry.getZipper([Direction, SpriteComponent, Velocity]);
+
+ entities.forEach(({ Direction, SpriteComponent, Velocity }) => {
+ if (!SpriteComponent.sprite) return;
+ if ((Velocity.y != 0 || Velocity.x != 0) && SpriteComponent.getAnimation() != "walk") {
+ SpriteComponent.setAnimation("walk");
+ } else if (Velocity.x == 0 && Velocity.y == 0 && SpriteComponent.getAnimation() != "idle") {
+ SpriteComponent.setAnimation("idle");
+ }
+
+ if (Direction.x < 0 && !SpriteComponent.isFlipped()) {
+ SpriteComponent.flip();
+ } else if (Direction.x > 0 && SpriteComponent.isFlipped()) {
+ SpriteComponent.unflip();
+ }
+ });
+};
diff --git a/raid-survival/client/systems/renderable/sprite.system.ts b/raid-survival/client/systems/renderable/sprite.system.ts
new file mode 100644
index 0000000..5d15069
--- /dev/null
+++ b/raid-survival/client/systems/renderable/sprite.system.ts
@@ -0,0 +1,182 @@
+import { type Context, NfFile } from "@nanoforge-dev/common";
+import { type Registry } from "@nanoforge-dev/ecs-client";
+
+import { Graphics2DLibrary, Sprite } from "@nanoforge-dev/graphics-2d";
+import { TransformComponent } from "../../components/essentials/transform.component";
+import { SpriteComponent } from "../../components/renderable/sprite.component";
+import { AssetManagerLibrary } from "@nanoforge-dev/asset-manager";
+
+type Animations = Record;
+
+const imageCache = new Map();
+const imageLoading = new Map>();
+const failedSpriteKeys = new Set();
+
+const ASSET_RELOAD_COOLDOWN_MS = 15000;
+const ASSET_RELOAD_STORAGE_KEY = "nf_sprite_asset_reload_at";
+
+function recoverFromStaleAsset(spriteKey: string, path: string, err: unknown) {
+ if (!path.startsWith("blob:")) return false;
+
+ try {
+ const last = Number(sessionStorage.getItem(ASSET_RELOAD_STORAGE_KEY) ?? 0);
+ if (Date.now() - last < ASSET_RELOAD_COOLDOWN_MS) {
+ console.error(
+ `spriteSystem: sprite "${spriteKey}" failed with a stale asset URL, and we already reloaded ` +
+ `recently - not reloading again to avoid a loop.`,
+ err,
+ );
+ return false;
+ }
+ sessionStorage.setItem(ASSET_RELOAD_STORAGE_KEY, String(Date.now()));
+ } catch {
+ // sessionStorage unavailable - fall through and still attempt the reload once.
+ }
+
+ console.error(
+ `spriteSystem: sprite "${spriteKey}" failed with a stale blob: asset URL (nf's hot-reload/OPFS asset ` +
+ `cache went stale for this tab) - reloading the page to recover.`,
+ err,
+ );
+ setTimeout(() => window.location.reload(), 300);
+ return true;
+}
+
+function loadImage(path: string): Promise {
+ if (imageCache.has(path)) return Promise.resolve(imageCache.get(path));
+ if (imageLoading.has(path)) return Promise.resolve(imageLoading.get(path));
+
+ const promise = new Promise((resolve, reject) => {
+ const image = new Image();
+ image.onload = () => {
+ imageCache.set(path, image);
+ imageLoading.delete(path);
+ resolve(image);
+ };
+ image.onerror = () => {
+ imageLoading.delete(path);
+ reject(new Error(`Failed to load image: ${path}`));
+ };
+ image.src = path;
+ });
+
+ imageLoading.set(path, promise);
+ return promise;
+}
+
+const animationsCache = new Map();
+const animationsLoading = new Map>();
+
+function loadAnimations(file: NfFile): Promise {
+ if (animationsCache.has(file.path)) return Promise.resolve(animationsCache.get(file.path));
+ if (animationsLoading.has(file.path)) return Promise.resolve(animationsLoading.get(file.path));
+
+ const promise = file.text().then((raw) => {
+ const result: Animations = {};
+
+ raw
+ .split("\n")
+ .map((line) => line.trim())
+ .filter((line) => line.length > 0)
+ .forEach((line) => {
+ const [name, framesStr] = line.split(":").map((s) => s.trim());
+ if (!name || !framesStr) throw new Error(`${name} failed to parse animations`);
+
+ const frames = framesStr
+ .split(" ")
+ .filter((f) => f.length > 0)
+ .flatMap((frame) => frame.split(",").map(Number));
+
+ result[name] = frames;
+ });
+
+ animationsCache.set(file.path, result);
+ return result;
+ });
+
+ animationsLoading.set(file.path, promise);
+ return promise;
+}
+
+export const spriteSystem = async (registry: Registry, ctx: Context) => {
+ const entities: {
+ id: number;
+ TransformComponent: TransformComponent;
+ SpriteComponent: SpriteComponent;
+ }[] = registry.getIndexedZipper([TransformComponent, SpriteComponent]);
+ const graphics = ctx.libs.getGraphics();
+ const assetManager = ctx.libs.getAssetManager();
+
+ for (const entity of entities) {
+ if (
+ !entity.SpriteComponent.sprite &&
+ !entity.SpriteComponent.loading &&
+ !failedSpriteKeys.has(entity.SpriteComponent.spriteKey)
+ ) {
+ entity.SpriteComponent.loading = true;
+ let imageFile: NfFile | undefined;
+ try {
+ imageFile = assetManager.getAsset(entity.SpriteComponent.spriteKey);
+ const animationsFile = assetManager.getAsset(entity.SpriteComponent.animationsKey || "");
+
+ const image = await loadImage(imageFile.path);
+ if (!image) continue;
+ const animations = animationsFile
+ ? await loadAnimations(animationsFile)
+ : {
+ idle: [0, 0, image.width, image.height],
+ };
+
+ const [, , frameWidth, frameHeight] =
+ animations && animations["idle"] ? animations["idle"] : [0, 0, image.width, image.height];
+
+ const newSprite = new Sprite({
+ x: entity.TransformComponent.x,
+ y: entity.TransformComponent.y,
+ image,
+ animation: "idle",
+ animations,
+ frameRate: 7,
+ width: frameWidth || 24,
+ height: frameHeight || 24,
+ scale: {
+ x: entity.SpriteComponent.getScale().x,
+ y: entity.SpriteComponent.getScale().y,
+ },
+ });
+
+ newSprite.offsetX(newSprite.width() / 2);
+
+ entity.SpriteComponent.sprite = newSprite;
+
+ newSprite.start();
+ entity.SpriteComponent.layer?.add(newSprite);
+ } catch (err) {
+ failedSpriteKeys.add(entity.SpriteComponent.spriteKey);
+
+ const recovering = imageFile ? recoverFromStaleAsset(entity.SpriteComponent.spriteKey, imageFile.path, err) : false;
+ if (!recovering) {
+ console.error(
+ `spriteSystem: giving up on sprite "${entity.SpriteComponent.spriteKey}" after a load failure ` +
+ `(this entity will stay invisible; it will not be retried).`,
+ err,
+ );
+ }
+ } finally {
+ entity.SpriteComponent.loading = false;
+ }
+ }
+
+ entity.SpriteComponent.sprite?.position({
+ x: entity.TransformComponent.x + entity.SpriteComponent.sprite.offsetX(),
+ y: entity.TransformComponent.y + entity.SpriteComponent.sprite.offsetY(),
+ });
+
+ entity.SpriteComponent.sprite?.rotation(entity.TransformComponent.rotation);
+ }
+
+ graphics.stage.batchDraw();
+};
+
+// * Required to generate code
+export default spriteSystem.name;
diff --git a/raid-survival/client/systems/renderable/textarea.ts b/raid-survival/client/systems/renderable/textarea.ts
new file mode 100644
index 0000000..26c20e5
--- /dev/null
+++ b/raid-survival/client/systems/renderable/textarea.ts
@@ -0,0 +1,10 @@
+import type { Registry } from "@nanoforge-dev/ecs-client";
+import { TextAreaComponent } from "../../components/renderable/textarea.component";
+
+export const textareaSystem = async (registry: Registry) => {
+ const entities: {TextAreaComponent: TextAreaComponent}[] = registry.getIndexedZipper([TextAreaComponent]);
+
+ for (const entity of entities) {
+ entity.TextAreaComponent.sync();
+ }
+}
\ No newline at end of file
diff --git a/raid-survival/client/systems/rotate-to-direction.system.ts b/raid-survival/client/systems/rotate-to-direction.system.ts
new file mode 100644
index 0000000..b5eb807
--- /dev/null
+++ b/raid-survival/client/systems/rotate-to-direction.system.ts
@@ -0,0 +1,19 @@
+import type { Registry } from "@nanoforge-dev/ecs-client";
+import { Direction } from "../components/direction.component";
+import { DirectionRotatorComponent } from "../components/direction-rotator.component";
+import { TransformComponent } from "../components/essentials/transform.component";
+
+export function rotateToDirectionSystem(registry: Registry) {
+ const entities: {
+ TransformComponent: TransformComponent;
+ Direction: Direction;
+ DirectionRotatorComponent: DirectionRotatorComponent;
+ }[] = registry.getZipper([TransformComponent, Direction, DirectionRotatorComponent]);
+
+ for (const entity of entities) {
+ if (!entity.DirectionRotatorComponent.enable) return;
+ entity.TransformComponent.rotation =
+ (Math.atan2(entity.Direction.y, entity.Direction.x) * 180) / Math.PI +
+ entity.DirectionRotatorComponent.offset;
+ }
+}
\ No newline at end of file
diff --git a/raid-survival/client/systems/scene.ts b/raid-survival/client/systems/scene.ts
new file mode 100644
index 0000000..f1d466f
--- /dev/null
+++ b/raid-survival/client/systems/scene.ts
@@ -0,0 +1,8 @@
+import type { Registry } from "@nanoforge-dev/ecs-client";
+import type { Context } from "@nanoforge-dev/common";
+import { sceneManager } from "../main";
+
+export function sceneSystem(registry: Registry, ctx: Context) {
+ const scene = sceneManager.getScene()
+ scene?.tick(registry, ctx);
+}
\ No newline at end of file
diff --git a/raid-survival/client/systems/shoot-control.system.ts b/raid-survival/client/systems/shoot-control.system.ts
new file mode 100644
index 0000000..bb55d8e
--- /dev/null
+++ b/raid-survival/client/systems/shoot-control.system.ts
@@ -0,0 +1,46 @@
+import { type Context } from "@nanoforge-dev/common";
+import { type Registry } from "@nanoforge-dev/ecs-client";
+import { type InputLibrary } from "@nanoforge-dev/input";
+
+import { ShootController } from "../components/shoot.controller";
+import { Direction } from "../components/direction.component";
+import { Vector2d } from "@nanoforge-dev/graphics-2d";
+import { TransformComponent } from "../components/essentials/transform.component";
+import { sceneManager } from "../main";
+
+const PLAYER_SPRITE_SIZE: Vector2d = { x: 24, y: 24 };
+
+export function shootControl(registry: Registry, ctx: Context) {
+ const entities: {
+ ShootController: ShootController;
+ Direction: Direction;
+ TransformComponent: TransformComponent;
+ }[] = registry.getZipper([ShootController, Direction, TransformComponent]);
+ const input = ctx.libs.getInput();
+
+ const pointerPosition: Vector2d | null = sceneManager.getScene()?.layer?.getRelativePointerPosition() || null;
+
+ if (!pointerPosition) return;
+
+ entities.forEach(({ ShootController, Direction, TransformComponent }) => {
+ ShootController.mainWeaponShooting = (
+ input.isKeyPressed(ShootController.keyShootMainWeapon)
+ );
+ ShootController.secondWeaponShooting = (
+ input.isKeyPressed(ShootController.keyShootSecondWeapon)
+ );
+ ShootController.position.x = pointerPosition?.x || 0;
+ ShootController.position.y = pointerPosition?.y || 0;
+
+ const dx = pointerPosition.x - (TransformComponent.x + PLAYER_SPRITE_SIZE.x / 2);
+ const dy = pointerPosition.y - (TransformComponent.y + PLAYER_SPRITE_SIZE.y / 2);
+ const length = Math.hypot(dx, dy);
+
+ if (length > 0) {
+ Direction.x = dx / length;
+ Direction.y = dy / length;
+ }
+ });
+}
+// * Required to generate code
+export default shootControl.name;
diff --git a/raid-survival/client/systems/transform-children-to-parent.system.ts b/raid-survival/client/systems/transform-children-to-parent.system.ts
new file mode 100644
index 0000000..fda021c
--- /dev/null
+++ b/raid-survival/client/systems/transform-children-to-parent.system.ts
@@ -0,0 +1,33 @@
+import type { Registry } from "@nanoforge-dev/ecs-client";
+import { TransformComponent } from "../components/essentials/transform.component";
+import { ChildrenComponent } from "../components/children.component";
+import { Direction } from "../components/direction.component";
+
+export function transformChildrenToParentSystem(registry: Registry) {
+ const entities: {
+ ChildrenComponent: ChildrenComponent;
+ TransformComponent: TransformComponent;
+ Direction: Direction;
+ }[] = registry.getZipper([ChildrenComponent, TransformComponent, Direction]);
+
+ for (const entity of entities) {
+ const parent = registry.entityFromIndex(entity.ChildrenComponent.parentId);
+ const parentTransform: TransformComponent | undefined = registry.getEntityComponent(
+ parent,
+ TransformComponent,
+ );
+ if (parentTransform) {
+ entity.TransformComponent.x =
+ parentTransform.x + (entity.ChildrenComponent.options.LocalTransform?.x || 0);
+ entity.TransformComponent.y =
+ parentTransform.y + (entity.ChildrenComponent.options.LocalTransform?.y || 0);
+ }
+ const parentDirection: Direction | undefined = registry.getEntityComponent(
+ parent, Direction
+ );
+ if (parentDirection) {
+ entity.Direction.x = parentDirection.x;
+ entity.Direction.y = parentDirection.y;
+ }
+ }
+}
\ No newline at end of file
diff --git a/raid-survival/eslint.config.js b/raid-survival/eslint.config.js
new file mode 100644
index 0000000..79b3980
--- /dev/null
+++ b/raid-survival/eslint.config.js
@@ -0,0 +1,21 @@
+import pluginJs from "@eslint/js";
+import eslintConfigPrettier from "eslint-config-prettier";
+import globals from "globals";
+import tseslint from "typescript-eslint";
+
+export default [
+ { files: ["{client,server}/**/*.{ts}"] },
+ { languageOptions: { globals: globals.node } },
+
+ pluginJs.configs.recommended,
+ ...tseslint.configs.recommended,
+ ...tseslint.configs.strict,
+ eslintConfigPrettier,
+ { ignores: ["**/*.js", "**/*.d.ts"] },
+ {
+ files: ["**/packet-handlers/**/*.ts"],
+ rules: {
+ "@typescript-eslint/no-explicit-any": "off",
+ },
+ },
+];
diff --git a/raid-survival/nanoforge.config.json b/raid-survival/nanoforge.config.json
new file mode 100644
index 0000000..6923dda
--- /dev/null
+++ b/raid-survival/nanoforge.config.json
@@ -0,0 +1,10 @@
+{
+ "name": "raid-survival",
+ "language": "ts",
+ "client": {
+ "enable": true
+ },
+ "server": {
+ "enable": true
+ }
+}
\ No newline at end of file
diff --git a/raid-survival/package.json b/raid-survival/package.json
new file mode 100644
index 0000000..3f45b4b
--- /dev/null
+++ b/raid-survival/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "raid-survival",
+ "version": "0.0.0",
+ "description": "",
+ "author": "",
+ "scripts": {
+ "build": "nf build",
+ "start": "nf start",
+ "lint": "prettier --check . && eslint --format=pretty \"{client,server}/**/*.ts\"",
+ "format": "prettier --write . && eslint --fix --format=pretty \"{client,server}/**/*.ts\""
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.0",
+ "@nanoforge-dev/asset-manager": "^1",
+ "@nanoforge-dev/cli": "latest",
+ "@nanoforge-dev/common": "^1",
+ "@nanoforge-dev/config": "^1",
+ "@nanoforge-dev/core": "^1",
+ "@nanoforge-dev/ecs-client": "^1",
+ "@nanoforge-dev/ecs-server": "^1",
+ "@nanoforge-dev/graphics-2d": "^1",
+ "@nanoforge-dev/input": "^1",
+ "@nanoforge-dev/music": "^1",
+ "@nanoforge-dev/network-client": "^1",
+ "@nanoforge-dev/network-server": "^1",
+ "@nanoforge-dev/sound": "^1",
+ "@typescript-eslint/eslint-plugin": "^8.46.2",
+ "@typescript-eslint/parser": "^8.46.2",
+ "eslint": "^9.39.0",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-prettier": "^5.5.4",
+ "globals": "^16.5.0",
+ "lint-staged": "^16.2.6",
+ "prettier": "^3.6.2",
+ "typescript": "^5.9.3",
+ "typescript-eslint": "^8.46.2"
+ },
+ "lint-staged": {
+ "**/*.ts": [
+ "prettier --write"
+ ],
+ "{client,server}/**/*.ts": [
+ "eslint --fix"
+ ]
+ },
+ "pnpm": {
+ "neverBuiltDependencies": []
+ },
+ "private": true
+}
diff --git a/raid-survival/pnpm-lock.yaml b/raid-survival/pnpm-lock.yaml
new file mode 100644
index 0000000..71ec684
--- /dev/null
+++ b/raid-survival/pnpm-lock.yaml
@@ -0,0 +1,2585 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ devDependencies:
+ '@eslint/js':
+ specifier: ^9.39.0
+ version: 9.39.5
+ '@nanoforge-dev/asset-manager':
+ specifier: ^1
+ version: 1.4.2
+ '@nanoforge-dev/cli':
+ specifier: latest
+ version: 1.6.2
+ '@nanoforge-dev/common':
+ specifier: ^1
+ version: 1.4.2
+ '@nanoforge-dev/config':
+ specifier: ^1
+ version: 1.4.2
+ '@nanoforge-dev/core':
+ specifier: ^1
+ version: 1.4.2
+ '@nanoforge-dev/ecs-client':
+ specifier: ^1
+ version: 1.4.2
+ '@nanoforge-dev/ecs-server':
+ specifier: ^1
+ version: 1.4.2
+ '@nanoforge-dev/graphics-2d':
+ specifier: ^1
+ version: 1.4.2
+ '@nanoforge-dev/input':
+ specifier: ^1
+ version: 1.4.2
+ '@nanoforge-dev/music':
+ specifier: ^1
+ version: 1.4.2
+ '@nanoforge-dev/network-client':
+ specifier: ^1
+ version: 1.4.2
+ '@nanoforge-dev/network-server':
+ specifier: ^1
+ version: 1.4.2(supports-color@7.2.0)
+ '@nanoforge-dev/sound':
+ specifier: ^1
+ version: 1.4.2
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^8.46.2
+ version: 8.67.0(@typescript-eslint/parser@8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+ '@typescript-eslint/parser':
+ specifier: ^8.46.2
+ version: 8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+ eslint:
+ specifier: ^9.39.0
+ version: 9.39.5(supports-color@7.2.0)
+ eslint-config-prettier:
+ specifier: ^10.1.8
+ version: 10.1.8(eslint@9.39.5(supports-color@7.2.0))
+ eslint-plugin-prettier:
+ specifier: ^5.5.4
+ version: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5(supports-color@7.2.0)))(eslint@9.39.5(supports-color@7.2.0))(prettier@3.9.6)
+ globals:
+ specifier: ^16.5.0
+ version: 16.5.0
+ lint-staged:
+ specifier: ^16.2.6
+ version: 16.4.0
+ prettier:
+ specifier: ^3.6.2
+ version: 3.9.6
+ typescript:
+ specifier: ^5.9.3
+ version: 5.9.3
+ typescript-eslint:
+ specifier: ^8.46.2
+ version: 8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+
+packages:
+
+ '@angular-devkit/core@22.1.4':
+ resolution: {integrity: sha512-40jG/6chng9fQuWtpg1kadPO17sRTurNh0aav6Uxebn65PfNpTpvhPIYVIcRgv4tCKdMd++YH95A8SiaIOwqig==}
+ engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
+ peerDependencies:
+ chokidar: ^5.0.0
+ peerDependenciesMeta:
+ chokidar:
+ optional: true
+
+ '@angular-devkit/schematics-cli@22.1.4':
+ resolution: {integrity: sha512-xcerOAJ2knWBvzliwPjAgbZ15vZ8OZHziv01LolOgKqRVMBu80Ijj8jEcdlj++CXbxPD/X3Hi1OFKuNHVmHwFA==}
+ engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
+ hasBin: true
+
+ '@angular-devkit/schematics@22.1.4':
+ resolution: {integrity: sha512-UBJ7jc7B0vRfmrVwlvpmuudo1D7AfpO6ugf1BxvkM4sw5m8cJpZtaIJFKBorv0QZo78CyDOroQfQdXaijjB2ZA==}
+ engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
+
+ '@eslint-community/eslint-utils@4.10.1':
+ resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.2':
+ resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/config-array@0.21.2':
+ resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/config-helpers@0.4.2':
+ resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@0.17.0':
+ resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/eslintrc@3.3.6':
+ resolution: {integrity: sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/js@9.39.5':
+ resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/object-schema@2.1.7':
+ resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/plugin-kit@0.4.1':
+ resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@humanfs/core@0.19.2':
+ resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.8':
+ resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/types@0.15.0':
+ resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/retry@0.4.3':
+ resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
+ engines: {node: '>=18.18'}
+
+ '@inquirer/ansi@2.0.7':
+ resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+
+ '@inquirer/checkbox@5.2.1':
+ resolution: {integrity: sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/confirm@6.1.1':
+ resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/core@11.2.1':
+ resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/editor@5.2.2':
+ resolution: {integrity: sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/expand@5.1.1':
+ resolution: {integrity: sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/external-editor@3.0.3':
+ resolution: {integrity: sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/figures@2.0.7':
+ resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+
+ '@inquirer/input@5.1.2':
+ resolution: {integrity: sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/number@4.1.1':
+ resolution: {integrity: sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/password@5.1.1':
+ resolution: {integrity: sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/prompts@8.5.2':
+ resolution: {integrity: sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/rawlist@5.3.1':
+ resolution: {integrity: sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/search@4.2.1':
+ resolution: {integrity: sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/select@5.2.1':
+ resolution: {integrity: sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/type@4.0.7':
+ resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@isaacs/fs-minipass@4.0.1':
+ resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
+ engines: {node: '>=18.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@mapbox/node-pre-gyp@2.0.3':
+ resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ '@nanoforge-dev/asset-manager@1.4.2':
+ resolution: {integrity: sha512-CtUoNc5tc2QVdEs6nDER2WutBArS72RFod+cM+fwwrq+1btNeI7ed4sjbxqCBJLASqvZ7HQ3av6P8FGpfjRI6g==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/cli@1.6.2':
+ resolution: {integrity: sha512-BtjWOu6cX7A3sk256cCNA+wyem9oobzhfjg4/7tNt/zN1vLYTb5is3s+D5IRRG6M1ZovW0W1InN53ss5boBxVQ==}
+ engines: {node: '25'}
+ hasBin: true
+
+ '@nanoforge-dev/common@1.4.2':
+ resolution: {integrity: sha512-Km7eTwmpQ0XTx5o15Cu05ojRCL8px/DANVc0eYBLzKnG2/LamKs5P/e3HG+woULPz0/fc+ah/RPKyQwFtx5lBw==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/config@1.4.2':
+ resolution: {integrity: sha512-SdV/nINKC5VQyvKZs6s6T+vlsSfKE47lhYgcacWqSBdi9BTDSTj6GNZRoWpwSMyiKCZ+Wj6DbmyJvPXDc/wTgA==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/core@1.4.2':
+ resolution: {integrity: sha512-Ja7zNj78f/gMipBR7n+4u7Xb/UOZZzPMt55jb/mEj7g+0Qloq66cPwT1lHhx+8owi+lOncJ1bjIu2QUzThHvqg==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/ecs-client@1.4.2':
+ resolution: {integrity: sha512-toeySsQfH0VMsA1UKUrhRWACYj7g7ikru8SqMf5e4LdacQAOkIenMRDyuyo52OVq1fJs1e47jM4XBkX6wGghQA==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/ecs-lib@1.4.2':
+ resolution: {integrity: sha512-FM7lhHvBWvsaZs03biagHAu3P2Hlw1hoEye2b3xjj3EeVbRE8r8CNFcYtCoSbM2cVFKZBr3Z0N0UJBXLnizkgw==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/ecs-server@1.4.2':
+ resolution: {integrity: sha512-P1DCfOe2ke1iL1HvDs2kiz4RYf7NBLYV5Vbkk8jWJlgYrzRS8Lif/xRgPLE4Wjv5uASrMbTCB/M9EI+3cqRvsQ==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/editor@0.1.4':
+ resolution: {integrity: sha512-bqbrH8ulFSiZz1iisJHtHeEzj1RV/70uErCYHeW4DIhVPsWbBfWKFvg/ghAbmPk5vsj0YnhDX6DSpik4S1QoiQ==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/graphics-2d@1.4.2':
+ resolution: {integrity: sha512-wH275hs9nl48HVI8GN5UjUIupylJ9Q9dJAxA8MCxKvgkEUcKwtC2gYXIjxaf5b2CoqG1O+qipnHzpnrAQCqrgA==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/input@1.4.2':
+ resolution: {integrity: sha512-U38Eo1anJe9Fehzq4iQ/xIpDWnoWxXVLkAZ5CPf/k4iKLNm0scUg5LHZ8TswGEoaBtJmwFeiIGWU6OHfYYHcJw==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/loader-server@1.4.0':
+ resolution: {integrity: sha512-jvNoHxx54BAfjUTMZHb/T/8ApbdPV/NNGwiQITUBprH4Uv8jQmbR7zNB5Z6/vIEjSVVxrU0b4fFa+BkdnKX/iQ==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/music@1.4.2':
+ resolution: {integrity: sha512-m0UPkq5SKrjmGpPtGfSRsnEUzczBVubWEmniUD/TbKkKtFgJ/GgHxDwgH4B0LlBdHiCQJPHa8F1eJlnOd3lMsg==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/network-client@1.4.2':
+ resolution: {integrity: sha512-qV56qAjUHDakgBqq9eSvKE9/657rEr3OCP/84kylbsy0TXLGZ/jywUTnvR/9woIVkrUOzdMf2okZzFKfSf28lw==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/network-server@1.4.2':
+ resolution: {integrity: sha512-5pOQ5VuQCof1oSlmgqO0BCs4ScRDk79FFhssUL7rky/sdkyzLKYdqG0UNP6uc8WiJ7E+l8bd7dY2fOgWeLhR3w==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/schematics@2.2.1':
+ resolution: {integrity: sha512-MdmhFncakgejGRIf1HTPKTskhdv8bIst21uWRSZCXGgGv0UNYEfDiziOsWPVCzGc9dXei59RIDznBwgpAMeXLg==}
+ engines: {node: '25'}
+
+ '@nanoforge-dev/sound@1.4.2':
+ resolution: {integrity: sha512-1qZdYGeJPwAfZYj3PAIdv/y0+P9NtkVw0J9mrpmLcOyQ7DKKoYzlTPGQkpJzErE0y3epHooFcLBY8jRZegH3dg==}
+ engines: {node: '25'}
+
+ '@oven/bun-darwin-aarch64@1.3.14':
+ resolution: {integrity: sha512-Omj20SuiHBOUjUBIyqtkNjSUIjOtEOJwmbix/ZyFH4BaQ6OZTaaRWIR4TjHVz0yadHgli6lLTiAh1uarnvD49A==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@oven/bun-darwin-x64-baseline@1.3.14':
+ resolution: {integrity: sha512-OSfsTZstc898HHElhU4NccaBGOSSDn5VfahiVTnidZ9B/+wb7WTyfZJaBeJcfjwJ9H2W9uTh2TGtl3UfcXgV9g==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@oven/bun-darwin-x64@1.3.14':
+ resolution: {integrity: sha512-FFj3QdU/OhlDyZOJ8CWfN5eWLpRlT4qjZg7lMQi7jA6GuoY5ajlO1zWLP/MuHYRSbXQUvV52RejNi8DVnAp13w==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@oven/bun-freebsd-aarch64@1.3.14':
+ resolution: {integrity: sha512-LIKrXaFxAHybVO5Pf+9XP2FHUj/5APvXTUKk9dqHm5iFz4oH+W24cmhjkJirNujh9hKeTyrpWSe3no9JZKowIw==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@oven/bun-freebsd-x64@1.3.14':
+ resolution: {integrity: sha512-uwD+fGUH1ADpIF3B1U2jWzzb20QwRLZfj5QZ28GUCGrAJ/nTmWrD6YYGsblCY1wuhldRez3lU40AyuvSCyLYmw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@oven/bun-linux-aarch64-android@1.3.14':
+ resolution: {integrity: sha512-y4kq5b85lsrmFb9Xvi4w9mA5IEFJkLMrSmYn06q24KjL9rUWDWO3VFZEtteZxUN5+ec3Zm5S8OnJw1umaCbVjA==}
+ cpu: [arm64]
+ os: [android]
+
+ '@oven/bun-linux-aarch64-musl@1.3.14':
+ resolution: {integrity: sha512-jmqOA92Cd1NL/1XBd4bFkJLxQ86K0RW7ohxS2qzzAvuitO4JiIxjjTeCspoU44zCozH72HpfZfUE2On31OjnWA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oven/bun-linux-aarch64@1.3.14':
+ resolution: {integrity: sha512-X5SsPZHs+iYO8R/efIcRtc7gT2Q2DgPfliCxEkx4cXBumwkw0c/EsHMNwH3EgGpCDaZ7IYVPhpCG/xBOQHEwZw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oven/bun-linux-x64-android@1.3.14':
+ resolution: {integrity: sha512-qe9e1d+3VAEU7nAA2ol9Jvmy/o99PVMSgZhHn7Q/9O3YcDrfEqyQ8zm4zoe5qTEo8HZH0dN03Le0Ys2eQPs7eg==}
+ cpu: [x64]
+ os: [android]
+
+ '@oven/bun-linux-x64-baseline@1.3.14':
+ resolution: {integrity: sha512-q/8EdOC0yUE8FPeoOVq8/Pw5I9/tJaYmUfO/uDUAREx8IUnOJH1RJ5A3BjFqre8pvJoiZA9AovPJq5FnNNjSxA==}
+ cpu: [x64]
+ os: [linux]
+
+ '@oven/bun-linux-x64-musl-baseline@1.3.14':
+ resolution: {integrity: sha512-n6iE71G4lQE4XkrZhQQcL5YUlxDbnq6nqV7zeQi33PMsLT/0kYE+RvHOtBWZ3w0wMdXZfINmp63hIb9ijUBGtw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@oven/bun-linux-x64-musl@1.3.14':
+ resolution: {integrity: sha512-GBCB/k/sIqcr06eTNgg7g46qiUv35Jasx4XiccJ/n7RGqrE4RWUD/XJBbWFprVPjvqd59+QtSnS99XGqvftHfg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@oven/bun-linux-x64@1.3.14':
+ resolution: {integrity: sha512-7OVTAKvwfPmSbIV1HpdOoVVx5VRc427GuPPne93N6vk4eQBPId9nXmZDh9/zGaKPdbVjVtQSZafWQoUjx38Utw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@oven/bun-windows-aarch64@1.3.14':
+ resolution: {integrity: sha512-T7s3x/BsVKQObGU6QDkZeI6wKynzqGbBH1yI77jrrj5siElclxr3DQrDIk8CV4G5/SJq2HHq4kpLyYY2DKCSmA==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@oven/bun-windows-x64-baseline@1.3.14':
+ resolution: {integrity: sha512-uIjLUC1S9DWgICzuoMba7vurBJnBruE4S5CxnvmZkdqWVXRzx1Rgu636HoH+k0qeaQCFh3jeG3JQ1y6fRHv0sw==}
+ cpu: [x64]
+ os: [win32]
+
+ '@oven/bun-windows-x64@1.3.14':
+ resolution: {integrity: sha512-mUFWL3BoYkNpjd8e9PqROiFF/1Xeotq20mABJsiQH62jM1g5zqWh4khw1RZ6bX8Q8fWvlPaxG1PjofkmjUi3vg==}
+ cpu: [x64]
+ os: [win32]
+
+ '@pkgr/core@0.3.6':
+ resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+
+ '@sindresorhus/is@4.6.0':
+ resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
+ engines: {node: '>=10'}
+
+ '@types/estree@1.0.9':
+ resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/validator@13.15.10':
+ resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==}
+
+ '@typescript-eslint/eslint-plugin@8.67.0':
+ resolution: {integrity: sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^8.67.0
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/parser@8.67.0':
+ resolution: {integrity: sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/project-service@8.67.0':
+ resolution: {integrity: sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/scope-manager@8.67.0':
+ resolution: {integrity: sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/tsconfig-utils@8.67.0':
+ resolution: {integrity: sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/type-utils@8.67.0':
+ resolution: {integrity: sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/types@8.67.0':
+ resolution: {integrity: sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/typescript-estree@8.67.0':
+ resolution: {integrity: sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/utils@8.67.0':
+ resolution: {integrity: sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/visitor-keys@8.67.0':
+ resolution: {integrity: sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ abbrev@3.0.1:
+ resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==}
+ engines: {node: ^18.17.0 || >=20.5.0}
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn@8.18.0:
+ resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
+ ajv-formats@3.0.1:
+ resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
+ ajv@6.15.0:
+ resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
+
+ ajv@8.20.0:
+ resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==}
+
+ ansi-escapes@7.3.0:
+ resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==}
+ engines: {node: '>=18'}
+
+ ansi-regex@6.3.0:
+ resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==}
+ engines: {node: '>=12'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ ansi-styles@6.2.3:
+ resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
+ engines: {node: '>=12'}
+
+ ansis@4.3.1:
+ resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==}
+ engines: {node: '>=14'}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ balanced-match@4.0.4:
+ resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
+ engines: {node: 18 || 20 || >=22}
+
+ brace-expansion@1.1.18:
+ resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==}
+
+ brace-expansion@5.0.9:
+ resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==}
+ engines: {node: 20 || >=22}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ bun@1.3.14:
+ resolution: {integrity: sha512-aB6GVd42x1Y5ie1K16SF+oLGtgSkwX9hgoDdIW88pjvfTccU8F1vfpoOt34QLv0dZ1v3XimtaxPlZUG81Gx9Zg==}
+ cpu: [arm64, x64]
+ os: [darwin, linux, android, freebsd, win32]
+ hasBin: true
+
+ bundle-name@4.1.0:
+ resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
+ engines: {node: '>=18'}
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ chalk@5.6.2:
+ resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
+ engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+
+ char-regex@1.0.2:
+ resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
+ engines: {node: '>=10'}
+
+ chardet@2.2.0:
+ resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==}
+
+ chokidar@5.0.0:
+ resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
+ engines: {node: '>= 20.19.0'}
+
+ chownr@3.0.0:
+ resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
+ engines: {node: '>=18'}
+
+ class-transformer@0.5.1:
+ resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==}
+
+ class-validator@0.14.4:
+ resolution: {integrity: sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==}
+
+ class-validator@0.15.1:
+ resolution: {integrity: sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw==}
+
+ cli-cursor@5.0.0:
+ resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
+ engines: {node: '>=18'}
+
+ cli-spinners@3.4.0:
+ resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==}
+ engines: {node: '>=18.20'}
+
+ cli-truncate@5.2.0:
+ resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
+ engines: {node: '>=20'}
+
+ cli-width@4.1.0:
+ resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
+ engines: {node: '>= 12'}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ colorette@2.0.20:
+ resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
+
+ commander@14.0.3:
+ resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
+ engines: {node: '>=20'}
+
+ commander@15.0.0:
+ resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==}
+ engines: {node: '>=22.12.0'}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ consola@3.4.2:
+ resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
+ engines: {node: ^14.18.0 || >=16.10.0}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ default-browser-id@5.0.1:
+ resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==}
+ engines: {node: '>=18'}
+
+ default-browser@5.5.0:
+ resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==}
+ engines: {node: '>=18'}
+
+ define-lazy-prop@3.0.0:
+ resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
+ engines: {node: '>=12'}
+
+ defu@6.1.7:
+ resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
+
+ destr@2.0.5:
+ resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ domexception@1.0.1:
+ resolution: {integrity: sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==}
+ deprecated: Use your platform's native DOMException instead
+
+ dotenv@17.4.2:
+ resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==}
+ engines: {node: '>=12'}
+
+ emoji-regex@10.6.0:
+ resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
+
+ emojilib@2.4.0:
+ resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==}
+
+ environment@1.1.0:
+ resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
+ engines: {node: '>=18'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ eslint-config-prettier@10.1.8:
+ resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==}
+ hasBin: true
+ peerDependencies:
+ eslint: '>=7.0.0'
+
+ eslint-plugin-prettier@5.5.6:
+ resolution: {integrity: sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ '@types/eslint': '>=8.0.0'
+ eslint: '>=8.0.0'
+ eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0'
+ prettier: '>=3.0.0'
+ peerDependenciesMeta:
+ '@types/eslint':
+ optional: true
+ eslint-config-prettier:
+ optional: true
+
+ eslint-scope@8.4.0:
+ resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-visitor-keys@4.2.1:
+ resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@5.0.1:
+ resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ eslint@9.39.5:
+ resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
+ espree@10.4.0:
+ resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ esquery@1.7.0:
+ resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ eventemitter3@5.0.4:
+ resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-diff@1.3.0:
+ resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fast-string-truncated-width@3.0.3:
+ resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==}
+
+ fast-string-width@3.0.2:
+ resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==}
+
+ fast-uri@3.1.5:
+ resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==}
+
+ fast-wrap-ansi@0.2.2:
+ resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
+
+ flatted@3.4.4:
+ resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==}
+
+ get-east-asian-width@1.6.0:
+ resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
+ engines: {node: '>=18'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
+
+ globals@16.5.0:
+ resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==}
+ engines: {node: '>=18'}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ iconv-lite@0.7.3:
+ resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==}
+ engines: {node: '>=0.10.0'}
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ ignore@7.0.6:
+ resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==}
+ engines: {node: '>= 4'}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ is-docker@3.0.0:
+ resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ hasBin: true
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-fullwidth-code-point@5.1.0:
+ resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==}
+ engines: {node: '>=18'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-in-ssh@1.0.0:
+ resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==}
+ engines: {node: '>=20'}
+
+ is-inside-container@1.0.0:
+ resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
+ engines: {node: '>=14.16'}
+ hasBin: true
+
+ is-interactive@2.0.0:
+ resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
+ engines: {node: '>=12'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-unicode-supported@2.1.0:
+ resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
+ engines: {node: '>=18'}
+
+ is-wsl@3.1.1:
+ resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
+ engines: {node: '>=16'}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ js-yaml@4.3.1:
+ resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==}
+ hasBin: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ jsonc-parser@3.3.1:
+ resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ konva@10.3.1:
+ resolution: {integrity: sha512-7fT/jSkL2bZZOHRBk2QVMKAUbgHoKG72W+KJtpP5YMJUZRmjgoafROgxTADsrxKOY2ZM5eKyN/x6CM9PrlFfOg==}
+ peerDependencies:
+ canvas: ^3.0.0
+ skia-canvas: ^3.0.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+ skia-canvas:
+ optional: true
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
+ libphonenumber-js@1.13.11:
+ resolution: {integrity: sha512-ETER2kMaIFTI/Nh1a8Gk03dUF/SL0VZqtI+CcVHZxp5WIHYwNS7S+uiYZDYCvLy3lOR4/DAD5jf0h5WkePPpqg==}
+
+ lint-staged@16.4.0:
+ resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==}
+ engines: {node: '>=20.17'}
+ hasBin: true
+
+ listr2@9.0.5:
+ resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==}
+ engines: {node: '>=20.0.0'}
+
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ log-symbols@7.0.1:
+ resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==}
+ engines: {node: '>=18'}
+
+ log-update@6.1.0:
+ resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
+ engines: {node: '>=18'}
+
+ magic-string@1.0.0:
+ resolution: {integrity: sha512-CGvjzMN08iv6w1mm4/x3Gh1hLb4VnyRUA15FFpl6CsCIGGoe36k7kY5KNz9QDbSBN5I/fWHM6ZlIkUTa5xdUEA==}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ mimic-function@5.0.1:
+ resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
+ engines: {node: '>=18'}
+
+ minimatch@10.2.6:
+ resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==}
+ engines: {node: 18 || 20 || >=22}
+
+ minimatch@3.1.5:
+ resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
+
+ minipass@7.1.3:
+ resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minizlib@3.1.0:
+ resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
+ engines: {node: '>= 18'}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ mute-stream@3.0.0:
+ resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==}
+ engines: {node: ^20.17.0 || >=22.9.0}
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ node-emoji@2.2.0:
+ resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==}
+ engines: {node: '>=18'}
+
+ node-fetch@2.7.0:
+ resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+
+ nopt@8.1.0:
+ resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==}
+ engines: {node: ^18.17.0 || >=20.5.0}
+ hasBin: true
+
+ onetime@7.0.0:
+ resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
+ engines: {node: '>=18'}
+
+ open@11.0.1:
+ resolution: {integrity: sha512-NzwMUB6C1D0+Kd+9iMS/H4k+Ck3cTX6Ckyfr/gAGlmvSE1LUQZnEZvWBi4PYmMwH/S5SMeTXnE+9uAz8uF+pWw==}
+ engines: {node: '>=20'}
+
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
+ ora@9.4.1:
+ resolution: {integrity: sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==}
+ engines: {node: '>=20'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ picomatch@2.3.2:
+ resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.5:
+ resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
+ engines: {node: '>=12'}
+
+ powershell-utils@0.1.0:
+ resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==}
+ engines: {node: '>=20'}
+
+ powershell-utils@0.2.0:
+ resolution: {integrity: sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==}
+ engines: {node: '>=20'}
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ prettier-linter-helpers@1.0.1:
+ resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==}
+ engines: {node: '>=6.0.0'}
+
+ prettier@3.9.6:
+ resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ rc9@3.0.1:
+ resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==}
+
+ readdirp@5.1.1:
+ resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==}
+ engines: {node: '>= 20.19.0'}
+
+ reflect-metadata@0.2.2:
+ resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
+
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ restore-cursor@5.1.0:
+ resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
+ engines: {node: '>=18'}
+
+ rfdc@1.4.1:
+ resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
+
+ run-applescript@7.1.0:
+ resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
+ engines: {node: '>=18'}
+
+ rxjs@7.8.2:
+ resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ semver@7.8.5:
+ resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ skin-tone@2.0.0:
+ resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==}
+ engines: {node: '>=8'}
+
+ slice-ansi@7.1.2:
+ resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==}
+ engines: {node: '>=18'}
+
+ slice-ansi@8.0.0:
+ resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
+ engines: {node: '>=20'}
+
+ source-map@0.7.6:
+ resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
+ engines: {node: '>= 12'}
+
+ stdin-discarder@0.3.2:
+ resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==}
+ engines: {node: '>=18'}
+
+ string-argv@0.3.2:
+ resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
+ engines: {node: '>=0.6.19'}
+
+ string-width@7.2.0:
+ resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
+ engines: {node: '>=18'}
+
+ string-width@8.2.2:
+ resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==}
+ engines: {node: '>=20'}
+
+ strip-ansi@7.2.0:
+ resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
+ engines: {node: '>=12'}
+
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ synckit@0.11.13:
+ resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+
+ tar@7.5.22:
+ resolution: {integrity: sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==}
+ engines: {node: '>=18'}
+
+ tinyexec@1.3.0:
+ resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==}
+ engines: {node: '>=18'}
+
+ tinyglobby@0.2.17:
+ resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
+ engines: {node: '>=12.0.0'}
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ tr46@0.0.3:
+ resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+
+ ts-api-utils@2.5.0:
+ resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ typescript: '>=4.8.4'
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
+ typescript-eslint@8.67.0:
+ resolution: {integrity: sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ typescript@6.0.3:
+ resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ unicode-emoji-modifier-base@1.0.0:
+ resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==}
+ engines: {node: '>=4'}
+
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+ validator@13.15.35:
+ resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==}
+ engines: {node: '>= 0.10'}
+
+ webidl-conversions@3.0.1:
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+
+ webidl-conversions@4.0.2:
+ resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
+
+ whatwg-url@5.0.0:
+ resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
+ wrap-ansi@9.0.2:
+ resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
+ engines: {node: '>=18'}
+
+ wrtc@0.4.7:
+ resolution: {integrity: sha512-P6Hn7VT4lfSH49HxLHcHhDq+aFf/jd9dPY7lDHeFhZ22N3858EKuwm2jmnlPzpsRGEPaoF6XwkcxY5SYnt4f/g==}
+ engines: {node: ^8.11.2 || >=10.0.0}
+ bundledDependencies:
+ - node-pre-gyp
+
+ ws@8.21.3:
+ resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ wsl-utils@1.0.0:
+ resolution: {integrity: sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==}
+ engines: {node: '>=20'}
+
+ yallist@5.0.0:
+ resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
+ engines: {node: '>=18'}
+
+ yaml@2.9.0:
+ resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+ yoctocolors@2.2.0:
+ resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==}
+ engines: {node: '>=18'}
+
+snapshots:
+
+ '@angular-devkit/core@22.1.4(chokidar@5.0.0)':
+ dependencies:
+ ajv: 8.20.0
+ ajv-formats: 3.0.1(ajv@8.20.0)
+ jsonc-parser: 3.3.1
+ picomatch: 4.0.5
+ rxjs: 7.8.2
+ source-map: 0.7.6
+ optionalDependencies:
+ chokidar: 5.0.0
+
+ '@angular-devkit/schematics-cli@22.1.4(chokidar@5.0.0)':
+ dependencies:
+ '@angular-devkit/core': 22.1.4(chokidar@5.0.0)
+ '@angular-devkit/schematics': 22.1.4(chokidar@5.0.0)
+ '@inquirer/prompts': 8.5.2
+ transitivePeerDependencies:
+ - '@types/node'
+ - chokidar
+
+ '@angular-devkit/schematics@22.1.4(chokidar@5.0.0)':
+ dependencies:
+ '@angular-devkit/core': 22.1.4(chokidar@5.0.0)
+ jsonc-parser: 3.3.1
+ magic-string: 1.0.0
+ ora: 9.4.1
+ rxjs: 7.8.2
+ transitivePeerDependencies:
+ - chokidar
+
+ '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(supports-color@7.2.0))':
+ dependencies:
+ eslint: 9.39.5(supports-color@7.2.0)
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.2': {}
+
+ '@eslint/config-array@0.21.2(supports-color@7.2.0)':
+ dependencies:
+ '@eslint/object-schema': 2.1.7
+ debug: 4.4.3(supports-color@7.2.0)
+ minimatch: 3.1.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/config-helpers@0.4.2':
+ dependencies:
+ '@eslint/core': 0.17.0
+
+ '@eslint/core@0.17.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/eslintrc@3.3.6(supports-color@7.2.0)':
+ dependencies:
+ ajv: 6.15.0
+ debug: 4.4.3(supports-color@7.2.0)
+ espree: 10.4.0
+ globals: 14.0.0
+ ignore: 5.3.2
+ import-fresh: 3.3.1
+ js-yaml: 4.3.1
+ minimatch: 3.1.5
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@9.39.5': {}
+
+ '@eslint/object-schema@2.1.7': {}
+
+ '@eslint/plugin-kit@0.4.1':
+ dependencies:
+ '@eslint/core': 0.17.0
+ levn: 0.4.1
+
+ '@humanfs/core@0.19.2':
+ dependencies:
+ '@humanfs/types': 0.15.0
+
+ '@humanfs/node@0.16.8':
+ dependencies:
+ '@humanfs/core': 0.19.2
+ '@humanfs/types': 0.15.0
+ '@humanwhocodes/retry': 0.4.3
+
+ '@humanfs/types@0.15.0': {}
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/retry@0.4.3': {}
+
+ '@inquirer/ansi@2.0.7': {}
+
+ '@inquirer/checkbox@5.2.1':
+ dependencies:
+ '@inquirer/ansi': 2.0.7
+ '@inquirer/core': 11.2.1
+ '@inquirer/figures': 2.0.7
+ '@inquirer/type': 4.0.7
+
+ '@inquirer/confirm@6.1.1':
+ dependencies:
+ '@inquirer/core': 11.2.1
+ '@inquirer/type': 4.0.7
+
+ '@inquirer/core@11.2.1':
+ dependencies:
+ '@inquirer/ansi': 2.0.7
+ '@inquirer/figures': 2.0.7
+ '@inquirer/type': 4.0.7
+ cli-width: 4.1.0
+ fast-wrap-ansi: 0.2.2
+ mute-stream: 3.0.0
+ signal-exit: 4.1.0
+
+ '@inquirer/editor@5.2.2':
+ dependencies:
+ '@inquirer/core': 11.2.1
+ '@inquirer/external-editor': 3.0.3
+ '@inquirer/type': 4.0.7
+
+ '@inquirer/expand@5.1.1':
+ dependencies:
+ '@inquirer/core': 11.2.1
+ '@inquirer/type': 4.0.7
+
+ '@inquirer/external-editor@3.0.3':
+ dependencies:
+ chardet: 2.2.0
+ iconv-lite: 0.7.3
+
+ '@inquirer/figures@2.0.7': {}
+
+ '@inquirer/input@5.1.2':
+ dependencies:
+ '@inquirer/core': 11.2.1
+ '@inquirer/type': 4.0.7
+
+ '@inquirer/number@4.1.1':
+ dependencies:
+ '@inquirer/core': 11.2.1
+ '@inquirer/type': 4.0.7
+
+ '@inquirer/password@5.1.1':
+ dependencies:
+ '@inquirer/ansi': 2.0.7
+ '@inquirer/core': 11.2.1
+ '@inquirer/type': 4.0.7
+
+ '@inquirer/prompts@8.5.2':
+ dependencies:
+ '@inquirer/checkbox': 5.2.1
+ '@inquirer/confirm': 6.1.1
+ '@inquirer/editor': 5.2.2
+ '@inquirer/expand': 5.1.1
+ '@inquirer/input': 5.1.2
+ '@inquirer/number': 4.1.1
+ '@inquirer/password': 5.1.1
+ '@inquirer/rawlist': 5.3.1
+ '@inquirer/search': 4.2.1
+ '@inquirer/select': 5.2.1
+
+ '@inquirer/rawlist@5.3.1':
+ dependencies:
+ '@inquirer/core': 11.2.1
+ '@inquirer/type': 4.0.7
+
+ '@inquirer/search@4.2.1':
+ dependencies:
+ '@inquirer/core': 11.2.1
+ '@inquirer/figures': 2.0.7
+ '@inquirer/type': 4.0.7
+
+ '@inquirer/select@5.2.1':
+ dependencies:
+ '@inquirer/ansi': 2.0.7
+ '@inquirer/core': 11.2.1
+ '@inquirer/figures': 2.0.7
+ '@inquirer/type': 4.0.7
+
+ '@inquirer/type@4.0.7': {}
+
+ '@isaacs/fs-minipass@4.0.1':
+ dependencies:
+ minipass: 7.1.3
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@mapbox/node-pre-gyp@2.0.3(supports-color@7.2.0)':
+ dependencies:
+ consola: 3.4.2
+ detect-libc: 2.1.2
+ https-proxy-agent: 7.0.6(supports-color@7.2.0)
+ node-fetch: 2.7.0
+ nopt: 8.1.0
+ semver: 7.8.5
+ tar: 7.5.22
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ '@nanoforge-dev/asset-manager@1.4.2':
+ dependencies:
+ '@nanoforge-dev/common': 1.4.2
+
+ '@nanoforge-dev/cli@1.6.2':
+ dependencies:
+ '@angular-devkit/schematics': 22.1.4(chokidar@5.0.0)
+ '@angular-devkit/schematics-cli': 22.1.4(chokidar@5.0.0)
+ '@inquirer/prompts': 8.5.2
+ '@nanoforge-dev/loader-client': link:../../loader/apps/client
+ '@nanoforge-dev/loader-server': 1.4.0
+ '@nanoforge-dev/schematics': 2.2.1(chokidar@5.0.0)
+ ansis: 4.3.1
+ bun: 1.3.14
+ chokidar: 5.0.0
+ class-transformer: 0.5.1
+ class-validator: 0.15.1
+ commander: 15.0.0
+ dotenv: 17.4.2
+ node-emoji: 2.2.0
+ open: 11.0.1
+ ora: 9.4.1
+ rc9: 3.0.1
+ reflect-metadata: 0.2.2
+ optionalDependencies:
+ '@nanoforge-dev/editor': 0.1.4
+ transitivePeerDependencies:
+ - '@types/node'
+
+ '@nanoforge-dev/common@1.4.2': {}
+
+ '@nanoforge-dev/config@1.4.2':
+ dependencies:
+ class-transformer: 0.5.1
+ class-validator: 0.14.4
+
+ '@nanoforge-dev/core@1.4.2':
+ dependencies:
+ '@nanoforge-dev/asset-manager': 1.4.2
+ '@nanoforge-dev/common': 1.4.2
+ '@nanoforge-dev/input': 1.4.2
+ class-transformer: 0.5.1
+ class-validator: 0.14.4
+
+ '@nanoforge-dev/ecs-client@1.4.2':
+ dependencies:
+ '@nanoforge-dev/common': 1.4.2
+ '@nanoforge-dev/ecs-lib': 1.4.2
+
+ '@nanoforge-dev/ecs-lib@1.4.2':
+ dependencies:
+ '@nanoforge-dev/common': 1.4.2
+
+ '@nanoforge-dev/ecs-server@1.4.2':
+ dependencies:
+ '@nanoforge-dev/common': 1.4.2
+ '@nanoforge-dev/ecs-lib': 1.4.2
+
+ '@nanoforge-dev/editor@0.1.4':
+ dependencies:
+ bun: 1.3.14
+ class-transformer: 0.5.1
+ class-validator: 0.15.1
+ dotenv: 17.4.2
+ micromatch: 4.0.8
+ tar: 7.5.22
+ typescript: 6.0.3
+ optional: true
+
+ '@nanoforge-dev/graphics-2d@1.4.2':
+ dependencies:
+ '@nanoforge-dev/common': 1.4.2
+ konva: 10.3.1
+ transitivePeerDependencies:
+ - canvas
+ - skia-canvas
+
+ '@nanoforge-dev/input@1.4.2':
+ dependencies:
+ '@nanoforge-dev/common': 1.4.2
+
+ '@nanoforge-dev/loader-server@1.4.0':
+ dependencies:
+ commander: 15.0.0
+
+ '@nanoforge-dev/music@1.4.2':
+ dependencies:
+ '@nanoforge-dev/common': 1.4.2
+
+ '@nanoforge-dev/network-client@1.4.2':
+ dependencies:
+ '@nanoforge-dev/common': 1.4.2
+ '@nanoforge-dev/config': 1.4.2
+
+ '@nanoforge-dev/network-server@1.4.2(supports-color@7.2.0)':
+ dependencies:
+ '@mapbox/node-pre-gyp': 2.0.3(supports-color@7.2.0)
+ '@nanoforge-dev/common': 1.4.2
+ '@nanoforge-dev/config': 1.4.2
+ wrtc: 0.4.7
+ ws: 8.21.3
+ transitivePeerDependencies:
+ - bufferutil
+ - encoding
+ - supports-color
+ - utf-8-validate
+
+ '@nanoforge-dev/schematics@2.2.1(chokidar@5.0.0)':
+ dependencies:
+ '@angular-devkit/core': 22.1.4(chokidar@5.0.0)
+ '@angular-devkit/schematics': 22.1.4(chokidar@5.0.0)
+ rxjs: 7.8.2
+ transitivePeerDependencies:
+ - chokidar
+
+ '@nanoforge-dev/sound@1.4.2':
+ dependencies:
+ '@nanoforge-dev/common': 1.4.2
+
+ '@oven/bun-darwin-aarch64@1.3.14':
+ optional: true
+
+ '@oven/bun-darwin-x64-baseline@1.3.14':
+ optional: true
+
+ '@oven/bun-darwin-x64@1.3.14':
+ optional: true
+
+ '@oven/bun-freebsd-aarch64@1.3.14':
+ optional: true
+
+ '@oven/bun-freebsd-x64@1.3.14':
+ optional: true
+
+ '@oven/bun-linux-aarch64-android@1.3.14':
+ optional: true
+
+ '@oven/bun-linux-aarch64-musl@1.3.14':
+ optional: true
+
+ '@oven/bun-linux-aarch64@1.3.14':
+ optional: true
+
+ '@oven/bun-linux-x64-android@1.3.14':
+ optional: true
+
+ '@oven/bun-linux-x64-baseline@1.3.14':
+ optional: true
+
+ '@oven/bun-linux-x64-musl-baseline@1.3.14':
+ optional: true
+
+ '@oven/bun-linux-x64-musl@1.3.14':
+ optional: true
+
+ '@oven/bun-linux-x64@1.3.14':
+ optional: true
+
+ '@oven/bun-windows-aarch64@1.3.14':
+ optional: true
+
+ '@oven/bun-windows-x64-baseline@1.3.14':
+ optional: true
+
+ '@oven/bun-windows-x64@1.3.14':
+ optional: true
+
+ '@pkgr/core@0.3.6': {}
+
+ '@sindresorhus/is@4.6.0': {}
+
+ '@types/estree@1.0.9': {}
+
+ '@types/json-schema@7.0.15': {}
+
+ '@types/validator@13.15.10': {}
+
+ '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/regexpp': 4.12.2
+ '@typescript-eslint/parser': 8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+ '@typescript-eslint/scope-manager': 8.67.0
+ '@typescript-eslint/type-utils': 8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.67.0
+ eslint: 9.39.5(supports-color@7.2.0)
+ ignore: 7.0.6
+ natural-compare: 1.4.0
+ ts-api-utils: 2.5.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/parser@8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 8.67.0
+ '@typescript-eslint/types': 8.67.0
+ '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.67.0
+ debug: 4.4.3(supports-color@7.2.0)
+ eslint: 9.39.5(supports-color@7.2.0)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/project-service@8.67.0(supports-color@7.2.0)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.67.0
+ debug: 4.4.3(supports-color@7.2.0)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/scope-manager@8.67.0':
+ dependencies:
+ '@typescript-eslint/types': 8.67.0
+ '@typescript-eslint/visitor-keys': 8.67.0
+
+ '@typescript-eslint/tsconfig-utils@8.67.0(typescript@5.9.3)':
+ dependencies:
+ typescript: 5.9.3
+
+ '@typescript-eslint/type-utils@8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/types': 8.67.0
+ '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+ debug: 4.4.3(supports-color@7.2.0)
+ eslint: 9.39.5(supports-color@7.2.0)
+ ts-api-utils: 2.5.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/types@8.67.0': {}
+
+ '@typescript-eslint/typescript-estree@8.67.0(supports-color@7.2.0)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/project-service': 8.67.0(supports-color@7.2.0)(typescript@5.9.3)
+ '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.67.0
+ '@typescript-eslint/visitor-keys': 8.67.0
+ debug: 4.4.3(supports-color@7.2.0)
+ minimatch: 10.2.6
+ semver: 7.8.5
+ tinyglobby: 0.2.17
+ ts-api-utils: 2.5.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(supports-color@7.2.0))
+ '@typescript-eslint/scope-manager': 8.67.0
+ '@typescript-eslint/types': 8.67.0
+ '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@5.9.3)
+ eslint: 9.39.5(supports-color@7.2.0)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/visitor-keys@8.67.0':
+ dependencies:
+ '@typescript-eslint/types': 8.67.0
+ eslint-visitor-keys: 5.0.1
+
+ abbrev@3.0.1: {}
+
+ acorn-jsx@5.3.2(acorn@8.18.0):
+ dependencies:
+ acorn: 8.18.0
+
+ acorn@8.18.0: {}
+
+ agent-base@7.1.4: {}
+
+ ajv-formats@3.0.1(ajv@8.20.0):
+ optionalDependencies:
+ ajv: 8.20.0
+
+ ajv@6.15.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ ajv@8.20.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-uri: 3.1.5
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+
+ ansi-escapes@7.3.0:
+ dependencies:
+ environment: 1.1.0
+
+ ansi-regex@6.3.0: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ ansi-styles@6.2.3: {}
+
+ ansis@4.3.1: {}
+
+ argparse@2.0.1: {}
+
+ balanced-match@1.0.2: {}
+
+ balanced-match@4.0.4: {}
+
+ brace-expansion@1.1.18:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@5.0.9:
+ dependencies:
+ balanced-match: 4.0.4
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+ optional: true
+
+ bun@1.3.14:
+ optionalDependencies:
+ '@oven/bun-darwin-aarch64': 1.3.14
+ '@oven/bun-darwin-x64': 1.3.14
+ '@oven/bun-darwin-x64-baseline': 1.3.14
+ '@oven/bun-freebsd-aarch64': 1.3.14
+ '@oven/bun-freebsd-x64': 1.3.14
+ '@oven/bun-linux-aarch64': 1.3.14
+ '@oven/bun-linux-aarch64-android': 1.3.14
+ '@oven/bun-linux-aarch64-musl': 1.3.14
+ '@oven/bun-linux-x64': 1.3.14
+ '@oven/bun-linux-x64-android': 1.3.14
+ '@oven/bun-linux-x64-baseline': 1.3.14
+ '@oven/bun-linux-x64-musl': 1.3.14
+ '@oven/bun-linux-x64-musl-baseline': 1.3.14
+ '@oven/bun-windows-aarch64': 1.3.14
+ '@oven/bun-windows-x64': 1.3.14
+ '@oven/bun-windows-x64-baseline': 1.3.14
+
+ bundle-name@4.1.0:
+ dependencies:
+ run-applescript: 7.1.0
+
+ callsites@3.1.0: {}
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ chalk@5.6.2: {}
+
+ char-regex@1.0.2: {}
+
+ chardet@2.2.0: {}
+
+ chokidar@5.0.0:
+ dependencies:
+ readdirp: 5.1.1
+
+ chownr@3.0.0: {}
+
+ class-transformer@0.5.1: {}
+
+ class-validator@0.14.4:
+ dependencies:
+ '@types/validator': 13.15.10
+ libphonenumber-js: 1.13.11
+ validator: 13.15.35
+
+ class-validator@0.15.1:
+ dependencies:
+ '@types/validator': 13.15.10
+ libphonenumber-js: 1.13.11
+ validator: 13.15.35
+
+ cli-cursor@5.0.0:
+ dependencies:
+ restore-cursor: 5.1.0
+
+ cli-spinners@3.4.0: {}
+
+ cli-truncate@5.2.0:
+ dependencies:
+ slice-ansi: 8.0.0
+ string-width: 8.2.2
+
+ cli-width@4.1.0: {}
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ colorette@2.0.20: {}
+
+ commander@14.0.3: {}
+
+ commander@15.0.0: {}
+
+ concat-map@0.0.1: {}
+
+ consola@3.4.2: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ debug@4.4.3(supports-color@7.2.0):
+ dependencies:
+ ms: 2.1.3
+ optionalDependencies:
+ supports-color: 7.2.0
+
+ deep-is@0.1.4: {}
+
+ default-browser-id@5.0.1: {}
+
+ default-browser@5.5.0:
+ dependencies:
+ bundle-name: 4.1.0
+ default-browser-id: 5.0.1
+
+ define-lazy-prop@3.0.0: {}
+
+ defu@6.1.7: {}
+
+ destr@2.0.5: {}
+
+ detect-libc@2.1.2: {}
+
+ domexception@1.0.1:
+ dependencies:
+ webidl-conversions: 4.0.2
+ optional: true
+
+ dotenv@17.4.2: {}
+
+ emoji-regex@10.6.0: {}
+
+ emojilib@2.4.0: {}
+
+ environment@1.1.0: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ eslint-config-prettier@10.1.8(eslint@9.39.5(supports-color@7.2.0)):
+ dependencies:
+ eslint: 9.39.5(supports-color@7.2.0)
+
+ eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5(supports-color@7.2.0)))(eslint@9.39.5(supports-color@7.2.0))(prettier@3.9.6):
+ dependencies:
+ eslint: 9.39.5(supports-color@7.2.0)
+ prettier: 3.9.6
+ prettier-linter-helpers: 1.0.1
+ synckit: 0.11.13
+ optionalDependencies:
+ eslint-config-prettier: 10.1.8(eslint@9.39.5(supports-color@7.2.0))
+
+ eslint-scope@8.4.0:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint-visitor-keys@4.2.1: {}
+
+ eslint-visitor-keys@5.0.1: {}
+
+ eslint@9.39.5(supports-color@7.2.0):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(supports-color@7.2.0))
+ '@eslint-community/regexpp': 4.12.2
+ '@eslint/config-array': 0.21.2(supports-color@7.2.0)
+ '@eslint/config-helpers': 0.4.2
+ '@eslint/core': 0.17.0
+ '@eslint/eslintrc': 3.3.6(supports-color@7.2.0)
+ '@eslint/js': 9.39.5
+ '@eslint/plugin-kit': 0.4.1
+ '@humanfs/node': 0.16.8
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.3
+ '@types/estree': 1.0.9
+ ajv: 6.15.0
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.3(supports-color@7.2.0)
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.4.0
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ esquery: 1.7.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.5
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@10.4.0:
+ dependencies:
+ acorn: 8.18.0
+ acorn-jsx: 5.3.2(acorn@8.18.0)
+ eslint-visitor-keys: 4.2.1
+
+ esquery@1.7.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@5.3.0: {}
+
+ esutils@2.0.3: {}
+
+ eventemitter3@5.0.4: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-diff@1.3.0: {}
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fast-string-truncated-width@3.0.3: {}
+
+ fast-string-width@3.0.2:
+ dependencies:
+ fast-string-truncated-width: 3.0.3
+
+ fast-uri@3.1.5: {}
+
+ fast-wrap-ansi@0.2.2:
+ dependencies:
+ fast-string-width: 3.0.2
+
+ fdir@6.5.0(picomatch@4.0.5):
+ optionalDependencies:
+ picomatch: 4.0.5
+
+ file-entry-cache@8.0.0:
+ dependencies:
+ flat-cache: 4.0.1
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+ optional: true
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@4.0.1:
+ dependencies:
+ flatted: 3.4.4
+ keyv: 4.5.4
+
+ flatted@3.4.4: {}
+
+ get-east-asian-width@1.6.0: {}
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ globals@14.0.0: {}
+
+ globals@16.5.0: {}
+
+ has-flag@4.0.0: {}
+
+ https-proxy-agent@7.0.6(supports-color@7.2.0):
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3(supports-color@7.2.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ iconv-lite@0.7.3:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ ignore@5.3.2: {}
+
+ ignore@7.0.6: {}
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ imurmurhash@0.1.4: {}
+
+ is-docker@3.0.0: {}
+
+ is-extglob@2.1.1: {}
+
+ is-fullwidth-code-point@5.1.0:
+ dependencies:
+ get-east-asian-width: 1.6.0
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-in-ssh@1.0.0: {}
+
+ is-inside-container@1.0.0:
+ dependencies:
+ is-docker: 3.0.0
+
+ is-interactive@2.0.0: {}
+
+ is-number@7.0.0:
+ optional: true
+
+ is-unicode-supported@2.1.0: {}
+
+ is-wsl@3.1.1:
+ dependencies:
+ is-inside-container: 1.0.0
+
+ isexe@2.0.0: {}
+
+ js-yaml@4.3.1:
+ dependencies:
+ argparse: 2.0.1
+
+ json-buffer@3.0.1: {}
+
+ json-schema-traverse@0.4.1: {}
+
+ json-schema-traverse@1.0.0: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ jsonc-parser@3.3.1: {}
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ konva@10.3.1: {}
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ libphonenumber-js@1.13.11: {}
+
+ lint-staged@16.4.0:
+ dependencies:
+ commander: 14.0.3
+ listr2: 9.0.5
+ picomatch: 4.0.5
+ string-argv: 0.3.2
+ tinyexec: 1.3.0
+ yaml: 2.9.0
+
+ listr2@9.0.5:
+ dependencies:
+ cli-truncate: 5.2.0
+ colorette: 2.0.20
+ eventemitter3: 5.0.4
+ log-update: 6.1.0
+ rfdc: 1.4.1
+ wrap-ansi: 9.0.2
+
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash.merge@4.6.2: {}
+
+ log-symbols@7.0.1:
+ dependencies:
+ is-unicode-supported: 2.1.0
+ yoctocolors: 2.2.0
+
+ log-update@6.1.0:
+ dependencies:
+ ansi-escapes: 7.3.0
+ cli-cursor: 5.0.0
+ slice-ansi: 7.1.2
+ strip-ansi: 7.2.0
+ wrap-ansi: 9.0.2
+
+ magic-string@1.0.0:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.2
+ optional: true
+
+ mimic-function@5.0.1: {}
+
+ minimatch@10.2.6:
+ dependencies:
+ brace-expansion: 5.0.9
+
+ minimatch@3.1.5:
+ dependencies:
+ brace-expansion: 1.1.18
+
+ minipass@7.1.3: {}
+
+ minizlib@3.1.0:
+ dependencies:
+ minipass: 7.1.3
+
+ ms@2.1.3: {}
+
+ mute-stream@3.0.0: {}
+
+ natural-compare@1.4.0: {}
+
+ node-emoji@2.2.0:
+ dependencies:
+ '@sindresorhus/is': 4.6.0
+ char-regex: 1.0.2
+ emojilib: 2.4.0
+ skin-tone: 2.0.0
+
+ node-fetch@2.7.0:
+ dependencies:
+ whatwg-url: 5.0.0
+
+ nopt@8.1.0:
+ dependencies:
+ abbrev: 3.0.1
+
+ onetime@7.0.0:
+ dependencies:
+ mimic-function: 5.0.1
+
+ open@11.0.1:
+ dependencies:
+ default-browser: 5.5.0
+ define-lazy-prop: 3.0.0
+ is-in-ssh: 1.0.0
+ is-inside-container: 1.0.0
+ powershell-utils: 0.2.0
+ wsl-utils: 1.0.0
+
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
+ ora@9.4.1:
+ dependencies:
+ chalk: 5.6.2
+ cli-cursor: 5.0.0
+ cli-spinners: 3.4.0
+ is-interactive: 2.0.0
+ is-unicode-supported: 2.1.0
+ log-symbols: 7.0.1
+ stdin-discarder: 0.3.2
+ string-width: 8.2.2
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ path-exists@4.0.0: {}
+
+ path-key@3.1.1: {}
+
+ picomatch@2.3.2:
+ optional: true
+
+ picomatch@4.0.5: {}
+
+ powershell-utils@0.1.0: {}
+
+ powershell-utils@0.2.0: {}
+
+ prelude-ls@1.2.1: {}
+
+ prettier-linter-helpers@1.0.1:
+ dependencies:
+ fast-diff: 1.3.0
+
+ prettier@3.9.6: {}
+
+ punycode@2.3.1: {}
+
+ rc9@3.0.1:
+ dependencies:
+ defu: 6.1.7
+ destr: 2.0.5
+
+ readdirp@5.1.1: {}
+
+ reflect-metadata@0.2.2: {}
+
+ require-from-string@2.0.2: {}
+
+ resolve-from@4.0.0: {}
+
+ restore-cursor@5.1.0:
+ dependencies:
+ onetime: 7.0.0
+ signal-exit: 4.1.0
+
+ rfdc@1.4.1: {}
+
+ run-applescript@7.1.0: {}
+
+ rxjs@7.8.2:
+ dependencies:
+ tslib: 2.8.1
+
+ safer-buffer@2.1.2: {}
+
+ semver@7.8.5: {}
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ signal-exit@4.1.0: {}
+
+ skin-tone@2.0.0:
+ dependencies:
+ unicode-emoji-modifier-base: 1.0.0
+
+ slice-ansi@7.1.2:
+ dependencies:
+ ansi-styles: 6.2.3
+ is-fullwidth-code-point: 5.1.0
+
+ slice-ansi@8.0.0:
+ dependencies:
+ ansi-styles: 6.2.3
+ is-fullwidth-code-point: 5.1.0
+
+ source-map@0.7.6: {}
+
+ stdin-discarder@0.3.2: {}
+
+ string-argv@0.3.2: {}
+
+ string-width@7.2.0:
+ dependencies:
+ emoji-regex: 10.6.0
+ get-east-asian-width: 1.6.0
+ strip-ansi: 7.2.0
+
+ string-width@8.2.2:
+ dependencies:
+ get-east-asian-width: 1.6.0
+ strip-ansi: 7.2.0
+
+ strip-ansi@7.2.0:
+ dependencies:
+ ansi-regex: 6.3.0
+
+ strip-json-comments@3.1.1: {}
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ synckit@0.11.13:
+ dependencies:
+ '@pkgr/core': 0.3.6
+
+ tar@7.5.22:
+ dependencies:
+ '@isaacs/fs-minipass': 4.0.1
+ chownr: 3.0.0
+ minipass: 7.1.3
+ minizlib: 3.1.0
+ yallist: 5.0.0
+
+ tinyexec@1.3.0: {}
+
+ tinyglobby@0.2.17:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.5)
+ picomatch: 4.0.5
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+ optional: true
+
+ tr46@0.0.3: {}
+
+ ts-api-utils@2.5.0(typescript@5.9.3):
+ dependencies:
+ typescript: 5.9.3
+
+ tslib@2.8.1: {}
+
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
+ typescript-eslint@8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3):
+ dependencies:
+ '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+ '@typescript-eslint/parser': 8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+ '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+ eslint: 9.39.5(supports-color@7.2.0)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ typescript@5.9.3: {}
+
+ typescript@6.0.3:
+ optional: true
+
+ unicode-emoji-modifier-base@1.0.0: {}
+
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
+ validator@13.15.35: {}
+
+ webidl-conversions@3.0.1: {}
+
+ webidl-conversions@4.0.2:
+ optional: true
+
+ whatwg-url@5.0.0:
+ dependencies:
+ tr46: 0.0.3
+ webidl-conversions: 3.0.1
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ word-wrap@1.2.5: {}
+
+ wrap-ansi@9.0.2:
+ dependencies:
+ ansi-styles: 6.2.3
+ string-width: 7.2.0
+ strip-ansi: 7.2.0
+
+ wrtc@0.4.7:
+ optionalDependencies:
+ domexception: 1.0.1
+
+ ws@8.21.3: {}
+
+ wsl-utils@1.0.0:
+ dependencies:
+ is-wsl: 3.1.1
+ powershell-utils: 0.1.0
+
+ yallist@5.0.0: {}
+
+ yaml@2.9.0: {}
+
+ yocto-queue@0.1.0: {}
+
+ yoctocolors@2.2.0: {}
diff --git a/raid-survival/pnpm-workspace.yaml b/raid-survival/pnpm-workspace.yaml
new file mode 100644
index 0000000..eee5fd3
--- /dev/null
+++ b/raid-survival/pnpm-workspace.yaml
@@ -0,0 +1,7 @@
+allowBuilds:
+ bun: true
+ wrtc: true
+
+overrides:
+ "@nanoforge-dev/loader-website": "link:/home/leoout/eip/loader/apps/website"
+ "@nanoforge-dev/loader-client": "link:/home/leoout/eip/loader/apps/client"
diff --git a/raid-survival/prettier.config.js b/raid-survival/prettier.config.js
new file mode 100644
index 0000000..8e5015d
--- /dev/null
+++ b/raid-survival/prettier.config.js
@@ -0,0 +1,6 @@
+export default {
+ useTabs: false,
+ singleQuote: false,
+ trailingComma: "all",
+ printWidth: 100,
+};
diff --git a/raid-survival/server/components/collision-box.component.ts b/raid-survival/server/components/collision-box.component.ts
new file mode 100644
index 0000000..fa4ea43
--- /dev/null
+++ b/raid-survival/server/components/collision-box.component.ts
@@ -0,0 +1,11 @@
+export class CollisionBox {
+ name = this.constructor.name;
+
+ constructor(
+ public width: number,
+ public height: number,
+ ) {}
+}
+
+// * Required to generate code
+export default CollisionBox.name;
diff --git a/raid-survival/server/components/direction.component.ts b/raid-survival/server/components/direction.component.ts
new file mode 100644
index 0000000..2c14047
--- /dev/null
+++ b/raid-survival/server/components/direction.component.ts
@@ -0,0 +1,11 @@
+export class Direction {
+ name = this.constructor.name;
+
+ constructor(
+ public x: number,
+ public y: number,
+ ) {}
+}
+
+// * Required to generate code
+export default Direction.name;
diff --git a/raid-survival/server/components/health.component.ts b/raid-survival/server/components/health.component.ts
new file mode 100644
index 0000000..fd2451d
--- /dev/null
+++ b/raid-survival/server/components/health.component.ts
@@ -0,0 +1,11 @@
+export class Health {
+ name = this.constructor.name;
+
+ constructor(
+ public current: number,
+ public max: number,
+ ) {}
+}
+
+// * Required to generate code
+export default Health.name;
diff --git a/raid-survival/server/components/lobby.component.ts b/raid-survival/server/components/lobby.component.ts
new file mode 100644
index 0000000..87cc323
--- /dev/null
+++ b/raid-survival/server/components/lobby.component.ts
@@ -0,0 +1,6 @@
+export class Lobby {
+ name = this.constructor.name;
+}
+
+// * Required to generate code
+export default Lobby.name;
diff --git a/raid-survival/server/components/login.component.ts b/raid-survival/server/components/login.component.ts
new file mode 100644
index 0000000..55e9309
--- /dev/null
+++ b/raid-survival/server/components/login.component.ts
@@ -0,0 +1,8 @@
+export class Login {
+ name = this.constructor.name;
+
+ constructor(public id: string) {}
+}
+
+// * Required to generate code
+export default Login.name;
diff --git a/raid-survival/server/components/map-collisions.component.ts b/raid-survival/server/components/map-collisions.component.ts
new file mode 100644
index 0000000..17b3df7
--- /dev/null
+++ b/raid-survival/server/components/map-collisions.component.ts
@@ -0,0 +1,26 @@
+export interface TreeLocation {
+ x: number;
+ y: number;
+}
+
+export class MapCollisions {
+ name = this.constructor.name;
+
+ private readonly treeCells: Set;
+
+ constructor(
+ public tileSize: number,
+ public cols: number,
+ public rows: number,
+ public treeLocations: TreeLocation[],
+ ) {
+ this.treeCells = new Set(treeLocations.map(({ x, y }) => `${x},${y}`));
+ }
+
+ isTreeCell(cellX: number, cellY: number): boolean {
+ return this.treeCells.has(`${cellX},${cellY}`);
+ }
+}
+
+// * Required to generate code
+export default MapCollisions.name;
diff --git a/raid-survival/server/components/position.component.ts b/raid-survival/server/components/position.component.ts
new file mode 100644
index 0000000..23a606a
--- /dev/null
+++ b/raid-survival/server/components/position.component.ts
@@ -0,0 +1,11 @@
+export class Position {
+ name = this.constructor.name;
+
+ constructor(
+ public x: number,
+ public y: number,
+ ) {}
+}
+
+// * Required to generate code
+export default Position.name;
diff --git a/raid-survival/server/components/velocity.component.ts b/raid-survival/server/components/velocity.component.ts
new file mode 100644
index 0000000..d9afbfb
--- /dev/null
+++ b/raid-survival/server/components/velocity.component.ts
@@ -0,0 +1,11 @@
+export class Velocity {
+ name = this.constructor.name;
+
+ constructor(
+ public x: number,
+ public y: number,
+ ) {}
+}
+
+// * Required to generate code
+export default Velocity.name;
diff --git a/raid-survival/server/main.ts b/raid-survival/server/main.ts
new file mode 100644
index 0000000..56d39aa
--- /dev/null
+++ b/raid-survival/server/main.ts
@@ -0,0 +1,45 @@
+import { type IRunOptions } from "@nanoforge-dev/common";
+import { NanoforgeFactory } from "@nanoforge-dev/core";
+
+import { AssetManagerLibrary } from "@nanoforge-dev/asset-manager";
+import { ECSServerLibrary } from "@nanoforge-dev/ecs-server";
+import { NetworkServerLibrary } from "@nanoforge-dev/network-server";
+import { moveSystem } from "./systems/move.system";
+import { mapCollisionSystem } from "./systems/map-collision.system";
+import { obstacleCollisionSystem } from "./systems/obstacle-collision.system";
+import { packetHandler } from "./systems/packet-handler.system";
+
+export const PLAYER_SPEED = 100;
+
+export enum GameStatusEnum {
+ Lobby,
+ InGame,
+ EndScreen,
+}
+
+export const gameStatus = { status: GameStatusEnum.Lobby };
+
+export const clients: { clientId: number; entityId: number; username: string; connected: boolean }[] = [];
+
+export async function main(options: IRunOptions) {
+ const app = NanoforgeFactory.createServer();
+
+ const assetManagerLibrary = new AssetManagerLibrary();
+ const ecsLibrary = new ECSServerLibrary();
+ const networkLibrary = new NetworkServerLibrary();
+
+ app.useAssetManager(assetManagerLibrary);
+ app.useComponentSystem(ecsLibrary);
+ app.useNetwork(networkLibrary);
+
+ await app.init(options);
+
+ const registry = ecsLibrary.registry;
+
+ registry.addSystem(packetHandler);
+ registry.addSystem(moveSystem);
+ registry.addSystem(mapCollisionSystem);
+ registry.addSystem(obstacleCollisionSystem);
+
+ await app.run();
+}
diff --git a/raid-survival/server/network-utils.ts b/raid-survival/server/network-utils.ts
new file mode 100644
index 0000000..9347c1b
--- /dev/null
+++ b/raid-survival/server/network-utils.ts
@@ -0,0 +1,10 @@
+import { NetworkServerLibrary } from "@nanoforge-dev/network-server";
+import { clients } from "./main";
+
+export function sendToInGamePlayers(network: NetworkServerLibrary, packet: unknown) {
+ clients.forEach(({ clientId }) => {
+ if (clientId !== -1) {
+ network.tcp.sendToClient(clientId, new TextEncoder().encode(JSON.stringify(packet)));
+ }
+ });
+}
diff --git a/raid-survival/server/static/map-collision.json b/raid-survival/server/static/map-collision.json
new file mode 100644
index 0000000..6824970
--- /dev/null
+++ b/raid-survival/server/static/map-collision.json
@@ -0,0 +1,10208 @@
+{
+ "image": "raid-survival.png",
+ "tileSize": 16,
+ "cols": 100,
+ "rows": 100,
+ "collision": [
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ ]
+}
\ No newline at end of file
diff --git a/raid-survival/server/systems/collision-resolve.ts b/raid-survival/server/systems/collision-resolve.ts
new file mode 100644
index 0000000..1218df5
--- /dev/null
+++ b/raid-survival/server/systems/collision-resolve.ts
@@ -0,0 +1,46 @@
+import { type Context } from "@nanoforge-dev/common";
+import { NetworkServerLibrary } from "@nanoforge-dev/network-server";
+
+import { Position } from "../components/position.component";
+import { Velocity } from "../components/velocity.component";
+import { sendToInGamePlayers } from "../network-utils";
+
+// Shared by every server-side collision system (map trees, the lobby structure, ...): resolves
+// a moving entity's overlap with a static obstacle by reverting just the axis (or axes) that
+// caused it - so it keeps sliding along the other axis instead of hard-stopping - then
+// broadcasts the corrected position/velocity. `isBlocked` tests whether a given (x, y) overlaps
+// the obstacle.
+export function resolveCollision(
+ ctx: Context,
+ entityId: number,
+ position: Position,
+ velocity: Velocity,
+ delta: number,
+ isBlocked: (x: number, y: number) => boolean,
+): void {
+ const prevX = position.x - velocity.x * delta;
+ const prevY = position.y - velocity.y * delta;
+
+ if (!isBlocked(position.x, prevY)) {
+ // Only the Y movement caused the overlap - stop on Y, keep sliding along X.
+ position.y = prevY;
+ velocity.y = 0;
+ } else if (!isBlocked(prevX, position.y)) {
+ // Only the X movement caused the overlap - stop on X, keep sliding along Y.
+ position.x = prevX;
+ velocity.x = 0;
+ } else {
+ // Both axes overlap on their own (e.g. moving straight into a corner) - stop fully.
+ position.x = prevX;
+ position.y = prevY;
+ velocity.x = 0;
+ velocity.y = 0;
+ }
+
+ sendToInGamePlayers(ctx.libs.getNetwork(), {
+ type: "move",
+ id: entityId,
+ velocity: { x: velocity.x, y: velocity.y },
+ position: { x: position.x, y: position.y },
+ });
+}
diff --git a/raid-survival/server/systems/map-collision.system.ts b/raid-survival/server/systems/map-collision.system.ts
new file mode 100644
index 0000000..2d7737b
--- /dev/null
+++ b/raid-survival/server/systems/map-collision.system.ts
@@ -0,0 +1,48 @@
+import { type Context } from "@nanoforge-dev/common";
+import { type Registry } from "@nanoforge-dev/ecs-client";
+
+import { Position } from "../components/position.component";
+import { Velocity } from "../components/velocity.component";
+import { CollisionBox } from "../components/collision-box.component";
+import { MapCollisions } from "../components/map-collisions.component";
+import { resolveCollision } from "./collision-resolve";
+
+function overlapsTree(map: MapCollisions, x: number, y: number, box: CollisionBox): boolean {
+ const corners: [number, number][] = [
+ [x, y],
+ [x + box.width, y],
+ [x, y + box.height],
+ [x + box.width, y + box.height],
+ ];
+
+ return corners.some(([cornerX, cornerY]) => {
+ const cellX = Math.floor(cornerX / map.tileSize);
+ const cellY = Math.floor(cornerY / map.tileSize);
+ return map.isTreeCell(cellX, cellY);
+ });
+}
+
+export function mapCollisionSystem(registry: Registry, ctx: Context) {
+ const maps = registry.getZipper([MapCollisions]);
+ const map = maps[0]?.MapCollisions;
+ if (!map) return;
+
+ const delta = ctx.app.delta / 1000;
+ const entities = registry.getIndexedZipper([Position, Velocity, CollisionBox]);
+
+ for (const {
+ id: entityId,
+ Position: position,
+ Velocity: velocity,
+ CollisionBox: box,
+ } of entities) {
+ if (!overlapsTree(map, position.x, position.y, box)) continue;
+
+ resolveCollision(ctx, entityId, position, velocity, delta, (x, y) =>
+ overlapsTree(map, x, y, box),
+ );
+ }
+}
+
+// * Required to generate code
+export default mapCollisionSystem.name;
diff --git a/raid-survival/server/systems/move.system.ts b/raid-survival/server/systems/move.system.ts
new file mode 100644
index 0000000..536d1be
--- /dev/null
+++ b/raid-survival/server/systems/move.system.ts
@@ -0,0 +1,16 @@
+import { type Context } from "@nanoforge-dev/common";
+import { type Registry } from "@nanoforge-dev/ecs-client";
+
+import { Position } from "../components/position.component";
+import { Velocity } from "../components/velocity.component";
+
+export function moveSystem(registry: Registry, ctx: Context) {
+ const entities = registry.getZipper([Position, Velocity]);
+
+ entities.forEach(({ Position, Velocity }) => {
+ Position.x += (Velocity.x * ctx.app.delta) / 1000;
+ Position.y += (Velocity.y * ctx.app.delta) / 1000;
+ });
+}
+// * Required to generate code
+export default moveSystem.name;
diff --git a/raid-survival/server/systems/obstacle-collision.system.ts b/raid-survival/server/systems/obstacle-collision.system.ts
new file mode 100644
index 0000000..cd0044a
--- /dev/null
+++ b/raid-survival/server/systems/obstacle-collision.system.ts
@@ -0,0 +1,51 @@
+import { type Context } from "@nanoforge-dev/common";
+import { type Registry } from "@nanoforge-dev/ecs-client";
+
+import { Position } from "../components/position.component";
+import { Velocity } from "../components/velocity.component";
+import { CollisionBox } from "../components/collision-box.component";
+import { resolveCollision } from "./collision-resolve";
+
+function overlapsBox(
+ x1: number,
+ y1: number,
+ box1: CollisionBox,
+ x2: number,
+ y2: number,
+ box2: CollisionBox,
+): boolean {
+ return x1 < x2 + box2.width && x1 + box1.width > x2 && y1 < y2 + box2.height && y1 + box1.height > y2;
+}
+
+export function obstacleCollisionSystem(registry: Registry, ctx: Context) {
+ const boxed = registry.getIndexedZipper([Position, CollisionBox]);
+ const obstacles = boxed.filter(
+ ({ id }) => !registry.getEntityComponent(registry.entityFromIndex(id), Velocity),
+ );
+ if (obstacles.length === 0) return;
+
+ const delta = ctx.app.delta / 1000;
+ const movers = registry.getIndexedZipper([Position, Velocity, CollisionBox]);
+
+ for (const {
+ id: entityId,
+ Position: position,
+ Velocity: velocity,
+ CollisionBox: box,
+ } of movers) {
+ for (const obstacle of obstacles) {
+ if (
+ !overlapsBox(position.x, position.y, box, obstacle.Position.x, obstacle.Position.y, obstacle.CollisionBox)
+ )
+ continue;
+
+ resolveCollision(ctx, entityId, position, velocity, delta, (x, y) =>
+ overlapsBox(x, y, box, obstacle.Position.x, obstacle.Position.y, obstacle.CollisionBox),
+ );
+ break;
+ }
+ }
+}
+
+// * Required to generate code
+export default obstacleCollisionSystem.name;
diff --git a/raid-survival/server/systems/packet-handler.system.ts b/raid-survival/server/systems/packet-handler.system.ts
new file mode 100644
index 0000000..ba2bdf5
--- /dev/null
+++ b/raid-survival/server/systems/packet-handler.system.ts
@@ -0,0 +1,39 @@
+import { type Registry } from "@nanoforge-dev/ecs-client";
+import { NetworkServerLibrary } from "@nanoforge-dev/network-server";
+import { Context } from "@nanoforge-dev/common";
+import { inputPacketHandler } from "./packet-handlers/input-packet.handler";
+import { clients } from "../main";
+import { joinLobbyPacketHandler } from "./packet-handlers/join-lobby-packet.handler";
+import { startGamePacketHandler } from "./packet-handlers/start-game-packet.handler";
+
+export type PacketHandler = (
+ client: number,
+ packet: unknown,
+ registry: Registry,
+ ctx: Context,
+) => unknown;
+
+export const packetHandlers: Map = new Map([
+ ["input", inputPacketHandler],
+ ["joinLobby", joinLobbyPacketHandler],
+ ["startGame", startGamePacketHandler],
+]);
+
+export function packetHandler(registry: Registry, ctx: Context) {
+ const network = ctx.libs.getNetwork();
+ const connectedClients = network.tcp.getConnectedClients();
+ clients.forEach((client) => {
+ if (connectedClients.findIndex((id) => id === client.clientId) === -1) {
+ client.connected = false;
+ }
+ });
+ network.tcp.getReceivedPackets().forEach((packets, client) => {
+ const jsonPackets = packets.map((packet: AllowSharedBufferSource | undefined) => {
+ return JSON.parse(new TextDecoder().decode(packet));
+ });
+ if (!jsonPackets || jsonPackets.length === 0) return;
+ jsonPackets.forEach((packet) => {
+ packetHandlers.get(packet.type)?.(client, packet, registry, ctx);
+ });
+ });
+}
diff --git a/raid-survival/server/systems/packet-handlers/input-packet.handler.ts b/raid-survival/server/systems/packet-handlers/input-packet.handler.ts
new file mode 100644
index 0000000..10c6584
--- /dev/null
+++ b/raid-survival/server/systems/packet-handlers/input-packet.handler.ts
@@ -0,0 +1,55 @@
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { Velocity } from "../../components/velocity.component";
+import { Position } from "../../components/position.component";
+import { clients, PLAYER_SPEED } from "../../main";
+import { Context } from "@nanoforge-dev/common";
+import { NetworkServerLibrary } from "@nanoforge-dev/network-server";
+import { sendToInGamePlayers } from "../../network-utils";
+import { Direction } from "../../components/direction.component";
+import { Login } from "../../components/login.component";
+
+export function inputPacketHandler(
+ clientId: number,
+ packet: any,
+ registry: Registry,
+ ctx: Context,
+): void {
+ const network = ctx.libs.getNetwork();
+ const zipper = registry.getIndexedZipper([Login, Velocity, Position, Direction]);
+ const log = clients.find((client) => client.clientId === clientId)?.username;
+ const it = zipper.find(({ Login }) => {
+ return Login.id === log;
+ });
+ if (!it) return;
+
+ if (packet.direction) {
+ it.Direction.x = packet.direction.x;
+ it.Direction.y = packet.direction.y;
+ sendToInGamePlayers(network, {
+ type: "direction",
+ id: it.id,
+ direction: { x: it.Direction.x, y: it.Direction.y },
+ });
+ }
+
+ if (packet.moveKeys) {
+ let dx = 0;
+ let dy = 0;
+
+ if (packet.moveKeys.findIndex((key: string) => key === "up") !== -1) dy -= 1;
+ if (packet.moveKeys.findIndex((key: string) => key === "down") !== -1) dy += 1;
+ if (packet.moveKeys.findIndex((key: string) => key === "left") !== -1) dx -= 1;
+ if (packet.moveKeys.findIndex((key: string) => key === "right") !== -1) dx += 1;
+
+ const len = Math.hypot(dx, dy) || 1;
+ it.Velocity.x = (dx / len) * PLAYER_SPEED;
+ it.Velocity.y = (dy / len) * PLAYER_SPEED;
+
+ sendToInGamePlayers(network, {
+ type: "move",
+ id: it.id,
+ velocity: { x: it.Velocity.x, y: it.Velocity.y },
+ position: { x: it.Position.x, y: it.Position.y },
+ });
+ }
+}
diff --git a/raid-survival/server/systems/packet-handlers/join-lobby-packet.handler.ts b/raid-survival/server/systems/packet-handlers/join-lobby-packet.handler.ts
new file mode 100644
index 0000000..a11628a
--- /dev/null
+++ b/raid-survival/server/systems/packet-handlers/join-lobby-packet.handler.ts
@@ -0,0 +1,83 @@
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { clients, gameStatus, GameStatusEnum } from "../../main";
+import { Context } from "@nanoforge-dev/common";
+import { NetworkServerLibrary } from "@nanoforge-dev/network-server";
+import { sendToInGamePlayers } from "../../network-utils";
+
+export function joinLobbyPacketHandler(
+ clientId: number,
+ packet: any,
+ _registry: Registry,
+ ctx: Context,
+): void {
+ const network = ctx.libs.getNetwork();
+
+ function sendJoinLobbyInfo() {
+ network.tcp.sendToClient(
+ clientId,
+ new TextEncoder().encode(
+ JSON.stringify({
+ type: "joinLobby",
+ result: "success",
+ players: clients.map((client) => {
+ return {
+ id: client.entityId,
+ username: client.username,
+ };
+ }),
+ }),
+ ),
+ );
+
+ sendToInGamePlayers(network, {
+ type: "lobbyInfo",
+ players: clients.map((client) => ({
+ id: client.entityId,
+ username: client.username,
+ })),
+ });
+ }
+
+ if (gameStatus.status === GameStatusEnum.InGame) {
+ if (
+ clients.find((client) => {
+ return client.username === packet.username;
+ }) === undefined
+ )
+ sendJoinLobbyInfo();
+ else {
+ network.tcp.sendToClient(
+ clientId,
+ new TextEncoder().encode(JSON.stringify({ type: "joinLobby", result: "in game" })),
+ );
+ }
+ return;
+ }
+
+ let connected = false;
+ for (const client of clients) {
+ if (client.username === packet.username) {
+ client.clientId = clientId;
+ connected = true;
+ break;
+ }
+ }
+
+ if (!connected && clients.length >= 4) {
+ network.tcp.sendToClient(
+ clientId,
+ new TextEncoder().encode(JSON.stringify({ type: "joinLobby", result: "full" })),
+ );
+ return;
+ }
+ if (!connected) {
+ const entityId = _registry.spawnEntity().getId();
+ clients.push({
+ username: packet.username,
+ clientId: clientId,
+ entityId: entityId,
+ connected: true,
+ });
+ sendJoinLobbyInfo();
+ }
+}
diff --git a/raid-survival/server/systems/packet-handlers/start-game-packet.handler.ts b/raid-survival/server/systems/packet-handlers/start-game-packet.handler.ts
new file mode 100644
index 0000000..19d8daf
--- /dev/null
+++ b/raid-survival/server/systems/packet-handlers/start-game-packet.handler.ts
@@ -0,0 +1,118 @@
+import { Registry } from "@nanoforge-dev/ecs-client";
+import { clients, gameStatus, GameStatusEnum } from "../../main";
+import { Context } from "@nanoforge-dev/common";
+import { NetworkServerLibrary } from "@nanoforge-dev/network-server";
+import { sendToInGamePlayers } from "../../network-utils";
+import { Position } from "../../components/position.component";
+import { Direction } from "../../components/direction.component";
+import { Velocity } from "../../components/velocity.component";
+import { Login } from "../../components/login.component";
+import { MapCollisions, TreeLocation } from "../../components/map-collisions.component";
+import { CollisionBox } from "../../components/collision-box.component";
+import { Lobby } from "../../components/lobby.component";
+import { Health } from "../../components/health.component";
+import { Vector2d } from "@nanoforge-dev/graphics-2d";
+import mapCollisionData from "../../static/map-collision.json";
+
+const MAX_PLAYERS = 4;
+
+const LOBBY_MAX_HEALTH = 500;
+const PLAYER_MAX_HEALTH = 100;
+
+const MAP_CENTER: Vector2d = {
+ x: (mapCollisionData.cols * mapCollisionData.tileSize) / 2,
+ y: (mapCollisionData.rows * mapCollisionData.tileSize) / 2,
+};
+
+const PLAYER_COLLISION_BOX: Vector2d = { x: 24, y: 24 };
+
+const LOBBY_SPRITE_SIZE: Vector2d = { x: 187, y: 143 };
+
+const tilesFor = (size: number) => Math.ceil(size / mapCollisionData.tileSize) * mapCollisionData.tileSize;
+const LOBBY_COLLISION_BOX: Vector2d = {
+ x: tilesFor(LOBBY_SPRITE_SIZE.x),
+ y: tilesFor(LOBBY_SPRITE_SIZE.y),
+};
+
+const LOBBY_POSITION: Vector2d = {
+ x: MAP_CENTER.x - LOBBY_COLLISION_BOX.x / 2,
+ y: MAP_CENTER.y - LOBBY_COLLISION_BOX.y / 2,
+};
+
+const PLAYER_SPAWN_CENTER_OFFSET =
+ LOBBY_COLLISION_BOX.x / 2 + PLAYER_COLLISION_BOX.x / 2 + mapCollisionData.tileSize;
+const PLAYER_SPAWN_CENTERS: Vector2d[] = [
+ { x: MAP_CENTER.x - PLAYER_SPAWN_CENTER_OFFSET, y: MAP_CENTER.y - PLAYER_SPAWN_CENTER_OFFSET },
+ { x: MAP_CENTER.x + PLAYER_SPAWN_CENTER_OFFSET, y: MAP_CENTER.y - PLAYER_SPAWN_CENTER_OFFSET },
+ { x: MAP_CENTER.x - PLAYER_SPAWN_CENTER_OFFSET, y: MAP_CENTER.y + PLAYER_SPAWN_CENTER_OFFSET },
+ { x: MAP_CENTER.x + PLAYER_SPAWN_CENTER_OFFSET, y: MAP_CENTER.y + PLAYER_SPAWN_CENTER_OFFSET },
+];
+const PLAYERS_SPAWNERS: Vector2d[] = PLAYER_SPAWN_CENTERS.map((center) => ({
+ x: center.x - PLAYER_COLLISION_BOX.x / 2,
+ y: center.y - PLAYER_COLLISION_BOX.y / 2,
+}));
+
+const mapTreeLocations: TreeLocation[] = mapCollisionData.collision.flatMap((row, y) =>
+ row.flatMap((blocked, x) => (blocked ? [{ x, y }] : [])),
+);
+
+export function startGamePacketHandler(
+ _clientId: number,
+ _packet: any,
+ registry: Registry,
+ ctx: Context,
+): void {
+ const network = ctx.libs.getNetwork();
+ if (gameStatus.status === GameStatusEnum.InGame) return;
+ gameStatus.status = GameStatusEnum.InGame;
+
+ const map = registry.spawnEntity();
+ registry.addComponent(
+ map,
+ new MapCollisions(mapCollisionData.tileSize, mapCollisionData.cols, mapCollisionData.rows, mapTreeLocations),
+ );
+
+ const lobby = registry.spawnEntity();
+ registry.addComponent(lobby, new Position(LOBBY_POSITION.x, LOBBY_POSITION.y));
+ registry.addComponent(lobby, new CollisionBox(LOBBY_COLLISION_BOX.x, LOBBY_COLLISION_BOX.y));
+ registry.addComponent(lobby, new Health(LOBBY_MAX_HEALTH, LOBBY_MAX_HEALTH));
+ registry.addComponent(lobby, new Lobby());
+
+ const playersInformation: {
+ id: number;
+ username: string;
+ position: Vector2d;
+ health: { current: number; max: number };
+ }[] = [];
+
+ clients.forEach((client, index) => {
+ if (index >= MAX_PLAYERS) return;
+ if (!PLAYERS_SPAWNERS[index]) return;
+
+ const player = registry.entityFromIndex(client.entityId);
+
+ client.entityId = player.getId();
+ registry.addComponent(player, new Direction(1, 0));
+ registry.addComponent(player, new Login(client.username));
+ registry.addComponent(player, new Position(PLAYERS_SPAWNERS[index].x, PLAYERS_SPAWNERS[index].y));
+ registry.addComponent(player, new Velocity(0, 0));
+ registry.addComponent(player, new CollisionBox(PLAYER_COLLISION_BOX.x, PLAYER_COLLISION_BOX.y));
+ registry.addComponent(player, new Health(PLAYER_MAX_HEALTH, PLAYER_MAX_HEALTH));
+ playersInformation.push({
+ id: client.entityId,
+ username: client.username,
+ position: PLAYERS_SPAWNERS[index],
+ health: { current: PLAYER_MAX_HEALTH, max: PLAYER_MAX_HEALTH },
+ });
+ });
+
+ sendToInGamePlayers(network, {
+ type: "startGame",
+ players: playersInformation,
+ lobby: {
+ id: lobby.getId(),
+ position: LOBBY_POSITION,
+ health: { current: LOBBY_MAX_HEALTH, max: LOBBY_MAX_HEALTH },
+ },
+ });
+}
diff --git a/raid-survival/tsconfig.json b/raid-survival/tsconfig.json
new file mode 100644
index 0000000..79e653f
--- /dev/null
+++ b/raid-survival/tsconfig.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig.json",
+ // Mapped from https://www.typescriptlang.org/tsconfig
+ "compilerOptions": {
+ // Type Checking
+ "allowUnreachableCode": false,
+ "allowUnusedLabels": false,
+ "exactOptionalPropertyTypes": true,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitOverride": true,
+ "noImplicitReturns": true,
+ "noPropertyAccessFromIndexSignature": false,
+ "noUncheckedIndexedAccess": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "strict": true,
+
+ // Modules
+ "allowArbitraryExtensions": false,
+ "allowImportingTsExtensions": false,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "resolvePackageJsonExports": true,
+ "resolvePackageJsonImports": true,
+
+ // Interop Constraints
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": false,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": true,
+
+ // Language and Environment
+ "experimentalDecorators": true,
+ "lib": ["ESNext", "esnext.disposable", "DOM"],
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ },
+ "include": ["client/**/*.ts", "server/**/*.ts"],
+ "exclude": ["node_modules", ".nanoforge"]
+}