Further refactoring
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -23,4 +23,5 @@ dist-ssr
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
coverage
|
||||
coverage
|
||||
.eslintcache
|
||||
196
bun.lock
196
bun.lock
@@ -9,7 +9,11 @@
|
||||
"rot-js": "^2.2.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
||||
"@typescript-eslint/parser": "^8.52.0",
|
||||
"@vitest/coverage-v8": "^4.0.16",
|
||||
"eslint": "^9.39.2",
|
||||
"globals": "^17.0.0",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.2.4",
|
||||
"vitest": "^4.0.16",
|
||||
@@ -79,6 +83,32 @@
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||
|
||||
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
|
||||
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
|
||||
|
||||
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||
|
||||
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
|
||||
|
||||
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||
|
||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
@@ -137,6 +167,28 @@
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.52.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/type-utils": "8.52.0", "@typescript-eslint/utils": "8.52.0", "@typescript-eslint/visitor-keys": "8.52.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.52.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.52.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/types": "8.52.0", "@typescript-eslint/typescript-estree": "8.52.0", "@typescript-eslint/visitor-keys": "8.52.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg=="],
|
||||
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.52.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.52.0", "@typescript-eslint/types": "^8.52.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.52.0", "", { "dependencies": { "@typescript-eslint/types": "8.52.0", "@typescript-eslint/visitor-keys": "8.52.0" } }, "sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA=="],
|
||||
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.52.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.52.0", "", { "dependencies": { "@typescript-eslint/types": "8.52.0", "@typescript-eslint/typescript-estree": "8.52.0", "@typescript-eslint/utils": "8.52.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.52.0", "", {}, "sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.52.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.52.0", "@typescript-eslint/tsconfig-utils": "8.52.0", "@typescript-eslint/types": "8.52.0", "@typescript-eslint/visitor-keys": "8.52.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.52.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/types": "8.52.0", "@typescript-eslint/typescript-estree": "8.52.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.52.0", "", { "dependencies": { "@typescript-eslint/types": "8.52.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ=="],
|
||||
|
||||
"@vitest/coverage-v8": ["@vitest/coverage-v8@4.0.16", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.0.16", "ast-v8-to-istanbul": "^0.3.8", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.2.0", "magicast": "^0.5.1", "obug": "^2.1.1", "std-env": "^3.10.0", "tinyrainbow": "^3.0.3" }, "peerDependencies": { "@vitest/browser": "4.0.16", "vitest": "4.0.16" }, "optionalPeers": ["@vitest/browser"] }, "sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A=="],
|
||||
|
||||
"@vitest/expect": ["@vitest/expect@4.0.16", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.16", "@vitest/utils": "4.0.16", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA=="],
|
||||
@@ -153,32 +205,108 @@
|
||||
|
||||
"@vitest/utils": ["@vitest/utils@4.0.16", "", { "dependencies": { "@vitest/pretty-format": "4.0.16", "tinyrainbow": "^3.0.3" } }, "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||
|
||||
"ast-v8-to-istanbul": ["ast-v8-to-istanbul@0.3.10", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^9.0.1" } }, "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
||||
|
||||
"esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
||||
|
||||
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
|
||||
|
||||
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
||||
|
||||
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@17.0.0", "", {}, "sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
|
||||
|
||||
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
|
||||
|
||||
"istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="],
|
||||
@@ -189,18 +317,50 @@
|
||||
|
||||
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"magicast": ["magicast@0.5.1", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "source-map-js": "^1.2.1" } }, "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw=="],
|
||||
|
||||
"make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"phaser": ["phaser@3.90.0", "", { "dependencies": { "eventemitter3": "^5.0.1" } }, "sha512-/cziz/5ZIn02uDkC9RzN8VF9x3Gs3XdFFf9nkiMEQT3p7hQlWuyjy4QWosU802qqno2YSLn2BfqwOKLv/sSVfQ=="],
|
||||
@@ -211,12 +371,22 @@
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"rollup": ["rollup@4.54.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.54.0", "@rollup/rollup-android-arm64": "4.54.0", "@rollup/rollup-darwin-arm64": "4.54.0", "@rollup/rollup-darwin-x64": "4.54.0", "@rollup/rollup-freebsd-arm64": "4.54.0", "@rollup/rollup-freebsd-x64": "4.54.0", "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", "@rollup/rollup-linux-arm-musleabihf": "4.54.0", "@rollup/rollup-linux-arm64-gnu": "4.54.0", "@rollup/rollup-linux-arm64-musl": "4.54.0", "@rollup/rollup-linux-loong64-gnu": "4.54.0", "@rollup/rollup-linux-ppc64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-musl": "4.54.0", "@rollup/rollup-linux-s390x-gnu": "4.54.0", "@rollup/rollup-linux-x64-gnu": "4.54.0", "@rollup/rollup-linux-x64-musl": "4.54.0", "@rollup/rollup-openharmony-arm64": "4.54.0", "@rollup/rollup-win32-arm64-msvc": "4.54.0", "@rollup/rollup-win32-ia32-msvc": "4.54.0", "@rollup/rollup-win32-x64-gnu": "4.54.0", "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw=="],
|
||||
|
||||
"rot-js": ["rot-js@2.2.1", "", {}, "sha512-lItXH31vj4ebdypayCx9dh98qPr57E7jGW2lVMKxtBHooU3xpGRtLS8kdJIni232tvPJ8Sl0+aqXZj8c6W0MGw=="],
|
||||
|
||||
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
@@ -225,6 +395,8 @@
|
||||
|
||||
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
||||
@@ -235,12 +407,36 @@
|
||||
|
||||
"tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"vite": ["vite@7.3.0", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg=="],
|
||||
|
||||
"vitest": ["vitest@4.0.16", "", { "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", "@vitest/pretty-format": "4.0.16", "@vitest/runner": "4.0.16", "@vitest/snapshot": "4.0.16", "@vitest/spy": "4.0.16", "@vitest/utils": "4.0.16", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.16", "@vitest/browser-preview": "4.0.16", "@vitest/browser-webdriverio": "4.0.16", "@vitest/ui": "4.0.16", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||
|
||||
"@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
}
|
||||
}
|
||||
|
||||
54
eslint.config.js
Normal file
54
eslint.config.js
Normal file
@@ -0,0 +1,54 @@
|
||||
// @ts-check
|
||||
import tseslint from "@typescript-eslint/eslint-plugin";
|
||||
import tsparser from "@typescript-eslint/parser";
|
||||
|
||||
export default [
|
||||
{
|
||||
files: ["src/**/*.ts", "src/**/*.tsx"],
|
||||
languageOptions: {
|
||||
parser: tsparser,
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": tseslint,
|
||||
},
|
||||
rules: {
|
||||
// TypeScript recommended rules (subset)
|
||||
"@typescript-eslint/no-explicit-any": "warn", // Warn, not error for gradual migration
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||
|
||||
// General code quality
|
||||
"no-console": "off", // Game dev needs console
|
||||
"prefer-const": "error",
|
||||
"no-var": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["src/**/*.test.ts", "src/**/__tests__/**/*.ts"],
|
||||
rules: {
|
||||
// More lenient for tests
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"node_modules/**",
|
||||
"dist/**",
|
||||
"coverage/**",
|
||||
"*.config.js",
|
||||
"*.config.ts",
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -10,10 +10,16 @@
|
||||
"test": "vitest",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"check": "tsc --noEmit",
|
||||
"lint": "eslint src --ext .ts,.tsx",
|
||||
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
||||
"verify": "bun run check && bun run test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
||||
"@typescript-eslint/parser": "^8.52.0",
|
||||
"@vitest/coverage-v8": "^4.0.16",
|
||||
"eslint": "^9.39.2",
|
||||
"globals": "^17.0.0",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.2.4",
|
||||
"vitest": "^4.0.16"
|
||||
|
||||
@@ -121,7 +121,11 @@ export const GAME_CONFIG = {
|
||||
minimapPanelHeight: 220,
|
||||
minimapPadding: 20,
|
||||
menuPanelWidth: 340,
|
||||
menuPanelHeight: 220
|
||||
menuPanelHeight: 220,
|
||||
// Targeting
|
||||
targetingLineColor: 0xff0000,
|
||||
targetingLineWidth: 2,
|
||||
targetingLineAlpha: 0.7
|
||||
},
|
||||
|
||||
gameplay: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FOV } from "rot-js";
|
||||
import type ROT from "rot-js";
|
||||
import { type World, type EntityId } from "../core/types";
|
||||
import { idx, inBounds } from "../engine/world/world-logic";
|
||||
import { blocksSight } from "../core/terrain";
|
||||
@@ -6,7 +7,7 @@ import { GAME_CONFIG } from "../core/config/GameConfig";
|
||||
import Phaser from "phaser";
|
||||
|
||||
export class FovManager {
|
||||
private fov!: any;
|
||||
private fov!: InstanceType<typeof ROT.FOV.PreciseShadowcasting>;
|
||||
private seen!: Uint8Array;
|
||||
private visible!: Uint8Array;
|
||||
private visibleStrength!: Float32Array;
|
||||
@@ -51,12 +52,12 @@ export class FovManager {
|
||||
}
|
||||
|
||||
isSeen(x: number, y: number): boolean {
|
||||
if (!inBounds({ width: this.worldWidth, height: this.worldHeight } as any, x, y)) return false;
|
||||
if (x < 0 || x >= this.worldWidth || y < 0 || y >= this.worldHeight) return false;
|
||||
return this.seen[y * this.worldWidth + x] === 1;
|
||||
}
|
||||
|
||||
isVisible(x: number, y: number): boolean {
|
||||
if (!inBounds({ width: this.worldWidth, height: this.worldHeight } as any, x, y)) return false;
|
||||
if (x < 0 || x >= this.worldWidth || y < 0 || y >= this.worldHeight) return false;
|
||||
return this.visible[y * this.worldWidth + x] === 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
type RunState,
|
||||
type World,
|
||||
type CombatantActor,
|
||||
type Item,
|
||||
type ItemDropActor,
|
||||
type UIUpdatePayload
|
||||
} from "../core/types";
|
||||
import { TILE_SIZE } from "../core/constants";
|
||||
@@ -16,14 +14,14 @@ import { inBounds, isBlocked, isPlayerOnExit, tryDestructTile } from "../engine/
|
||||
import { findPathAStar } from "../engine/world/pathfinding";
|
||||
import { applyAction, stepUntilPlayerTurn } from "../engine/simulation/simulation";
|
||||
import { generateWorld } from "../engine/world/generator";
|
||||
import { traceProjectile, getClosestVisibleEnemy } from "../engine/gameplay/CombatLogic";
|
||||
|
||||
|
||||
|
||||
import { DungeonRenderer } from "../rendering/DungeonRenderer";
|
||||
import { GAME_CONFIG } from "../core/config/GameConfig";
|
||||
import { EntityManager } from "../engine/EntityManager";
|
||||
import { ProgressionManager } from "../engine/ProgressionManager";
|
||||
import GameUI from "../ui/GameUI";
|
||||
import { CameraController } from "./systems/CameraController";
|
||||
import { ItemManager } from "./systems/ItemManager";
|
||||
import { TargetingSystem } from "./systems/TargetingSystem";
|
||||
|
||||
export class GameScene extends Phaser.Scene {
|
||||
private world!: World;
|
||||
@@ -40,22 +38,18 @@ export class GameScene extends Phaser.Scene {
|
||||
|
||||
private playerPath: Vec2[] = [];
|
||||
private awaitingPlayer = false;
|
||||
private followPlayer = true;
|
||||
|
||||
// Sub-systems
|
||||
private dungeonRenderer!: DungeonRenderer;
|
||||
private cameraController!: CameraController;
|
||||
private itemManager!: ItemManager;
|
||||
private isMenuOpen = false;
|
||||
private isInventoryOpen = false;
|
||||
private isCharacterOpen = false;
|
||||
|
||||
private entityManager!: EntityManager;
|
||||
private progressionManager: ProgressionManager = new ProgressionManager();
|
||||
|
||||
// Targeting Mode
|
||||
private isTargeting = false;
|
||||
private targetingItem: string | null = null;
|
||||
private targetCursor: { x: number, y: number } | null = null;
|
||||
private targetingGraphics!: Phaser.GameObjects.Graphics;
|
||||
private targetingSystem!: TargetingSystem;
|
||||
|
||||
private turnCount = 0; // Track turns for mana regen
|
||||
|
||||
@@ -72,7 +66,10 @@ export class GameScene extends Phaser.Scene {
|
||||
|
||||
// Initialize Sub-systems
|
||||
this.dungeonRenderer = new DungeonRenderer(this);
|
||||
this.targetingGraphics = this.add.graphics().setDepth(2000);
|
||||
this.cameraController = new CameraController(this.cameras.main);
|
||||
this.itemManager = new ItemManager(this.world, this.entityManager);
|
||||
const targetingGraphics = this.add.graphics().setDepth(2000);
|
||||
this.targetingSystem = new TargetingSystem(targetingGraphics);
|
||||
|
||||
// Launch UI Scene
|
||||
this.scene.launch("GameUI");
|
||||
@@ -177,82 +174,48 @@ export class GameScene extends Phaser.Scene {
|
||||
if (itemIdx === -1) return;
|
||||
const item = player.inventory.items[itemIdx];
|
||||
|
||||
if (item.stats && item.stats.hp && item.stats.hp > 0) {
|
||||
const healAmount = item.stats.hp;
|
||||
if (player.stats.hp < player.stats.maxHp) {
|
||||
player.stats.hp = Math.min(player.stats.hp + healAmount, player.stats.maxHp);
|
||||
|
||||
// Remove item after use
|
||||
player.inventory.items.splice(itemIdx, 1);
|
||||
|
||||
this.dungeonRenderer.showHeal(player.pos.x, player.pos.y, healAmount);
|
||||
this.commitPlayerAction({ type: "wait" });
|
||||
this.emitUIUpdate();
|
||||
}
|
||||
} else if (item.throwable) {
|
||||
// Check if already targeting this item -> verify intent to throw
|
||||
if (this.isTargeting && this.targetingItem === item.id) {
|
||||
if (this.targetCursor) {
|
||||
this.executeThrow(this.targetCursor.x, this.targetCursor.y);
|
||||
const result = this.itemManager.handleUse(data.itemId, player);
|
||||
|
||||
if (result.success && result.consumed) {
|
||||
const healAmount = player.stats.maxHp - player.stats.hp; // Already healed by manager
|
||||
const actualHeal = Math.min(healAmount, player.stats.hp);
|
||||
this.dungeonRenderer.showHeal(player.pos.x, player.pos.y, actualHeal);
|
||||
this.commitPlayerAction({ type: "wait" });
|
||||
this.emitUIUpdate();
|
||||
} else if (result.success && !result.consumed) {
|
||||
// Throwable item - start targeting
|
||||
if (this.targetingSystem.isActive && this.targetingSystem.itemId === item.id) {
|
||||
// Already targeting - execute throw
|
||||
if (this.targetingSystem.cursorPos) {
|
||||
this.executeThrow(this.targetingSystem.cursorPos.x, this.targetingSystem.cursorPos.y);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.targetingItem = item.id;
|
||||
this.isTargeting = true;
|
||||
|
||||
// Auto-target closest visible enemy
|
||||
const closest = getClosestVisibleEnemy(
|
||||
this.world,
|
||||
player.pos,
|
||||
this.dungeonRenderer.seenArray,
|
||||
this.targetingSystem.startTargeting(
|
||||
item.id,
|
||||
player.pos,
|
||||
this.world,
|
||||
this.dungeonRenderer.seenArray,
|
||||
this.world.width
|
||||
);
|
||||
|
||||
if (closest) {
|
||||
this.targetCursor = closest;
|
||||
} else {
|
||||
// Default to player pos or null?
|
||||
// If we default to mouse pos, we need current mouse pos.
|
||||
// Let's default to null and wait for mouse move, OR default to player pos forward?
|
||||
// Let's just default to null until mouse moves.
|
||||
this.targetCursor = null;
|
||||
}
|
||||
|
||||
this.drawTargetingLine();
|
||||
console.log("Targeting Mode: ON");
|
||||
this.emitUIUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
// Right Clicks to cancel targeting
|
||||
this.input.on('pointerdown', (p: Phaser.Input.Pointer) => {
|
||||
if (p.rightButtonDown() && this.isTargeting) {
|
||||
this.cancelTargeting();
|
||||
if (p.rightButtonDown() && this.targetingSystem.isActive) {
|
||||
this.targetingSystem.cancel();
|
||||
this.emitUIUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
// Zoom Control
|
||||
this.input.on(
|
||||
"wheel",
|
||||
(
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_gameObjects: any,
|
||||
_deltaX: number,
|
||||
deltaY: number,
|
||||
_deltaZ: number
|
||||
) => {
|
||||
this.input.on("wheel", (_pointer: Phaser.Input.Pointer, _gameObjects: Phaser.GameObjects.GameObject[], _deltaX: number, deltaY: number, _deltaZ: number) => {
|
||||
if (this.isMenuOpen || this.isInventoryOpen || this.dungeonRenderer.isMinimapVisible()) return;
|
||||
|
||||
const zoomDir = deltaY > 0 ? -1 : 1;
|
||||
const newZoom = Phaser.Math.Clamp(
|
||||
this.cameras.main.zoom + zoomDir * GAME_CONFIG.rendering.zoomStep,
|
||||
GAME_CONFIG.rendering.minZoom,
|
||||
GAME_CONFIG.rendering.maxZoom
|
||||
);
|
||||
this.cameras.main.setZoom(newZoom);
|
||||
}
|
||||
);
|
||||
this.cameraController.handleWheel(deltaY);
|
||||
});
|
||||
|
||||
// Disable context menu for right-click panning
|
||||
this.input.mouse?.disableContextMenu();
|
||||
@@ -260,12 +223,13 @@ export class GameScene extends Phaser.Scene {
|
||||
// Camera Panning
|
||||
this.input.on("pointermove", (p: Phaser.Input.Pointer) => {
|
||||
if (!p.isDown) { // Even if not down, we might need to update targeting line
|
||||
if (this.isTargeting) {
|
||||
if (this.targetingSystem.isActive) {
|
||||
const tx = Math.floor(p.worldX / TILE_SIZE);
|
||||
const ty = Math.floor(p.worldY / TILE_SIZE);
|
||||
// Only update if changed to avoid jitter if needed, but simple assignment is fine
|
||||
this.targetCursor = { x: tx, y: ty };
|
||||
this.drawTargetingLine();
|
||||
const player = this.world.actors.get(this.playerId) as CombatantActor;
|
||||
if (player) {
|
||||
this.targetingSystem.updateCursor({ x: tx, y: ty }, player.pos);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -283,28 +247,27 @@ export class GameScene extends Phaser.Scene {
|
||||
const dx = (x - prevX) / this.cameras.main.zoom;
|
||||
const dy = (y - prevY) / this.cameras.main.zoom;
|
||||
|
||||
this.cameras.main.scrollX -= dx;
|
||||
this.cameras.main.scrollY -= dy;
|
||||
|
||||
this.followPlayer = false;
|
||||
this.cameraController.handlePan(dx, dy);
|
||||
}
|
||||
|
||||
if (this.isTargeting) {
|
||||
if (this.targetingSystem.isActive) {
|
||||
const tx = Math.floor(p.worldX / TILE_SIZE);
|
||||
const ty = Math.floor(p.worldY / TILE_SIZE);
|
||||
this.targetCursor = { x: tx, y: ty };
|
||||
this.drawTargetingLine();
|
||||
const player = this.world.actors.get(this.playerId) as CombatantActor;
|
||||
if (player) {
|
||||
this.targetingSystem.updateCursor({ x: tx, y: ty }, player.pos);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Mouse click ->
|
||||
this.input.on("pointerdown", (p: Phaser.Input.Pointer) => {
|
||||
// Targeting Click
|
||||
if (this.isTargeting) {
|
||||
if (this.targetingSystem.isActive) {
|
||||
// Only Left Click throws
|
||||
if (p.button === 0) {
|
||||
if (this.targetCursor) {
|
||||
this.executeThrow(this.targetCursor.x, this.targetCursor.y);
|
||||
if (this.targetingSystem.cursorPos) {
|
||||
this.executeThrow(this.targetingSystem.cursorPos.x, this.targetingSystem.cursorPos.y);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -313,7 +276,7 @@ export class GameScene extends Phaser.Scene {
|
||||
// Movement Click
|
||||
if (p.button !== 0) return;
|
||||
|
||||
this.followPlayer = true;
|
||||
this.cameraController.enableFollowMode();
|
||||
|
||||
if (!this.awaitingPlayer) return;
|
||||
if (this.isMenuOpen || this.isInventoryOpen || this.dungeonRenderer.isMinimapVisible()) return;
|
||||
@@ -410,8 +373,9 @@ export class GameScene extends Phaser.Scene {
|
||||
if (this.cursors.down!.isDown) dy += 1;
|
||||
|
||||
if (dx !== 0 || dy !== 0) {
|
||||
if (this.isTargeting) {
|
||||
this.cancelTargeting();
|
||||
if (this.targetingSystem.isActive) {
|
||||
this.targetingSystem.cancel();
|
||||
this.emitUIUpdate();
|
||||
}
|
||||
const player = this.world.actors.get(this.playerId) as CombatantActor;
|
||||
const targetX = player.pos.x + dx;
|
||||
@@ -443,7 +407,7 @@ export class GameScene extends Phaser.Scene {
|
||||
playerId: this.playerId,
|
||||
floorIndex: this.floorIndex,
|
||||
uiState: {
|
||||
targetingItemId: this.targetingItem
|
||||
targetingItemId: this.targetingSystem.itemId
|
||||
}
|
||||
};
|
||||
this.events.emit("update-ui", payload);
|
||||
@@ -457,11 +421,15 @@ export class GameScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
this.awaitingPlayer = false;
|
||||
this.followPlayer = true;
|
||||
this.cameraController.enableFollowMode();
|
||||
|
||||
// Check for pickups right after move (before enemy turn, so you get it efficiently)
|
||||
if (action.type === "move") {
|
||||
this.tryPickupItem();
|
||||
const player = this.world.actors.get(this.playerId) as CombatantActor;
|
||||
const pickedItem = this.itemManager.tryPickup(player);
|
||||
if (pickedItem) {
|
||||
this.emitUIUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
const enemyStep = stepUntilPlayerTurn(this.world, this.playerId, this.entityManager);
|
||||
@@ -509,8 +477,8 @@ export class GameScene extends Phaser.Scene {
|
||||
|
||||
if (!this.world.actors.has(this.playerId)) {
|
||||
this.syncRunStateFromPlayer();
|
||||
const uiScene = this.scene.get("GameUI") as any;
|
||||
if (uiScene) {
|
||||
const uiScene = this.scene.get("GameUI") as GameUI;
|
||||
if (uiScene && 'showDeathScreen' in uiScene) {
|
||||
uiScene.showDeathScreen({
|
||||
floor: this.floorIndex,
|
||||
gold: this.runState.inventory.gold,
|
||||
@@ -528,8 +496,9 @@ export class GameScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
this.dungeonRenderer.computeFov(this.playerId);
|
||||
if (this.followPlayer) {
|
||||
this.centerCameraOnPlayer();
|
||||
if (this.cameraController.isFollowing) {
|
||||
const player = this.world.actors.get(this.playerId) as CombatantActor;
|
||||
this.cameraController.centerOnTile(player.pos.x, player.pos.y);
|
||||
}
|
||||
this.dungeonRenderer.render(this.playerPath);
|
||||
this.emitUIUpdate();
|
||||
@@ -537,18 +506,19 @@ export class GameScene extends Phaser.Scene {
|
||||
|
||||
private loadFloor(floor: number) {
|
||||
this.floorIndex = floor;
|
||||
this.followPlayer = true;
|
||||
this.cameraController.enableFollowMode();
|
||||
|
||||
const { world, playerId } = generateWorld(floor, this.runState);
|
||||
this.world = world;
|
||||
this.playerId = playerId;
|
||||
this.entityManager = new EntityManager(this.world);
|
||||
this.itemManager.updateWorld(this.world, this.entityManager);
|
||||
|
||||
|
||||
this.playerPath = [];
|
||||
this.awaitingPlayer = false;
|
||||
|
||||
this.cameras.main.setBounds(0, 0, this.world.width * TILE_SIZE, this.world.height * TILE_SIZE);
|
||||
this.cameraController.setBounds(0, 0, this.world.width * TILE_SIZE, this.world.height * TILE_SIZE);
|
||||
|
||||
this.dungeonRenderer.initializeFloor(this.world, this.playerId);
|
||||
|
||||
@@ -557,7 +527,8 @@ export class GameScene extends Phaser.Scene {
|
||||
|
||||
|
||||
this.dungeonRenderer.computeFov(this.playerId);
|
||||
this.centerCameraOnPlayer();
|
||||
const player = this.world.actors.get(this.playerId) as CombatantActor;
|
||||
this.cameraController.centerOnTile(player.pos.x, player.pos.y);
|
||||
this.dungeonRenderer.render(this.playerPath);
|
||||
this.emitUIUpdate();
|
||||
|
||||
@@ -584,139 +555,49 @@ export class GameScene extends Phaser.Scene {
|
||||
this.loadFloor(this.floorIndex);
|
||||
}
|
||||
|
||||
private centerCameraOnPlayer() {
|
||||
const player = this.world.actors.get(this.playerId) as CombatantActor;
|
||||
this.cameras.main.centerOn(
|
||||
player.pos.x * TILE_SIZE + TILE_SIZE / 2,
|
||||
player.pos.y * TILE_SIZE + TILE_SIZE / 2
|
||||
);
|
||||
}
|
||||
|
||||
private drawTargetingLine() {
|
||||
if (!this.world || !this.targetCursor) {
|
||||
this.targetingGraphics.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
this.targetingGraphics.clear();
|
||||
|
||||
const player = this.world.actors.get(this.playerId) as CombatantActor;
|
||||
if (!player) return;
|
||||
|
||||
const startX = player.pos.x * TILE_SIZE + TILE_SIZE / 2;
|
||||
const startY = player.pos.y * TILE_SIZE + TILE_SIZE / 2;
|
||||
|
||||
const endX = this.targetCursor.x * TILE_SIZE + TILE_SIZE / 2;
|
||||
const endY = this.targetCursor.y * TILE_SIZE + TILE_SIZE / 2;
|
||||
|
||||
this.targetingGraphics.lineStyle(2, 0xff0000, 0.7);
|
||||
this.targetingGraphics.lineBetween(startX, startY, endX, endY);
|
||||
|
||||
this.targetingGraphics.strokeRect(this.targetCursor.x * TILE_SIZE, this.targetCursor.y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
|
||||
}
|
||||
|
||||
private cancelTargeting() {
|
||||
this.isTargeting = false;
|
||||
this.targetingItem = null;
|
||||
this.targetCursor = null;
|
||||
this.targetingGraphics.clear();
|
||||
console.log("Targeting cancelled");
|
||||
this.emitUIUpdate();
|
||||
}
|
||||
|
||||
private executeThrow(targetX: number, targetY: number) {
|
||||
const player = this.world.actors.get(this.playerId) as CombatantActor;
|
||||
if (!player) return;
|
||||
|
||||
const itemArg = this.targetingItem;
|
||||
if (!itemArg) return;
|
||||
|
||||
const itemIdx = player.inventory!.items.findIndex(it => it.id === itemArg);
|
||||
if (itemIdx === -1) {
|
||||
console.log("Item not found!");
|
||||
this.cancelTargeting();
|
||||
return;
|
||||
}
|
||||
|
||||
const item = player.inventory!.items[itemIdx];
|
||||
player.inventory!.items.splice(itemIdx, 1);
|
||||
|
||||
const start = player.pos;
|
||||
const end = { x: targetX, y: targetY };
|
||||
|
||||
const result = traceProjectile(this.world, start, end, this.entityManager, this.playerId);
|
||||
const { blockedPos, hitActorId } = result;
|
||||
|
||||
this.dungeonRenderer.showProjectile(
|
||||
start,
|
||||
blockedPos,
|
||||
item.id,
|
||||
() => {
|
||||
private executeThrow(_targetX: number, _targetY: number) {
|
||||
const success = this.targetingSystem.executeThrow(
|
||||
this.world,
|
||||
this.playerId,
|
||||
this.entityManager,
|
||||
(blockedPos, hitActorId, item) => {
|
||||
if (hitActorId !== undefined) {
|
||||
const victim = this.world.actors.get(hitActorId) as CombatantActor;
|
||||
if (victim) {
|
||||
const dmg = item.stats?.attack ?? 1; // Use item stats
|
||||
const dmg = item.stats?.attack ?? 1;
|
||||
victim.stats.hp -= dmg;
|
||||
this.dungeonRenderer.showDamage(victim.pos.x, victim.pos.y, dmg);
|
||||
this.dungeonRenderer.shakeCamera();
|
||||
|
||||
if (victim.stats.hp <= 0) {
|
||||
// Force kill handled by simulation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the actual item at the landing spot
|
||||
this.spawnItem(item, blockedPos.x, blockedPos.y);
|
||||
const player = this.world.actors.get(this.playerId) as CombatantActor;
|
||||
this.dungeonRenderer.showProjectile(
|
||||
player.pos,
|
||||
blockedPos,
|
||||
item.id,
|
||||
() => {
|
||||
// Drop the actual item at the landing spot
|
||||
this.itemManager.spawnItem(item, blockedPos);
|
||||
|
||||
// "Count as walking over the tile" -> Trigger destruction/interaction
|
||||
// e.g. breaking grass, opening items
|
||||
if (tryDestructTile(this.world, blockedPos.x, blockedPos.y)) {
|
||||
this.dungeonRenderer.updateTile(blockedPos.x, blockedPos.y);
|
||||
}
|
||||
// Trigger destruction/interaction
|
||||
if (tryDestructTile(this.world, blockedPos.x, blockedPos.y)) {
|
||||
this.dungeonRenderer.updateTile(blockedPos.x, blockedPos.y);
|
||||
}
|
||||
|
||||
this.cancelTargeting();
|
||||
this.commitPlayerAction({ type: "throw" });
|
||||
this.emitUIUpdate();
|
||||
this.targetingSystem.cancel();
|
||||
this.commitPlayerAction({ type: "throw" });
|
||||
this.emitUIUpdate();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private spawnItem(item: Item, x: number, y: number) {
|
||||
if (!this.world || !this.entityManager) return;
|
||||
|
||||
const id = this.entityManager.getNextId();
|
||||
const drop: ItemDropActor = {
|
||||
id,
|
||||
pos: { x, y },
|
||||
category: "item_drop",
|
||||
item: { ...item } // Clone item
|
||||
};
|
||||
|
||||
this.entityManager.addActor(drop);
|
||||
// Ensure renderer knows? Renderer iterates world.actors, so it should pick it up if we handle "item_drop"
|
||||
}
|
||||
|
||||
private tryPickupItem() {
|
||||
const player = this.world.actors.get(this.playerId) as CombatantActor;
|
||||
if (!player) return;
|
||||
|
||||
const actors = this.entityManager.getActorsAt(player.pos.x, player.pos.y);
|
||||
const itemActor = actors.find(a => (a as any).category === "item_drop"); // Safe check
|
||||
|
||||
if (itemActor) {
|
||||
const drop = itemActor as any; // Cast to ItemDropActor
|
||||
const item = drop.item;
|
||||
|
||||
// Add to inventory
|
||||
player.inventory!.items.push(item);
|
||||
|
||||
// Remove from world
|
||||
this.entityManager.removeActor(drop.id);
|
||||
|
||||
console.log("Picked up:", item.name);
|
||||
// Show FX?
|
||||
// this.dungeonRenderer.showPickup(player.pos.x, player.pos.y); -> need to implement
|
||||
if (!success) {
|
||||
this.emitUIUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
85
src/scenes/systems/CameraController.ts
Normal file
85
src/scenes/systems/CameraController.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import Phaser from "phaser";
|
||||
import { TILE_SIZE } from "../../core/constants";
|
||||
import { GAME_CONFIG } from "../../core/config/GameConfig";
|
||||
|
||||
/**
|
||||
* Manages camera controls including zoom, panning, and follow mode.
|
||||
* Extracted from GameScene to reduce complexity and improve testability.
|
||||
*/
|
||||
export class CameraController {
|
||||
private camera: Phaser.Cameras.Scene2D.Camera;
|
||||
private followMode: boolean = true;
|
||||
|
||||
constructor(camera: Phaser.Cameras.Scene2D.Camera) {
|
||||
this.camera = camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable follow mode - camera will track the target entity
|
||||
*/
|
||||
enableFollowMode(): void {
|
||||
this.followMode = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable follow mode - camera stays at current position
|
||||
*/
|
||||
disableFollowMode(): void {
|
||||
this.followMode = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if camera is in follow mode
|
||||
*/
|
||||
get isFollowing(): boolean {
|
||||
return this.followMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Center camera on a specific world position (in pixels)
|
||||
*/
|
||||
centerOn(worldX: number, worldY: number): void {
|
||||
this.camera.centerOn(worldX, worldY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Center camera on a tile position
|
||||
*/
|
||||
centerOnTile(tileX: number, tileY: number): void {
|
||||
const worldX = tileX * TILE_SIZE + TILE_SIZE / 2;
|
||||
const worldY = tileY * TILE_SIZE + TILE_SIZE / 2;
|
||||
this.camera.centerOn(worldX, worldY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mouse wheel zoom
|
||||
* @param deltaY - Wheel delta (positive = zoom out, negative = zoom in)
|
||||
*/
|
||||
handleWheel(deltaY: number): void {
|
||||
const zoomDir = deltaY > 0 ? -1 : 1;
|
||||
const newZoom = Phaser.Math.Clamp(
|
||||
this.camera.zoom + zoomDir * GAME_CONFIG.rendering.zoomStep,
|
||||
GAME_CONFIG.rendering.minZoom,
|
||||
GAME_CONFIG.rendering.maxZoom
|
||||
);
|
||||
this.camera.setZoom(newZoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle camera panning via drag
|
||||
* @param dx - Change in x position
|
||||
* @param dy - Change in y position
|
||||
*/
|
||||
handlePan(dx: number, dy: number): void {
|
||||
this.camera.scrollX -= dx;
|
||||
this.camera.scrollY -= dy;
|
||||
this.disableFollowMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set camera bounds (usually to match world size)
|
||||
*/
|
||||
setBounds(x: number, y: number, width: number, height: number): void {
|
||||
this.camera.setBounds(x, y, width, height);
|
||||
}
|
||||
}
|
||||
151
src/scenes/systems/EventBridge.ts
Normal file
151
src/scenes/systems/EventBridge.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import type { GameScene } from "../GameScene";
|
||||
import type { DungeonRenderer } from "../../rendering/DungeonRenderer";
|
||||
import type { CombatantActor } from "../../core/types";
|
||||
import type { ProgressionManager } from "../../engine/ProgressionManager";
|
||||
import type { ItemManager } from "./ItemManager";
|
||||
import type { TargetingSystem } from "./TargetingSystem";
|
||||
|
||||
/**
|
||||
* Centralizes all event handling between GameScene and UI.
|
||||
* Extracted from GameScene to reduce complexity and make event flow clearer.
|
||||
*/
|
||||
export class EventBridge {
|
||||
private scene: GameScene;
|
||||
|
||||
constructor(scene: GameScene) {
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up all event listeners
|
||||
*/
|
||||
setupListeners(
|
||||
dungeonRenderer: DungeonRenderer,
|
||||
progressionManager: ProgressionManager,
|
||||
itemManager: ItemManager,
|
||||
targetingSystem: TargetingSystem,
|
||||
awaitingPlayerFn: () => boolean,
|
||||
commitActionFn: (action: any) => void,
|
||||
emitUIUpdateFn: () => void,
|
||||
restartGameFn: () => void,
|
||||
executeThrowFn: (x: number, y: number) => void
|
||||
): void {
|
||||
// Menu state listeners (from UI)
|
||||
this.scene.events.on("menu-toggled", (isOpen: boolean) => {
|
||||
(this.scene as any).isMenuOpen = isOpen;
|
||||
});
|
||||
|
||||
this.scene.events.on("inventory-toggled", (isOpen: boolean) => {
|
||||
(this.scene as any).isInventoryOpen = isOpen;
|
||||
});
|
||||
|
||||
this.scene.events.on("character-toggled", (isOpen: boolean) => {
|
||||
(this.scene as any).isCharacterOpen = isOpen;
|
||||
});
|
||||
|
||||
// Minimap toggle
|
||||
this.scene.events.on("toggle-minimap", () => {
|
||||
dungeonRenderer.toggleMinimap();
|
||||
});
|
||||
|
||||
// UI update requests
|
||||
this.scene.events.on("request-ui-update", () => {
|
||||
emitUIUpdateFn();
|
||||
});
|
||||
|
||||
// Game restart
|
||||
this.scene.events.on("restart-game", () => {
|
||||
restartGameFn();
|
||||
});
|
||||
|
||||
// Stat allocation
|
||||
this.scene.events.on("allocate-stat", (statName: string) => {
|
||||
const player = (this.scene as any).world.actors.get((this.scene as any).playerId) as CombatantActor;
|
||||
if (player) {
|
||||
progressionManager.allocateStat(player, statName);
|
||||
emitUIUpdateFn();
|
||||
}
|
||||
});
|
||||
|
||||
// Passive allocation
|
||||
this.scene.events.on("allocate-passive", (nodeId: string) => {
|
||||
const player = (this.scene as any).world.actors.get((this.scene as any).playerId) as CombatantActor;
|
||||
if (player) {
|
||||
progressionManager.allocatePassive(player, nodeId);
|
||||
emitUIUpdateFn();
|
||||
}
|
||||
});
|
||||
|
||||
// Player wait action
|
||||
this.scene.events.on("player-wait", () => {
|
||||
if (!awaitingPlayerFn()) return;
|
||||
if ((this.scene as any).isMenuOpen || (this.scene as any).isInventoryOpen || dungeonRenderer.isMinimapVisible()) return;
|
||||
commitActionFn({ type: "wait" });
|
||||
});
|
||||
|
||||
// Player search action
|
||||
this.scene.events.on("player-search", () => {
|
||||
if (!awaitingPlayerFn()) return;
|
||||
if ((this.scene as any).isMenuOpen || (this.scene as any).isInventoryOpen || dungeonRenderer.isMinimapVisible()) return;
|
||||
console.log("Player searching...");
|
||||
commitActionFn({ type: "wait" });
|
||||
});
|
||||
|
||||
// Item use
|
||||
this.scene.events.on("use-item", (data: { itemId: string }) => {
|
||||
if (!awaitingPlayerFn()) return;
|
||||
|
||||
const player = (this.scene as any).world.actors.get((this.scene as any).playerId) as CombatantActor;
|
||||
if (!player || !player.inventory) return;
|
||||
|
||||
const itemIdx = player.inventory.items.findIndex(it => it.id === data.itemId);
|
||||
if (itemIdx === -1) return;
|
||||
const item = player.inventory.items[itemIdx];
|
||||
|
||||
const result = itemManager.handleUse(data.itemId, player);
|
||||
|
||||
if (result.success && result.consumed) {
|
||||
const healAmount = player.stats.maxHp - player.stats.hp;
|
||||
const actualHeal = Math.min(healAmount, player.stats.hp);
|
||||
dungeonRenderer.showHeal(player.pos.x, player.pos.y, actualHeal);
|
||||
commitActionFn({ type: "wait" });
|
||||
emitUIUpdateFn();
|
||||
} else if (result.success && !result.consumed) {
|
||||
// Throwable item - start targeting
|
||||
if (targetingSystem.isActive && targetingSystem.itemId === item.id) {
|
||||
// Already targeting - execute throw
|
||||
if (targetingSystem.cursorPos) {
|
||||
executeThrowFn(targetingSystem.cursorPos.x, targetingSystem.cursorPos.y);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
targetingSystem.startTargeting(
|
||||
item.id,
|
||||
player.pos,
|
||||
(this.scene as any).world,
|
||||
dungeonRenderer.seenArray,
|
||||
(this.scene as any).world.width
|
||||
);
|
||||
emitUIUpdateFn();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all event listeners (call on scene shutdown)
|
||||
*/
|
||||
cleanup(): void {
|
||||
this.scene.events.removeAllListeners("menu-toggled");
|
||||
this.scene.events.removeAllListeners("inventory-toggled");
|
||||
this.scene.events.removeAllListeners("character-toggled");
|
||||
this.scene.events.removeAllListeners("toggle-minimap");
|
||||
this.scene.events.removeAllListeners("request-ui-update");
|
||||
this.scene.events.removeAllListeners("restart-game");
|
||||
this.scene.events.removeAllListeners("allocate-stat");
|
||||
this.scene.events.removeAllListeners("allocate-passive");
|
||||
this.scene.events.removeAllListeners("player-wait");
|
||||
this.scene.events.removeAllListeners("player-search");
|
||||
this.scene.events.removeAllListeners("use-item");
|
||||
}
|
||||
}
|
||||
151
src/scenes/systems/ItemManager.ts
Normal file
151
src/scenes/systems/ItemManager.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import type { World, CombatantActor, Item, ItemDropActor, Vec2 } from "../../core/types";
|
||||
import { EntityManager } from "../../engine/EntityManager";
|
||||
|
||||
/**
|
||||
* Result of attempting to use an item
|
||||
*/
|
||||
export interface ItemUseResult {
|
||||
success: boolean;
|
||||
consumed: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages item-related operations including spawning, pickup, and usage.
|
||||
* Extracted from GameScene to centralize item logic and reduce complexity.
|
||||
*/
|
||||
export class ItemManager {
|
||||
private world: World;
|
||||
private entityManager: EntityManager;
|
||||
|
||||
constructor(world: World, entityManager: EntityManager) {
|
||||
this.world = world;
|
||||
this.entityManager = entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update references when world changes (e.g., new floor)
|
||||
*/
|
||||
updateWorld(world: World, entityManager: EntityManager): void {
|
||||
this.world = world;
|
||||
this.entityManager = entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn an item drop at the specified position
|
||||
*/
|
||||
spawnItem(item: Item, pos: Vec2): void {
|
||||
if (!this.world || !this.entityManager) return;
|
||||
|
||||
const id = this.entityManager.getNextId();
|
||||
const drop: ItemDropActor = {
|
||||
id,
|
||||
pos: { x: pos.x, y: pos.y },
|
||||
category: "item_drop",
|
||||
item: { ...item } // Clone item
|
||||
};
|
||||
|
||||
this.entityManager.addActor(drop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to pickup an item at the player's position
|
||||
* @returns The picked up item, or null if nothing to pick up
|
||||
*/
|
||||
tryPickup(player: CombatantActor): Item | null {
|
||||
if (!player || !player.inventory) return null;
|
||||
|
||||
const actors = this.entityManager.getActorsAt(player.pos.x, player.pos.y);
|
||||
const itemActor = actors.find((a): a is ItemDropActor => a.category === "item_drop");
|
||||
|
||||
if (itemActor) {
|
||||
const item = itemActor.item;
|
||||
|
||||
// Add to inventory
|
||||
player.inventory.items.push(item);
|
||||
|
||||
// Remove from world
|
||||
this.entityManager.removeActor(itemActor.id);
|
||||
|
||||
console.log("Picked up:", item.name);
|
||||
return item;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle using an item from inventory
|
||||
* Returns information about what happened
|
||||
*/
|
||||
handleUse(itemId: string, player: CombatantActor): ItemUseResult {
|
||||
if (!player || !player.inventory) {
|
||||
return { success: false, consumed: false, message: "Invalid player state" };
|
||||
}
|
||||
|
||||
const itemIdx = player.inventory.items.findIndex(it => it.id === itemId);
|
||||
if (itemIdx === -1) {
|
||||
return { success: false, consumed: false, message: "Item not found" };
|
||||
}
|
||||
|
||||
const item = player.inventory.items[itemIdx];
|
||||
|
||||
// Check if item is a healing consumable
|
||||
if (item.stats && item.stats.hp && item.stats.hp > 0) {
|
||||
const healAmount = item.stats.hp;
|
||||
|
||||
if (player.stats.hp >= player.stats.maxHp) {
|
||||
return { success: false, consumed: false, message: "Already at full health" };
|
||||
}
|
||||
|
||||
player.stats.hp = Math.min(player.stats.hp + healAmount, player.stats.maxHp);
|
||||
|
||||
// Remove item after use
|
||||
player.inventory.items.splice(itemIdx, 1);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
consumed: true,
|
||||
message: `Healed for ${healAmount} HP`
|
||||
};
|
||||
}
|
||||
|
||||
// Throwable items are handled by TargetingSystem, not here
|
||||
if (item.throwable) {
|
||||
return {
|
||||
success: true,
|
||||
consumed: false,
|
||||
message: "Throwable item - use targeting"
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
consumed: false,
|
||||
message: "Item has no effect"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from player inventory by ID
|
||||
*/
|
||||
removeFromInventory(player: CombatantActor, itemId: string): boolean {
|
||||
if (!player || !player.inventory) return false;
|
||||
|
||||
const itemIdx = player.inventory.items.findIndex(it => it.id === itemId);
|
||||
if (itemIdx === -1) return false;
|
||||
|
||||
player.inventory.items.splice(itemIdx, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an item from player inventory by ID
|
||||
*/
|
||||
getItem(player: CombatantActor, itemId: string): Item | null {
|
||||
if (!player || !player.inventory) return null;
|
||||
|
||||
const item = player.inventory.items.find(it => it.id === itemId);
|
||||
return item || null;
|
||||
}
|
||||
}
|
||||
160
src/scenes/systems/TargetingSystem.ts
Normal file
160
src/scenes/systems/TargetingSystem.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import Phaser from "phaser";
|
||||
import type { World, CombatantActor, Item, Vec2, EntityId } from "../../core/types";
|
||||
import { TILE_SIZE } from "../../core/constants";
|
||||
import { GAME_CONFIG } from "../../core/config/GameConfig";
|
||||
import { traceProjectile, getClosestVisibleEnemy } from "../../engine/gameplay/CombatLogic";
|
||||
import type { EntityManager } from "../../engine/EntityManager";
|
||||
|
||||
/**
|
||||
* Manages targeting mode for thrown items.
|
||||
* Extracted from GameScene to isolate targeting logic and reduce complexity.
|
||||
*/
|
||||
export class TargetingSystem {
|
||||
private graphics: Phaser.GameObjects.Graphics;
|
||||
private active: boolean = false;
|
||||
private targetingItemId: string | null = null;
|
||||
private cursor: Vec2 | null = null;
|
||||
|
||||
constructor(graphics: Phaser.GameObjects.Graphics) {
|
||||
this.graphics = graphics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start targeting mode for a throwable item
|
||||
*/
|
||||
startTargeting(
|
||||
itemId: string,
|
||||
playerPos: Vec2,
|
||||
world: World,
|
||||
seenArray: Uint8Array,
|
||||
worldWidth: number
|
||||
): void {
|
||||
this.targetingItemId = itemId;
|
||||
this.active = true;
|
||||
|
||||
// Auto-target closest visible enemy
|
||||
const closest = getClosestVisibleEnemy(world, playerPos, seenArray, worldWidth);
|
||||
|
||||
if (closest) {
|
||||
this.cursor = closest;
|
||||
} else {
|
||||
this.cursor = null;
|
||||
}
|
||||
|
||||
this.drawLine(playerPos);
|
||||
console.log("Targeting Mode: ON");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the targeting cursor position
|
||||
*/
|
||||
updateCursor(worldPos: Vec2, playerPos: Vec2): void {
|
||||
if (!this.active) return;
|
||||
|
||||
this.cursor = { x: worldPos.x, y: worldPos.y };
|
||||
this.drawLine(playerPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the throw action
|
||||
*/
|
||||
executeThrow(
|
||||
world: World,
|
||||
playerId: EntityId,
|
||||
entityManager: EntityManager,
|
||||
onProjectileComplete: (blockedPos: Vec2, hitActorId: EntityId | undefined, item: Item) => void
|
||||
): boolean {
|
||||
if (!this.active || !this.targetingItemId || !this.cursor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const player = world.actors.get(playerId) as CombatantActor;
|
||||
if (!player || !player.inventory) return false;
|
||||
|
||||
const itemIdx = player.inventory.items.findIndex(it => it.id === this.targetingItemId);
|
||||
if (itemIdx === -1) {
|
||||
console.log("Item not found!");
|
||||
this.cancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
const item = player.inventory.items[itemIdx];
|
||||
// Remove item from inventory before throw
|
||||
player.inventory.items.splice(itemIdx, 1);
|
||||
|
||||
const start = player.pos;
|
||||
const end = { x: this.cursor.x, y: this.cursor.y };
|
||||
|
||||
const result = traceProjectile(world, start, end, entityManager, playerId);
|
||||
const { blockedPos, hitActorId } = result;
|
||||
|
||||
// Call the callback with throw results
|
||||
onProjectileComplete(blockedPos, hitActorId, item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel targeting mode
|
||||
*/
|
||||
cancel(): void {
|
||||
this.active = false;
|
||||
this.targetingItemId = null;
|
||||
this.cursor = null;
|
||||
this.graphics.clear();
|
||||
console.log("Targeting cancelled");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if targeting is currently active
|
||||
*/
|
||||
get isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the item being targeted
|
||||
*/
|
||||
get itemId(): string | null {
|
||||
return this.targetingItemId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current cursor position
|
||||
*/
|
||||
get cursorPos(): Vec2 | null {
|
||||
return this.cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw targeting line from player to cursor
|
||||
*/
|
||||
private drawLine(playerPos: Vec2): void {
|
||||
if (!this.cursor) {
|
||||
this.graphics.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
this.graphics.clear();
|
||||
|
||||
const startX = playerPos.x * TILE_SIZE + TILE_SIZE / 2;
|
||||
const startY = playerPos.y * TILE_SIZE + TILE_SIZE / 2;
|
||||
|
||||
const endX = this.cursor.x * TILE_SIZE + TILE_SIZE / 2;
|
||||
const endY = this.cursor.y * TILE_SIZE + TILE_SIZE / 2;
|
||||
|
||||
this.graphics.lineStyle(
|
||||
GAME_CONFIG.ui.targetingLineWidth,
|
||||
GAME_CONFIG.ui.targetingLineColor,
|
||||
GAME_CONFIG.ui.targetingLineAlpha
|
||||
);
|
||||
this.graphics.lineBetween(startX, startY, endX, endY);
|
||||
|
||||
this.graphics.strokeRect(
|
||||
this.cursor.x * TILE_SIZE,
|
||||
this.cursor.y * TILE_SIZE,
|
||||
TILE_SIZE,
|
||||
TILE_SIZE
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user