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"] +}