Merge remote-tracking branch 'origin/main' into feature/depth-sort-fix
388
package-lock.json
generated
@ -25,6 +25,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
|
||||
"@tauri-apps/cli": "^2.2.7",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^20.14.11",
|
||||
@ -1496,9 +1497,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.7.tgz",
|
||||
"integrity": "sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz",
|
||||
"integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -1509,9 +1510,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.7.tgz",
|
||||
"integrity": "sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz",
|
||||
"integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1522,9 +1523,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.7.tgz",
|
||||
"integrity": "sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz",
|
||||
"integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1535,9 +1536,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.7.tgz",
|
||||
"integrity": "sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz",
|
||||
"integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -1548,9 +1549,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.7.tgz",
|
||||
"integrity": "sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz",
|
||||
"integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1561,9 +1562,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.7.tgz",
|
||||
"integrity": "sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz",
|
||||
"integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -1574,9 +1575,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.7.tgz",
|
||||
"integrity": "sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz",
|
||||
"integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -1587,9 +1588,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.7.tgz",
|
||||
"integrity": "sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz",
|
||||
"integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -1600,9 +1601,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.7.tgz",
|
||||
"integrity": "sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1613,9 +1614,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.7.tgz",
|
||||
"integrity": "sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz",
|
||||
"integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1626,9 +1627,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.7.tgz",
|
||||
"integrity": "sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@ -1639,9 +1640,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.7.tgz",
|
||||
"integrity": "sha512-vrDk9JDa/BFkxcS2PbWpr0C/LiiSLxFbNOBgfbW6P8TBe9PPHx9Wqbvx2xgNi1TOAyQHQJ7RZFqBiEohm79r0w==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@ -1652,9 +1653,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.7.tgz",
|
||||
"integrity": "sha512-rB+ejFyjtmSo+g/a4eovDD1lHWHVqizN8P0Hm0RElkINpS0XOdpaXloqM4FBkF9ZWEzg6bezymbpLmeMldfLTw==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@ -1665,9 +1666,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.7.tgz",
|
||||
"integrity": "sha512-nNXNjo4As6dNqRn7OrsnHzwTgtypfRA3u3AKr0B3sOOo+HkedIbn8ZtFnB+4XyKJojIfqDKmbIzO1QydQ8c+Pw==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@ -1678,9 +1679,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.7.tgz",
|
||||
"integrity": "sha512-9kPVf9ahnpOMSGlCxXGv980wXD0zRR3wyk8+33/MXQIpQEOpaNe7dEHm5LMfyRZRNt9lMEQuH0jUKj15MkM7QA==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -1691,9 +1692,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.7.tgz",
|
||||
"integrity": "sha512-7wJPXRWTTPtTFDFezA8sle/1sdgxDjuMoRXEKtx97ViRxGGkVQYovem+Q8Pr/2HxiHp74SSRG+o6R0Yq0shPwQ==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz",
|
||||
"integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -1704,9 +1705,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.7.tgz",
|
||||
"integrity": "sha512-MN7aaBC7mAjsiMEZcsJvwNsQVNZShgES/9SzWp1HC9Yjqb5OpexYnRjF7RmE4itbeesHMYYQiAtUAQaSKs2Rfw==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz",
|
||||
"integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1717,9 +1718,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.7.tgz",
|
||||
"integrity": "sha512-aeawEKYswsFu1LhDM9RIgToobquzdtSc4jSVqHV8uApz4FVvhFl/mKh92wc8WpFc6aYCothV/03UjY6y7yLgbg==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz",
|
||||
"integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -1730,9 +1731,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.7.tgz",
|
||||
"integrity": "sha512-4ZedScpxxIrVO7otcZ8kCX1mZArtH2Wfj3uFCxRJ9NO80gg1XV0U/b2f/MKaGwj2X3QopHfoWiDQ917FRpwY3w==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz",
|
||||
"integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -1748,6 +1749,205 @@
|
||||
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.2.7.tgz",
|
||||
"integrity": "sha512-ZnsS2B4BplwXP37celanNANiIy8TCYhvg5RT09n72uR/o+navFZtGpFSqljV8fy1Y4ixIPds8FrGSXJCN2BerA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"bin": {
|
||||
"tauri": "tauri.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/tauri"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tauri-apps/cli-darwin-arm64": "2.2.7",
|
||||
"@tauri-apps/cli-darwin-x64": "2.2.7",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.2.7",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.2.7",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.2.7",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.2.7",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.2.7",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.2.7",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.2.7",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.2.7.tgz",
|
||||
"integrity": "sha512-54kcpxZ3X1Rq+pPTzk3iIcjEVY4yv493uRx/80rLoAA95vAC0c//31Whz75UVddDjJfZvXlXZ3uSZ+bnCOnt0A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.2.7.tgz",
|
||||
"integrity": "sha512-Vgu2XtBWemLnarB+6LqQeLanDlRj7CeFN//H8bVVdjbNzxcSxsvbLYMBP8+3boa7eBnjDrqMImRySSgL6IrwTw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.2.7.tgz",
|
||||
"integrity": "sha512-+Clha2iQAiK9zoY/KKW0KLHkR0k36O78YLx5Sl98tWkwI3OBZFg5H5WT1plH/4sbZIS2aLFN6dw58/JlY9Bu/g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.2.7.tgz",
|
||||
"integrity": "sha512-Z/Lp4SQe6BUEOays9BQAEum2pvZF4w9igyXijP+WbkOejZx4cDvarFJ5qXrqSLmBh7vxrdZcLwoLk9U//+yQrg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.7.tgz",
|
||||
"integrity": "sha512-+8HZ+txff/Y3YjAh80XcLXcX8kpGXVdr1P8AfjLHxHdS6QD4Md+acSxGTTNbplmHuBaSHJvuTvZf9tU1eDCTDg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.2.7.tgz",
|
||||
"integrity": "sha512-ahlSnuCnUntblp9dG7/w5ZWZOdzRFi3zl0oScgt7GF4KNAOEa7duADsxPA4/FT2hLRa0SvpqtD4IYFvCxoVv3Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.7.tgz",
|
||||
"integrity": "sha512-+qKAWnJRSX+pjjRbKAQgTdFY8ecdcu8UdJ69i7wn3ZcRn2nMMzOO2LOMOTQV42B7/Q64D1pIpmZj9yblTMvadA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.2.7.tgz",
|
||||
"integrity": "sha512-aa86nRnrwT04u9D9fhf5JVssuAZlUCCc8AjqQjqODQjMd4BMA2+d4K9qBMpEG/1kVh95vZaNsLogjEaqSTTw4A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.2.7.tgz",
|
||||
"integrity": "sha512-EiJ5/25tLSQOSGvv+t6o3ZBfOTKB5S3vb+hHQuKbfmKdRF0XQu2YPdIi1CQw1DU97ZAE0Dq4frvnyYEKWgMzVQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.2.7.tgz",
|
||||
"integrity": "sha512-ZB8Kw90j8Ld+9tCWyD2fWCYfIrzbQohJ4DJSidNwbnehlZzP7wAz6Z3xjsvUdKtQ3ibtfoeTqVInzCCEpI+pWg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node20": {
|
||||
"version": "20.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz",
|
||||
@ -2504,9 +2704,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001699",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz",
|
||||
"integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==",
|
||||
"version": "1.0.30001700",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz",
|
||||
"integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -2903,9 +3103,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.101",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.101.tgz",
|
||||
"integrity": "sha512-L0ISiQrP/56Acgu4/i/kfPwWSgrzYZUnQrC0+QPFuhqlLP1Ir7qzPPDVS9BcKIyWTRU8+o6CC8dKw38tSWhYIA==",
|
||||
"version": "1.5.103",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.103.tgz",
|
||||
"integrity": "sha512-P6+XzIkfndgsrjROJWfSvVEgNHtPgbhVyTkwLjUM2HU/h7pZRORgaTlHqfAikqxKmdJMLW8fftrdGWbd/Ds0FA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@ -4418,9 +4618,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz",
|
||||
"integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==",
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -4730,9 +4930,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.7.tgz",
|
||||
"integrity": "sha512-8qhyN0oZ4x0H6wmBgfKxJtxM7qS98YJ0k0kNh5ECVtuchIJ7z9IVVvzpmtQyT10PXKMtBxYr1wQ5Apg8RS8kXQ==",
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz",
|
||||
"integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.6"
|
||||
@ -4745,25 +4945,25 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.34.7",
|
||||
"@rollup/rollup-android-arm64": "4.34.7",
|
||||
"@rollup/rollup-darwin-arm64": "4.34.7",
|
||||
"@rollup/rollup-darwin-x64": "4.34.7",
|
||||
"@rollup/rollup-freebsd-arm64": "4.34.7",
|
||||
"@rollup/rollup-freebsd-x64": "4.34.7",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.34.7",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.34.7",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.34.7",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.34.7",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.34.7",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.7",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.34.7",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.34.7",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.34.7",
|
||||
"@rollup/rollup-linux-x64-musl": "4.34.7",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.34.7",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.34.7",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.34.7",
|
||||
"@rollup/rollup-android-arm-eabi": "4.34.8",
|
||||
"@rollup/rollup-android-arm64": "4.34.8",
|
||||
"@rollup/rollup-darwin-arm64": "4.34.8",
|
||||
"@rollup/rollup-darwin-x64": "4.34.8",
|
||||
"@rollup/rollup-freebsd-arm64": "4.34.8",
|
||||
"@rollup/rollup-freebsd-x64": "4.34.8",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.34.8",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.34.8",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.34.8",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-x64-musl": "4.34.8",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.34.8",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.34.8",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.34.8",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@ -5722,9 +5922,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue-component-type-helpers": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.0.tgz",
|
||||
"integrity": "sha512-cYrAnv2me7bPDcg9kIcGwjJiSB6Qyi08+jLDo9yuvoFQjzHiPTzML7RnkJB1+3P6KMsX/KbCD4QE3Tv/knEllw==",
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.2.tgz",
|
||||
"integrity": "sha512-6lLY+n2xz2kCYshl59mL6gy8OUUTmkscmDFMO8i7Lj+QKwgnIFUZmM1i/iTYObtrczZVdw7UakPqDTGwVSGaRg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@ -5981,9 +6181,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
@ -19,8 +19,8 @@
|
||||
"axios": "^1.7.9",
|
||||
"dexie": "^4.0.11",
|
||||
"phaser": "^3.88.2",
|
||||
"phavuer": "^0.16.5",
|
||||
"phaser3-rex-plugins": "^1.80.13",
|
||||
"phavuer": "^0.16.5",
|
||||
"pinia": "^2.3.1",
|
||||
"sharp": "^0.33.5",
|
||||
"socket.io-client": "^4.8.1",
|
||||
@ -31,6 +31,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
|
||||
"@tauri-apps/cli": "^2.2.7",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^20.14.11",
|
||||
|
4
src-tauri/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
/gen/schemas
|
5149
src-tauri/Cargo.lock
generated
Normal file
25
src-tauri/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
edition = "2021"
|
||||
rust-version = "1.77.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
name = "app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.4", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4"
|
||||
tauri = { version = "2.2.4", features = [] }
|
||||
tauri-plugin-log = "2.0.0-rc"
|
3
src-tauri/build.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
11
src-tauri/capabilities/default.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
]
|
||||
}
|
BIN
src-tauri/icons/128x128.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.ico
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
src-tauri/icons/icon.png
Normal file
After Width: | Height: | Size: 49 KiB |
16
src-tauri/src/lib.rs
Normal file
@ -0,0 +1,16 @@
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
6
src-tauri/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
app_lib::run();
|
||||
}
|
37
src-tauri/tauri.conf.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "noxious",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.noxious.app",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
"devUrl": "http://localhost:5173",
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"beforeBuildCommand": "npm run build-only"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Noxious",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import Debug from '@/components/utilities/Debug.vue'
|
||||
import Notifications from '@/components/utilities/Notifications.vue'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { useSoundComposable } from '@/composables/useSoundComposable'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, watch } from 'vue'
|
||||
|
||||
@ -26,8 +27,8 @@ const { playSound } = useSoundComposable()
|
||||
|
||||
const currentScreen = computed(() => {
|
||||
if (!gameStore.game.isLoaded) return Loading
|
||||
if (!gameStore.connection) return Login
|
||||
if (!gameStore.token) return Login
|
||||
if (!socketManager.connection) return Login
|
||||
if (!socketManager.token) return Login
|
||||
if (!gameStore.character) return Characters
|
||||
if (mapEditor.active.value) return MapEditor
|
||||
return Game
|
||||
|
@ -5,6 +5,8 @@ export enum Direction {
|
||||
}
|
||||
|
||||
export enum SocketEvent {
|
||||
CONNECT_ERROR = 'connect_error',
|
||||
RECONNECT_FAILED = 'reconnect_failed',
|
||||
CLOSE = '52',
|
||||
DATA = '51',
|
||||
CHARACTER_CONNECT = '50',
|
||||
@ -41,7 +43,7 @@ export enum SocketEvent {
|
||||
GM_MAP_REQUEST = '19',
|
||||
GM_MAP_UPDATE = '18',
|
||||
MAP_CHARACTER_MOVEERROR = '17',
|
||||
DISCONNECT = '16',
|
||||
DISCONNECT = 'disconnect',
|
||||
USER_DISCONNECT = '15',
|
||||
LOGIN = '14',
|
||||
LOGGED_IN = '13',
|
||||
@ -49,7 +51,7 @@ export enum SocketEvent {
|
||||
DATE = '11',
|
||||
FAILED = '10',
|
||||
COMPLETED = '9',
|
||||
CONNECTION = '8',
|
||||
CONNECTION = 'connection',
|
||||
WEATHER = '7',
|
||||
CHARACTER_DISCONNECT = '6',
|
||||
MAP_CHARACTER_ATTACK = '5',
|
||||
|
@ -151,8 +151,9 @@ export type CharacterType = {
|
||||
export type CharacterHair = {
|
||||
id: string
|
||||
name: string
|
||||
sprite?: Sprite
|
||||
sprite: string | Sprite
|
||||
gender: CharacterGender
|
||||
color: string
|
||||
isSelectable: boolean
|
||||
}
|
||||
|
||||
@ -209,6 +210,8 @@ export enum CharacterEquipmentSlotType {
|
||||
export type Sprite = {
|
||||
id: string
|
||||
name: string
|
||||
width: number | null
|
||||
height: number | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
spriteActions: SpriteAction[]
|
||||
|
@ -21,7 +21,17 @@ export async function downloadCache<T extends { id: string; updatedAt: Date }>(e
|
||||
}
|
||||
|
||||
const items = response.data ?? []
|
||||
const serverItemIds = new Set(items.map((item) => item.id))
|
||||
|
||||
// Remove items that don't exist on server
|
||||
const existingItems = await storage.getAll()
|
||||
for (const existingItem of existingItems) {
|
||||
if (!serverItemIds.has(existingItem.id)) {
|
||||
await storage.delete(existingItem.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Update or add new items
|
||||
for (const item of items) {
|
||||
let overwrite = false
|
||||
const existingItem = await storage.getById(item.id)
|
||||
|
@ -124,7 +124,7 @@ button {
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
@apply bg-red-400;
|
||||
@apply bg-red-500;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
<Container ref="characterContainer" :x="currentPositionX" :y="currentPositionY" :depth="isometricDepth">
|
||||
<ChatBubble :mapCharacter="props.mapCharacter" />
|
||||
<HealthBar :mapCharacter="props.mapCharacter" />
|
||||
<CharacterHair :mapCharacter="props.mapCharacter" />
|
||||
<Sprite ref="characterSprite" :origin-y="1" :flipX="isFlippedX" />
|
||||
<CharacterHair :mapCharacter="props.mapCharacter" :flipX="isFlippedX" />
|
||||
<Sprite ref="characterSprite" :flipX="isFlippedX" />
|
||||
</Container>
|
||||
</template>
|
||||
|
||||
@ -84,13 +84,14 @@ watch(
|
||||
rotation: props.mapCharacter.character.rotation,
|
||||
isAttacking: props.mapCharacter.isAttacking
|
||||
}),
|
||||
(oldValues, newValues) => {
|
||||
async (oldValues, newValues) => {
|
||||
handlePositionUpdate(oldValues, newValues)
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await initializeSprite()
|
||||
|
||||
if (props.mapCharacter.character.id === gameStore.character!.id) {
|
||||
scene.cameras.main.startFollow(characterContainer.value as Phaser.GameObjects.Container)
|
||||
}
|
||||
|
@ -1,54 +1,63 @@
|
||||
<template>
|
||||
<Image v-bind="imageProps" v-if="gameStore.isTextureLoaded(texture)" />
|
||||
<Image ref="image" v-if="hairSpriteId" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { MapCharacter, Sprite as SpriteT } from '@/application/types'
|
||||
import { loadSpriteTextures } from '@/services/textureService'
|
||||
import { CharacterHairStorage, CharacterTypeStorage, SpriteStorage } from '@/storage/storages'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { Image, useScene } from 'phavuer'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { Image, refObj, useScene } from 'phavuer'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
mapCharacter: MapCharacter
|
||||
}>()
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const scene = useScene()
|
||||
const hairSpriteId = ref('')
|
||||
const sprite = ref<SpriteT | null>(null)
|
||||
const hairSprite = ref<SpriteT | null>(null)
|
||||
const characterSpriteHeight = ref(0)
|
||||
const image = refObj<Phaser.GameObjects.Image>()
|
||||
|
||||
const flipX = computed(() => [6, 0].includes(props.mapCharacter.character.rotation ?? 0))
|
||||
const texture = computed(() => {
|
||||
const { rotation } = props.mapCharacter.character
|
||||
const direction = [0, 6].includes(rotation) ? 'back' : 'front'
|
||||
const direction = flipX.value ? 'back' : 'front'
|
||||
|
||||
return `${hairSpriteId.value}-${direction}`
|
||||
})
|
||||
|
||||
const isFlippedX = computed(() => [6, 4].includes(props.mapCharacter.character.rotation ?? 0))
|
||||
|
||||
const imageProps = computed(() => {
|
||||
const direction = [0, 6].includes(props.mapCharacter.character.rotation ?? 0) ? 'back' : 'front'
|
||||
const spriteAction = sprite.value?.spriteActions?.find((spriteAction) => spriteAction.action === direction)
|
||||
|
||||
return {
|
||||
depth: 9999,
|
||||
originX: Number(spriteAction?.originX) ?? 0,
|
||||
originY: Number(spriteAction?.originY) ?? 0,
|
||||
flipX: isFlippedX.value,
|
||||
texture: texture.value
|
||||
}
|
||||
})
|
||||
watch(
|
||||
() => props.mapCharacter.character,
|
||||
(newValue) => {
|
||||
if (!image.value) return
|
||||
image.value.setTexture(texture.value)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
const characterHairStorage = new CharacterHairStorage()
|
||||
const spriteId = await characterHairStorage.getSpriteId(props.mapCharacter.character.characterHair!)
|
||||
if (!spriteId) return
|
||||
if (!props.mapCharacter.character.characterType || !props.mapCharacter.character.characterHair) return
|
||||
|
||||
hairSpriteId.value = spriteId
|
||||
const characterTypeStorage = new CharacterTypeStorage()
|
||||
const characterHairStorage = new CharacterHairStorage()
|
||||
const spriteStorage = new SpriteStorage()
|
||||
sprite.value = await spriteStorage.getById(spriteId)
|
||||
await loadSpriteTextures(scene, spriteId)
|
||||
|
||||
const characterType = await characterTypeStorage.getById(props.mapCharacter.character.characterType!)
|
||||
if (!characterType) return
|
||||
characterSpriteHeight.value = 100
|
||||
|
||||
hairSpriteId.value = await characterHairStorage.getSpriteId(props.mapCharacter.character.characterHair)
|
||||
if (!hairSpriteId.value) return
|
||||
|
||||
hairSprite.value = await spriteStorage.getById(hairSpriteId.value)
|
||||
if (!hairSprite.value) return
|
||||
|
||||
await loadSpriteTextures(scene, hairSpriteId.value)
|
||||
|
||||
if (!image.value) return
|
||||
|
||||
image.value.setOrigin(0.5, 2.15)
|
||||
image.value.setTexture(texture.value)
|
||||
image.value.setSize(30, 40)
|
||||
})
|
||||
</script>
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useMapStore } from '@/stores/mapStore'
|
||||
import { onClickOutside, useFocus } from '@vueuse/core'
|
||||
@ -30,7 +31,6 @@ import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
const scene = useScene()
|
||||
const gameStore = useGameStore()
|
||||
const mapStore = useMapStore()
|
||||
|
||||
const message = ref('')
|
||||
const chats = ref<{ character: string; message: string }[]>([])
|
||||
@ -55,7 +55,7 @@ function unfocusChat(event: Event, targetElement: HTMLElement) {
|
||||
|
||||
const sendMessage = () => {
|
||||
if (!message.value.trim()) return
|
||||
gameStore.connection?.emit(SocketEvent.CHAT_MESSAGE, { message: message.value }, (response: boolean) => {})
|
||||
socketManager.emit(SocketEvent.CHAT_MESSAGE, { message: message.value }, (response: boolean) => {})
|
||||
message.value = ''
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ const scrollToBottom = () => {
|
||||
})
|
||||
}
|
||||
|
||||
gameStore.connection?.on(SocketEvent.CHAT_MESSAGE, (data: { character: string; message: string }) => {
|
||||
socketManager.on(SocketEvent.CHAT_MESSAGE, (data: { character: string; message: string }) => {
|
||||
if (!data.character || !data.message) return
|
||||
|
||||
chats.value.push({ character: data.character, message: data.message })
|
||||
@ -153,7 +153,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
gameStore.connection?.off(SocketEvent.CHAT_MESSAGE)
|
||||
socketManager.off(SocketEvent.CHAT_MESSAGE)
|
||||
removeEventListener('keydown', focusChat)
|
||||
})
|
||||
</script>
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useDateFormat } from '@vueuse/core'
|
||||
import { onUnmounted } from 'vue'
|
||||
@ -15,6 +16,6 @@ import { onUnmounted } from 'vue'
|
||||
const gameStore = useGameStore()
|
||||
|
||||
onUnmounted(() => {
|
||||
gameStore.connection?.off(SocketEvent.DATE)
|
||||
socketManager.off(SocketEvent.DATE)
|
||||
})
|
||||
</script>
|
||||
|
@ -6,6 +6,7 @@
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { MapCharacter, UUID } from '@/application/types'
|
||||
import Character from '@/components/game/character/Character.vue'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useMapStore } from '@/stores/mapStore'
|
||||
import { onUnmounted } from 'vue'
|
||||
@ -17,32 +18,32 @@ const props = defineProps<{
|
||||
tileMap: Phaser.Tilemaps.Tilemap
|
||||
}>()
|
||||
|
||||
gameStore.connection?.on(SocketEvent.MAP_CHARACTER_JOIN, (data: MapCharacter) => {
|
||||
socketManager.on(SocketEvent.MAP_CHARACTER_JOIN, (data: MapCharacter) => {
|
||||
mapStore.addCharacter(data)
|
||||
})
|
||||
|
||||
gameStore.connection?.on(SocketEvent.MAP_CHARACTER_LEAVE, (characterId: UUID) => {
|
||||
socketManager.on(SocketEvent.MAP_CHARACTER_LEAVE, (characterId: UUID) => {
|
||||
mapStore.removeCharacter(characterId)
|
||||
})
|
||||
|
||||
gameStore.connection?.on(SocketEvent.MAP_CHARACTER_MOVE, (data: { characterId: UUID; positionX: number; positionY: number; rotation: number; isMoving: boolean }) => {
|
||||
mapStore.updateCharacterPosition(data)
|
||||
// @TODO: Replace with universal class, composable or store
|
||||
if (data.characterId === gameStore.character?.id) {
|
||||
gameStore.character!.positionX = data.positionX
|
||||
gameStore.character!.positionY = data.positionY
|
||||
gameStore.character!.rotation = data.rotation
|
||||
socketManager.on(SocketEvent.MAP_CHARACTER_MOVE, ([characterId, posX, posY, rot, isMoving]: [UUID, number, number, number, boolean]) => {
|
||||
mapStore.updateCharacterPosition([characterId, posX, posY, rot, isMoving])
|
||||
|
||||
if (characterId === gameStore.character?.id) {
|
||||
gameStore.character!.positionX = posX
|
||||
gameStore.character!.positionY = posY
|
||||
gameStore.character!.rotation = rot
|
||||
}
|
||||
})
|
||||
|
||||
gameStore.connection?.on(SocketEvent.MAP_CHARACTER_ATTACK, (characterId: UUID) => {
|
||||
socketManager.on(SocketEvent.MAP_CHARACTER_ATTACK, (characterId: UUID) => {
|
||||
mapStore.updateCharacterProperty(characterId, 'isAttacking', true)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
gameStore.connection?.off(SocketEvent.MAP_CHARACTER_ATTACK)
|
||||
gameStore.connection?.off(SocketEvent.MAP_CHARACTER_MOVE)
|
||||
gameStore.connection?.off(SocketEvent.MAP_CHARACTER_JOIN)
|
||||
gameStore.connection?.off(SocketEvent.MAP_CHARACTER_LEAVE)
|
||||
socketManager.off(SocketEvent.MAP_CHARACTER_ATTACK)
|
||||
socketManager.off(SocketEvent.MAP_CHARACTER_MOVE)
|
||||
socketManager.off(SocketEvent.MAP_CHARACTER_JOIN)
|
||||
socketManager.off(SocketEvent.MAP_CHARACTER_LEAVE)
|
||||
})
|
||||
</script>
|
||||
|
@ -11,6 +11,7 @@ import { unduplicateArray } from '@/application/utilities'
|
||||
import Characters from '@/components/game/map/Characters.vue'
|
||||
import MapTiles from '@/components/game/map/MapTiles.vue'
|
||||
import PlacedMapObjects from '@/components/game/map/PlacedMapObjects.vue'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { createTileLayer, createTileMap, loadTileTexturesFromMapTileArray } from '@/services/mapService'
|
||||
import { MapStorage } from '@/storage/storages'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
@ -29,7 +30,7 @@ const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
|
||||
const tileMapLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
||||
|
||||
// Event listeners
|
||||
gameStore.connection?.on(SocketEvent.MAP_CHARACTER_TELEPORT, (data: mapLoadData) => {
|
||||
socketManager.on(SocketEvent.MAP_CHARACTER_TELEPORT, (data: mapLoadData) => {
|
||||
mapStore.setMapId(data.mapId)
|
||||
mapStore.setCharacters(data.characters)
|
||||
})
|
||||
@ -65,6 +66,6 @@ onUnmounted(() => {
|
||||
tileMap.value.destroy()
|
||||
}
|
||||
|
||||
gameStore.connection?.off(SocketEvent.MAP_CHARACTER_TELEPORT)
|
||||
socketManager.off(SocketEvent.MAP_CHARACTER_TELEPORT)
|
||||
})
|
||||
</script>
|
||||
|
@ -20,12 +20,29 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<div class="space-x-6 flex items-center">
|
||||
<label for="color">Color</label>
|
||||
<input v-model="characterColor" class="input-field" type="text" name="color" placeholder="Character Hair Color" />
|
||||
<div class="h-[38px] w-[38px] rounded" :style="{ backgroundColor: characterColor }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-field-half">
|
||||
<label for="spriteId">Sprite</label>
|
||||
<select v-model="characterSpriteId" class="input-field" name="spriteId">
|
||||
<option disabled selected value="">Select sprite</option>
|
||||
<option v-for="sprite in assetManagerStore.spriteList" :key="sprite.id" :value="sprite.id">{{ sprite.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-half">
|
||||
<label>Preview</label>
|
||||
<div v-if="characterSpriteId" class="flex flex-col">
|
||||
<div class="p-3 pb-5 min-h-32 block rounded-md default-border bg-gray-800">
|
||||
<div class="flex items-center justify-center p-1 h-full bg-gray-700 rounded">
|
||||
<img :src="config.server_endpoint + '/textures/sprites/' + characterSpriteId + '/front.png'" class="max-w-[200px] max-h-[200px] object-contain" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button>
|
||||
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="removeCharacterHair">Remove</button>
|
||||
</form>
|
||||
@ -34,49 +51,50 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { CharacterGender, CharacterHair, Sprite } from '@/application/types'
|
||||
import { downloadCache } from '@/application/utilities'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { CharacterHairStorage } from '@/storage/storages'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
||||
const selectedCharacterHair = computed(() => assetManagerStore.selectedCharacterHair)
|
||||
|
||||
const characterName = ref('')
|
||||
const characterGender = ref<CharacterGender>('MALE' as CharacterGender.MALE)
|
||||
const characterColor = ref<string>('#000000')
|
||||
const characterIsSelectable = ref<boolean>(false)
|
||||
const characterSpriteId = ref<string | null | undefined>(null)
|
||||
|
||||
const genderOptions: CharacterGender[] = ['MALE' as CharacterGender.MALE, 'FEMALE' as CharacterGender.FEMALE]
|
||||
|
||||
if (!selectedCharacterHair.value) {
|
||||
console.error('No character hair selected')
|
||||
}
|
||||
|
||||
if (selectedCharacterHair.value) {
|
||||
characterName.value = selectedCharacterHair.value.name
|
||||
characterGender.value = selectedCharacterHair.value.gender
|
||||
characterColor.value = selectedCharacterHair.value.color
|
||||
characterIsSelectable.value = selectedCharacterHair.value.isSelectable
|
||||
characterSpriteId.value = selectedCharacterHair.value.sprite?.id
|
||||
}
|
||||
|
||||
function removeCharacterHair() {
|
||||
async function removeCharacterHair() {
|
||||
if (!selectedCharacterHair.value) return
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_CHARACTERHAIR_REMOVE, { id: selectedCharacterHair.value.id }, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_REMOVE, { id: selectedCharacterHair.value.id }, async (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to remove character hair')
|
||||
return
|
||||
}
|
||||
refreshCharacterHairList()
|
||||
|
||||
await downloadCache('character_hair', new CharacterHairStorage())
|
||||
await refreshCharacterHairList()
|
||||
})
|
||||
}
|
||||
|
||||
function refreshCharacterHairList(unsetSelectedCharacterHair = true) {
|
||||
gameStore.connection?.emit(SocketEvent.GM_CHARACTERHAIR_LIST, {}, (response: CharacterHair[]) => {
|
||||
async function refreshCharacterHairList(unsetSelectedCharacterHair = true) {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_LIST, {}, (response: CharacterHair[]) => {
|
||||
assetManagerStore.setCharacterHairList(response)
|
||||
|
||||
if (unsetSelectedCharacterHair) {
|
||||
@ -85,21 +103,24 @@ function refreshCharacterHairList(unsetSelectedCharacterHair = true) {
|
||||
})
|
||||
}
|
||||
|
||||
function saveCharacterHair() {
|
||||
async function saveCharacterHair() {
|
||||
const characterHairData = {
|
||||
id: selectedCharacterHair.value!.id,
|
||||
name: characterName.value,
|
||||
gender: characterGender.value,
|
||||
color: characterColor.value,
|
||||
isSelectable: characterIsSelectable.value,
|
||||
spriteId: characterSpriteId.value
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_CHARACTERHAIR_UPDATE, characterHairData, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_UPDATE, characterHairData, async (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to save character type')
|
||||
return
|
||||
}
|
||||
refreshCharacterHairList(false)
|
||||
|
||||
await downloadCache('character_hair', new CharacterHairStorage())
|
||||
await refreshCharacterHairList(false)
|
||||
})
|
||||
}
|
||||
|
||||
@ -107,6 +128,7 @@ watch(selectedCharacterHair, (characterHair: CharacterHair | null) => {
|
||||
if (!characterHair) return
|
||||
characterName.value = characterHair.name
|
||||
characterGender.value = characterHair.gender
|
||||
characterColor.value = characterHair.color
|
||||
characterIsSelectable.value = characterHair.isSelectable
|
||||
characterSpriteId.value = characterHair.sprite?.id
|
||||
})
|
||||
@ -114,7 +136,7 @@ watch(selectedCharacterHair, (characterHair: CharacterHair | null) => {
|
||||
onMounted(() => {
|
||||
if (!selectedCharacterHair.value) return
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
|
@ -34,6 +34,7 @@
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { CharacterHair } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
@ -53,13 +54,13 @@ const handleSearch = () => {
|
||||
}
|
||||
|
||||
const createNewCharacterHair = () => {
|
||||
gameStore.connection?.emit(SocketEvent.GM_CHARACTERHAIR_CREATE, {}, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_CREATE, {}, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to create new character type')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_CHARACTERHAIR_LIST, {}, (response: CharacterHair[]) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_LIST, {}, (response: CharacterHair[]) => {
|
||||
assetManagerStore.setCharacterHairList(response)
|
||||
})
|
||||
})
|
||||
@ -93,7 +94,7 @@ function toTop() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit(SocketEvent.GM_CHARACTERHAIR_LIST, {}, (response: CharacterHair[]) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_LIST, {}, (response: CharacterHair[]) => {
|
||||
assetManagerStore.setCharacterHairList(response)
|
||||
})
|
||||
})
|
||||
|
@ -42,11 +42,12 @@
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { CharacterGender, CharacterRace, CharacterType, Sprite } from '@/application/types'
|
||||
import { downloadCache } from '@/application/utilities'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { CharacterTypeStorage } from '@/storage/storages'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
||||
const selectedCharacterType = computed(() => assetManagerStore.selectedCharacterType)
|
||||
@ -72,20 +73,22 @@ if (selectedCharacterType.value) {
|
||||
characterSpriteId.value = selectedCharacterType.value.sprite?.id
|
||||
}
|
||||
|
||||
function removeCharacterType() {
|
||||
async function removeCharacterType() {
|
||||
if (!selectedCharacterType.value) return
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_CHARACTERTYPE_REMOVE, { id: selectedCharacterType.value.id }, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_REMOVE, { id: selectedCharacterType.value.id }, async (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to remove character type')
|
||||
return
|
||||
}
|
||||
refreshCharacterTypeList()
|
||||
|
||||
await downloadCache('character_types', new CharacterTypeStorage())
|
||||
await refreshCharacterTypeList()
|
||||
})
|
||||
}
|
||||
|
||||
function refreshCharacterTypeList(unsetSelectedCharacterType = true) {
|
||||
gameStore.connection?.emit(SocketEvent.GM_CHARACTERTYPE_LIST, {}, (response: CharacterType[]) => {
|
||||
async function refreshCharacterTypeList(unsetSelectedCharacterType = true) {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_LIST, {}, (response: CharacterType[]) => {
|
||||
assetManagerStore.setCharacterTypeList(response)
|
||||
|
||||
if (unsetSelectedCharacterType) {
|
||||
@ -94,7 +97,7 @@ function refreshCharacterTypeList(unsetSelectedCharacterType = true) {
|
||||
})
|
||||
}
|
||||
|
||||
function saveCharacterType() {
|
||||
async function saveCharacterType() {
|
||||
const characterTypeData = {
|
||||
id: selectedCharacterType.value!.id,
|
||||
name: characterName.value,
|
||||
@ -104,12 +107,14 @@ function saveCharacterType() {
|
||||
spriteId: characterSpriteId.value
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_CHARACTERTYPE_UPDATE, characterTypeData, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_UPDATE, characterTypeData, async (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to save character type')
|
||||
return
|
||||
}
|
||||
refreshCharacterTypeList(false)
|
||||
|
||||
await downloadCache('character_types', new CharacterTypeStorage())
|
||||
await refreshCharacterTypeList(false)
|
||||
})
|
||||
}
|
||||
|
||||
@ -125,7 +130,7 @@ watch(selectedCharacterType, (characterType: CharacterType | null) => {
|
||||
onMounted(() => {
|
||||
if (!selectedCharacterType.value) return
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
|
@ -34,6 +34,7 @@
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { CharacterType } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
@ -53,13 +54,13 @@ const handleSearch = () => {
|
||||
}
|
||||
|
||||
const createNewCharacterType = () => {
|
||||
gameStore.connection?.emit(SocketEvent.GM_CHARACTERTYPE_CREATE, {}, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_CREATE, {}, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to create new character type')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_CHARACTERTYPE_LIST, {}, (response: CharacterType[]) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_LIST, {}, (response: CharacterType[]) => {
|
||||
assetManagerStore.setCharacterTypeList(response)
|
||||
})
|
||||
})
|
||||
@ -93,7 +94,7 @@ function toTop() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit(SocketEvent.GM_CHARACTERTYPE_LIST, {}, (response: CharacterType[]) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_LIST, {}, (response: CharacterType[]) => {
|
||||
assetManagerStore.setCharacterTypeList(response)
|
||||
})
|
||||
})
|
||||
|
@ -46,6 +46,7 @@
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Item, ItemRarity, ItemType, Sprite } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
@ -81,7 +82,7 @@ if (selectedItem.value) {
|
||||
function removeItem() {
|
||||
if (!selectedItem.value) return
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_ITEM_REMOVE, { id: selectedItem.value.id }, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_ITEM_REMOVE, { id: selectedItem.value.id }, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to remove item')
|
||||
return
|
||||
@ -91,7 +92,7 @@ function removeItem() {
|
||||
}
|
||||
|
||||
function refreshItemList(unsetSelectedItem = true) {
|
||||
gameStore.connection?.emit(SocketEvent.GM_ITEM_LIST, {}, (response: Item[]) => {
|
||||
socketManager.emit(SocketEvent.GM_ITEM_LIST, {}, (response: Item[]) => {
|
||||
assetManagerStore.setItemList(response)
|
||||
|
||||
if (unsetSelectedItem) {
|
||||
@ -111,7 +112,7 @@ function saveItem() {
|
||||
spriteId: itemSpriteId.value
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_ITEM_UPDATE, itemData, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_ITEM_UPDATE, itemData, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to save item')
|
||||
return
|
||||
@ -133,7 +134,7 @@ watch(selectedItem, (item: Item | null) => {
|
||||
onMounted(() => {
|
||||
if (!selectedItem.value) return
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
|
@ -31,6 +31,7 @@
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Item } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
@ -49,13 +50,13 @@ const handleSearch = () => {
|
||||
}
|
||||
|
||||
const createNewItem = () => {
|
||||
gameStore.connection?.emit(SocketEvent.GM_ITEM_CREATE, {}, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_ITEM_CREATE, {}, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to create new item')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_ITEM_LIST, {}, (response: Item[]) => {
|
||||
socketManager.emit(SocketEvent.GM_ITEM_LIST, {}, (response: Item[]) => {
|
||||
assetManagerStore.setItemList(response)
|
||||
})
|
||||
})
|
||||
@ -89,7 +90,7 @@ function toTop() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit(SocketEvent.GM_ITEM_LIST, {}, (response: Item[]) => {
|
||||
socketManager.emit(SocketEvent.GM_ITEM_LIST, {}, (response: Item[]) => {
|
||||
assetManagerStore.setItemList(response)
|
||||
})
|
||||
})
|
||||
|
@ -59,12 +59,13 @@
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { MapObject } from '@/application/types'
|
||||
import { downloadCache } from '@/application/utilities'
|
||||
import ChipsInput from '@/components/forms/ChipsInput.vue'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { MapObjectStorage } from '@/storage/storages'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
||||
const selectedMapObject = computed(() => assetManagerStore.selectedMapObject)
|
||||
@ -96,18 +97,21 @@ if (selectedMapObject.value) {
|
||||
mapObjectFrameHeight.value = selectedMapObject.value.frameHeight
|
||||
}
|
||||
|
||||
function removeObject() {
|
||||
gameStore.connection?.emit(SocketEvent.GM_MAPOBJECT_REMOVE, { mapObject: selectedMapObject.value?.id }, (response: boolean) => {
|
||||
async function removeObject() {
|
||||
if (!selectedMapObject.value) return
|
||||
socketManager.emit(SocketEvent.GM_MAPOBJECT_REMOVE, { mapObjectId: selectedMapObject.value.id }, async (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to remove mapObject')
|
||||
return
|
||||
}
|
||||
refreshObjectList()
|
||||
|
||||
await downloadCache('map_objects', new MapObjectStorage())
|
||||
await refreshObjectList()
|
||||
})
|
||||
}
|
||||
|
||||
function refreshObjectList(unsetSelectedMapObject = true) {
|
||||
gameStore.connection?.emit(SocketEvent.GM_MAPOBJECT_LIST, {}, (response: MapObject[]) => {
|
||||
async function refreshObjectList(unsetSelectedMapObject = true) {
|
||||
socketManager.emit(SocketEvent.GM_MAPOBJECT_LIST, {}, (response: MapObject[]) => {
|
||||
assetManagerStore.setMapObjectList(response)
|
||||
|
||||
if (unsetSelectedMapObject) {
|
||||
@ -116,13 +120,13 @@ function refreshObjectList(unsetSelectedMapObject = true) {
|
||||
})
|
||||
}
|
||||
|
||||
function saveObject() {
|
||||
async function saveObject() {
|
||||
if (!selectedMapObject.value) {
|
||||
console.error('No mapObject selected')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(
|
||||
socketManager.emit(
|
||||
SocketEvent.GM_MAPOBJECT_UPDATE,
|
||||
{
|
||||
id: selectedMapObject.value.id,
|
||||
@ -135,12 +139,14 @@ function saveObject() {
|
||||
frameWidth: mapObjectFrameWidth.value,
|
||||
frameHeight: mapObjectFrameHeight.value
|
||||
},
|
||||
(response: boolean) => {
|
||||
async (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to save mapObject')
|
||||
return
|
||||
}
|
||||
refreshObjectList(false)
|
||||
|
||||
await downloadCache('map_objects', new MapObjectStorage())
|
||||
await refreshObjectList(false)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { MapObject } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
@ -48,13 +49,13 @@ const elementToScroll = ref()
|
||||
const handleFileUpload = (e: Event) => {
|
||||
const files = (e.target as HTMLInputElement).files
|
||||
if (!files) return
|
||||
gameStore.connection?.emit(SocketEvent.GM_MAPOBJECT_UPLOAD, files, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_MAPOBJECT_UPLOAD, files, (response: boolean) => {
|
||||
if (!response) {
|
||||
if (config.environment === 'development') console.error('Failed to upload map object')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_MAPOBJECT_LIST, {}, (response: MapObject[]) => {
|
||||
socketManager.emit(SocketEvent.GM_MAPOBJECT_LIST, {}, (response: MapObject[]) => {
|
||||
assetManagerStore.setMapObjectList(response)
|
||||
})
|
||||
})
|
||||
@ -93,7 +94,7 @@ function toTop() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit(SocketEvent.GM_MAPOBJECT_LIST, {}, (response: MapObject[]) => {
|
||||
socketManager.emit(SocketEvent.GM_MAPOBJECT_LIST, {}, (response: MapObject[]) => {
|
||||
assetManagerStore.setMapObjectList(response)
|
||||
})
|
||||
})
|
||||
|
@ -7,6 +7,15 @@
|
||||
<input v-model="spriteName" class="input-field" type="text" name="name" placeholder="New sprite" />
|
||||
</div>
|
||||
|
||||
<div class="form-field-half">
|
||||
<label class="mb-1.5 font-titles" for="name">Width override</label>
|
||||
<input v-model="spriteWidth" class="input-field" type="number" name="width" />
|
||||
</div>
|
||||
<div class="form-field-half">
|
||||
<label class="mb-1.5 font-titles" for="name">Height override</label>
|
||||
<input v-model="spriteHeight" class="input-field" type="number" name="height" />
|
||||
</div>
|
||||
|
||||
<div class="w-full flex gap-2 mt-2 pb-4 relative">
|
||||
<button class="btn-cyan px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="saveSprite">Save</button>
|
||||
<button class="btn-red px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="deleteSprite">Delete</button>
|
||||
@ -69,21 +78,23 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Sprite, SpriteAction, UUID } from '@/application/types'
|
||||
import { uuidv4 } from '@/application/utilities'
|
||||
import type { Sprite, SpriteAction } from '@/application/types'
|
||||
import { downloadCache, uuidv4 } from '@/application/utilities'
|
||||
import SpriteActionsInput from '@/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue'
|
||||
import SpritePreview from '@/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue'
|
||||
import Accordion from '@/components/utilities/Accordion.vue'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { SpriteStorage } from '@/storage/storages'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
||||
const selectedSprite = computed(() => assetManagerStore.selectedSprite)
|
||||
|
||||
const spriteName = ref('')
|
||||
const spriteWidth = ref(0)
|
||||
const spriteHeight = ref(0)
|
||||
const spriteActions = ref<SpriteAction[]>([])
|
||||
const isModalOpen = ref(false)
|
||||
const selectedAction = ref<SpriteAction | null>(null)
|
||||
@ -94,31 +105,37 @@ if (!selectedSprite.value) {
|
||||
|
||||
if (selectedSprite.value) {
|
||||
spriteName.value = selectedSprite.value.name
|
||||
spriteWidth.value = selectedSprite.value.width
|
||||
spriteHeight.value = selectedSprite.value.height
|
||||
spriteActions.value = sortSpriteActions(selectedSprite.value.spriteActions)
|
||||
}
|
||||
|
||||
function deleteSprite() {
|
||||
gameStore.connection?.emit(SocketEvent.GM_SPRITE_DELETE, { id: selectedSprite.value?.id }, (response: boolean) => {
|
||||
async function deleteSprite() {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_DELETE, { id: selectedSprite.value?.id }, async (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to delete sprite')
|
||||
return
|
||||
}
|
||||
refreshSpriteList()
|
||||
|
||||
await downloadCache('sprites', new SpriteStorage())
|
||||
await refreshSpriteList()
|
||||
})
|
||||
}
|
||||
|
||||
function copySprite() {
|
||||
gameStore.connection?.emit(SocketEvent.GM_SPRITE_COPY, { id: selectedSprite.value?.id }, (response: boolean) => {
|
||||
async function copySprite() {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_COPY, { id: selectedSprite.value?.id }, async (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to copy sprite')
|
||||
return
|
||||
}
|
||||
refreshSpriteList(false)
|
||||
|
||||
await downloadCache('sprites', new SpriteStorage())
|
||||
await refreshSpriteList(false)
|
||||
})
|
||||
}
|
||||
|
||||
function refreshSpriteList(unsetSelectedSprite = true) {
|
||||
gameStore.connection?.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
async function refreshSpriteList(unsetSelectedSprite = true) {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
|
||||
if (unsetSelectedSprite) {
|
||||
@ -127,7 +144,7 @@ function refreshSpriteList(unsetSelectedSprite = true) {
|
||||
})
|
||||
}
|
||||
|
||||
function saveSprite() {
|
||||
async function saveSprite() {
|
||||
if (!selectedSprite.value) {
|
||||
console.error('No sprite selected')
|
||||
return
|
||||
@ -136,6 +153,8 @@ function saveSprite() {
|
||||
const updatedSprite = {
|
||||
id: selectedSprite.value.id,
|
||||
name: spriteName.value,
|
||||
width: spriteWidth.value,
|
||||
height: spriteHeight.value,
|
||||
spriteActions:
|
||||
spriteActions.value?.map((action) => {
|
||||
return {
|
||||
@ -150,12 +169,14 @@ function saveSprite() {
|
||||
}) ?? []
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_SPRITE_UPDATE, updatedSprite, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_UPDATE, updatedSprite, async (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to save sprite')
|
||||
return
|
||||
}
|
||||
refreshSpriteList(false)
|
||||
|
||||
await downloadCache('sprites', new SpriteStorage())
|
||||
await refreshSpriteList(false)
|
||||
})
|
||||
}
|
||||
|
||||
@ -211,6 +232,8 @@ function handleTempOffsetChange(action: SpriteAction, index: number, offset: { x
|
||||
watch(selectedSprite, (sprite: Sprite | null) => {
|
||||
if (!sprite) return
|
||||
spriteName.value = sprite.name
|
||||
spriteWidth.value = sprite.width
|
||||
spriteHeight.value = sprite.height
|
||||
spriteActions.value = sortSpriteActions(sprite.spriteActions)
|
||||
})
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Sprite } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
@ -41,13 +42,13 @@ const hasScrolled = ref(false)
|
||||
const elementToScroll = ref()
|
||||
|
||||
function newButtonClickHandler() {
|
||||
gameStore.connection?.emit(SocketEvent.GM_SPRITE_CREATE, {}, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_CREATE, {}, (response: boolean) => {
|
||||
if (!response) {
|
||||
if (config.environment === 'development') console.error('Failed to create new sprite')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
@ -86,7 +87,7 @@ function toTop() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
|
@ -26,15 +26,14 @@
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Tile } from '@/application/types'
|
||||
import { downloadCache } from '@/application/utilities'
|
||||
import ChipsInput from '@/components/forms/ChipsInput.vue'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { TileStorage } from '@/storage/storages'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
const tileStorage = new TileStorage()
|
||||
|
||||
const selectedTile = computed(() => assetManagerStore.selectedTile)
|
||||
|
||||
@ -57,18 +56,19 @@ watch(selectedTile, (tile: Tile | null) => {
|
||||
})
|
||||
|
||||
async function deleteTile() {
|
||||
gameStore.connection?.emit(SocketEvent.GM_TILE_DELETE, { id: selectedTile.value?.id }, async (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_TILE_DELETE, { id: selectedTile.value?.id }, async (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to delete tile')
|
||||
return
|
||||
}
|
||||
await tileStorage.delete(selectedTile.value!.id)
|
||||
refreshTileList()
|
||||
|
||||
await downloadCache('tiles', new TileStorage())
|
||||
await refreshTileList()
|
||||
})
|
||||
}
|
||||
|
||||
function refreshTileList(unsetSelectedTile = true) {
|
||||
gameStore.connection?.emit(SocketEvent.GM_TILE_LIST, {}, (response: Tile[]) => {
|
||||
async function refreshTileList(unsetSelectedTile = true) {
|
||||
socketManager.emit(SocketEvent.GM_TILE_LIST, {}, (response: Tile[]) => {
|
||||
assetManagerStore.setTileList(response)
|
||||
|
||||
if (unsetSelectedTile) {
|
||||
@ -77,25 +77,27 @@ function refreshTileList(unsetSelectedTile = true) {
|
||||
})
|
||||
}
|
||||
|
||||
function saveTile() {
|
||||
async function saveTile() {
|
||||
if (!selectedTile.value) {
|
||||
console.error('No tile selected')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(
|
||||
socketManager.emit(
|
||||
'gm:tile:update',
|
||||
{
|
||||
id: selectedTile.value.id,
|
||||
name: tileName.value,
|
||||
tags: tileTags.value
|
||||
},
|
||||
(response: boolean) => {
|
||||
async (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to save tile')
|
||||
return
|
||||
}
|
||||
refreshTileList(false)
|
||||
|
||||
await downloadCache('tiles', new TileStorage())
|
||||
await refreshTileList(false)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Tile } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
@ -48,13 +49,13 @@ const elementToScroll = ref()
|
||||
const handleFileUpload = (e: Event) => {
|
||||
const files = (e.target as HTMLInputElement).files
|
||||
if (!files) return
|
||||
gameStore.connection?.emit(SocketEvent.GM_TILE_UPLOAD, files, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_TILE_UPLOAD, files, (response: boolean) => {
|
||||
if (!response) {
|
||||
if (config.environment === 'development') console.error('Failed to upload tile')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_TILE_LIST, {}, (response: Tile[]) => {
|
||||
socketManager.emit(SocketEvent.GM_TILE_LIST, {}, (response: Tile[]) => {
|
||||
assetManagerStore.setTileList(response)
|
||||
})
|
||||
})
|
||||
@ -93,7 +94,7 @@ function toTop() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit(SocketEvent.GM_TILE_LIST, {}, (response: Tile[]) => {
|
||||
socketManager.emit(SocketEvent.GM_TILE_LIST, {}, (response: Tile[]) => {
|
||||
assetManagerStore.setTileList(response)
|
||||
})
|
||||
})
|
||||
|
@ -10,9 +10,9 @@ import MapEventTiles from '@/components/gameMaster/mapEditor/mapPartials/MapEven
|
||||
import MapTiles from '@/components/gameMaster/mapEditor/mapPartials/MapTiles.vue'
|
||||
import PlacedMapObjects from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { cloneArray, createTileArray, createTileLayer, createTileMap, placeTiles } from '@/services/mapService'
|
||||
import { cloneArray, createTileLayer, createTileMap, placeTiles } from '@/services/mapService'
|
||||
import { TileStorage } from '@/storage/storages'
|
||||
import { useManualRefHistory, useRefHistory } from '@vueuse/core'
|
||||
import { useRefHistory } from '@vueuse/core'
|
||||
import { useScene } from 'phavuer'
|
||||
import { onBeforeUnmount, onMounted, onUnmounted, ref, shallowRef, useTemplateRef, watch } from 'vue'
|
||||
|
||||
@ -58,6 +58,7 @@ watch(
|
||||
mapTiles.value!.clearTiles()
|
||||
eventTiles.value!.clearTiles()
|
||||
mapEditor.currentMap.value.placedMapObjects = []
|
||||
mapEditor.currentMap.value.mapEventTiles = []
|
||||
updateAndCommit(mapEditor.currentMap.value)
|
||||
mapEditor.resetClearTilesFlag()
|
||||
}
|
||||
|
@ -88,20 +88,17 @@ function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
|
||||
if (existingEventTile) return
|
||||
|
||||
// If teleport, check if there is a selected map
|
||||
if (mapEditor.drawMode.value === 'teleport' && !mapEditor.teleportSettings.value.toMapId) return
|
||||
|
||||
console.log(mapEditor.teleportSettings.value.toMapId)
|
||||
if (mapEditor.drawMode.value === 'teleport' && !mapEditor.teleportSettings.value.toMap) return
|
||||
|
||||
const newEventTile = {
|
||||
id: uuidv4() as UUID,
|
||||
map: map.id,
|
||||
type: mapEditor.drawMode.value === 'blocking tile' ? MapEventTileType.BLOCK : MapEventTileType.TELEPORT,
|
||||
positionX: tile.x,
|
||||
positionY: tile.y,
|
||||
teleport:
|
||||
mapEditor.drawMode.value === 'teleport'
|
||||
? {
|
||||
toMapId: mapEditor.teleportSettings.value.toMapId,
|
||||
toMap: mapEditor.teleportSettings.value.toMap,
|
||||
toPositionX: mapEditor.teleportSettings.value.toPositionX,
|
||||
toPositionY: mapEditor.teleportSettings.value.toPositionY,
|
||||
toRotation: mapEditor.teleportSettings.value.toRotation
|
||||
|
@ -129,7 +129,7 @@ function moveMapObject(id: string, map: MapT) {
|
||||
|
||||
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
||||
if (!tile) return
|
||||
console.log(id)
|
||||
|
||||
map.placedMapObjects.map((placed) => {
|
||||
if (placed.id === id) {
|
||||
placed.positionX = tile.x
|
||||
|
@ -39,6 +39,7 @@ import { SocketEvent } from '@/application/enums'
|
||||
import type { Map } from '@/application/types'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { MapStorage } from '@/storage/storages'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { ref, useTemplateRef } from 'vue'
|
||||
@ -57,7 +58,7 @@ const pvp = ref(false)
|
||||
defineExpose({ open: () => modalRef.value?.open() })
|
||||
|
||||
async function submit() {
|
||||
gameStore.connection?.emit(SocketEvent.GM_MAP_CREATE, { name: name.value, width: width.value, height: height.value }, async (response: Map | false) => {
|
||||
socketManager.emit(SocketEvent.GM_MAP_CREATE, { name: name.value, width: width.value, height: height.value }, async (response: Map | false) => {
|
||||
if (!response) {
|
||||
return
|
||||
}
|
||||
|
@ -1,17 +1,15 @@
|
||||
<template>
|
||||
<Modal ref="modalRef" :is-resizable="false" :modal-width="300" :modal-height="360" bg-style="none">
|
||||
<template #modalHeader>
|
||||
<h3 class="text-lg text-white">Maps</h3>
|
||||
<div class="flex items-center">
|
||||
<button class="btn-cyan w-7 h-7 font-normal flex items-center justify-center" @click="createMapModal?.open">+</button>
|
||||
<h3 class="text-lg text-white ml-2">Maps</h3>
|
||||
</div>
|
||||
</template>
|
||||
<template #modalBody>
|
||||
<div class="my-4 mx-auto h-full">
|
||||
<div class="text-center mb-4 px-2 flex gap-2.5">
|
||||
<button class="btn-cyan py-1.5 min-w-[calc(50%_-_5px)]" @click="fetchMaps">Refresh</button>
|
||||
<button class="btn-cyan py-1.5 min-w-[calc(50%_-_5px)]" @click="createMapModal?.open">New</button>
|
||||
</div>
|
||||
<div class="overflow-y-auto h-[calc(100%-20px)]">
|
||||
<div class="mx-auto h-full">
|
||||
<div class="overflow-y-auto h-[calc(100%)]">
|
||||
<div class="relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap" v-for="(map, index) in mapList" :key="map.id">
|
||||
<div class="absolute left-0 top-0 w-full h-px bg-gray-500" v-if="index === 0"></div>
|
||||
<div class="flex gap-3 items-center w-full" @click="() => loadMap(map.id)">
|
||||
<span>{{ map.name }}</span>
|
||||
<span class="ml-auto gap-1 flex">
|
||||
@ -24,7 +22,6 @@
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
|
||||
<CreateMap ref="createMapModal" @create="fetchMaps" />
|
||||
</template>
|
||||
|
||||
@ -34,6 +31,7 @@ import type { Map } from '@/application/types'
|
||||
import CreateMap from '@/components/gameMaster/mapEditor/partials/CreateMap.vue'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { MapStorage } from '@/storage/storages'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onMounted, ref, useTemplateRef } from 'vue'
|
||||
@ -61,14 +59,14 @@ async function fetchMaps() {
|
||||
}
|
||||
|
||||
function loadMap(id: string) {
|
||||
gameStore.connection?.emit(SocketEvent.GM_MAP_REQUEST, { mapId: id }, (response: Map) => {
|
||||
socketManager.emit(SocketEvent.GM_MAP_REQUEST, { mapId: id }, (response: Map) => {
|
||||
mapEditor.loadMap(response)
|
||||
})
|
||||
modalRef.value?.close()
|
||||
}
|
||||
|
||||
async function deleteMap(id: string) {
|
||||
gameStore.connection?.emit(SocketEvent.GM_MAP_DELETE, { mapId: id }, async (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_MAP_DELETE, { mapId: id }, async (response: boolean) => {
|
||||
if (!response) {
|
||||
gameStore.addNotification({
|
||||
title: 'Error',
|
||||
|
@ -45,6 +45,7 @@ import { SocketEvent } from '@/application/enums'
|
||||
import type { MapObject, Map as MapT, PlacedMapObject } from '@/application/types'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { MapObjectStorage } from '@/storage/storages'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onMounted, ref } from 'vue'
|
||||
@ -81,7 +82,7 @@ const handleDelete = () => {
|
||||
async function handleUpdate() {
|
||||
if (!mapObject.value) return
|
||||
|
||||
gameStore.connection?.emit(
|
||||
socketManager.emit(
|
||||
SocketEvent.GM_MAPOBJECT_UPDATE,
|
||||
{
|
||||
id: props.placedMapObject.mapObject as string,
|
||||
|
@ -26,7 +26,7 @@
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="toMap">Map to teleport to</label>
|
||||
<select v-model="toMapId" class="input-field" name="toMap" id="toMap">
|
||||
<select v-model="toMap" class="input-field" name="toMap" id="toMap">
|
||||
<option :value="null">Select map</option>
|
||||
<option v-for="map in mapList" :key="map.id" :value="map.id">{{ map.name }}</option>
|
||||
</select>
|
||||
@ -55,7 +55,7 @@ defineExpose({
|
||||
open: () => modalRef.value?.open()
|
||||
})
|
||||
|
||||
const { toPositionX, toPositionY, toRotation, toMapId } = useRefTeleportSettings()
|
||||
const { toPositionX, toPositionY, toRotation, toMap } = useRefTeleportSettings()
|
||||
|
||||
function useRefTeleportSettings() {
|
||||
const settings = mapEditor.teleportSettings.value
|
||||
@ -63,18 +63,18 @@ function useRefTeleportSettings() {
|
||||
toPositionX: ref(settings.toPositionX),
|
||||
toPositionY: ref(settings.toPositionY),
|
||||
toRotation: ref(settings.toRotation),
|
||||
toMapId: ref(settings.toMapId)
|
||||
toMap: ref(settings.toMap)
|
||||
}
|
||||
}
|
||||
|
||||
watch([toPositionX, toPositionY, toRotation, toMapId], updateTeleportSettings)
|
||||
watch([toPositionX, toPositionY, toRotation, toMap], updateTeleportSettings)
|
||||
|
||||
function updateTeleportSettings() {
|
||||
mapEditor.setTeleportSettings({
|
||||
toPositionX: toPositionX.value,
|
||||
toPositionY: toPositionY.value,
|
||||
toRotation: toRotation.value,
|
||||
toMapId: toMapId.value
|
||||
toMap: toMap.value
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ const selectPencilOpen = ref(false)
|
||||
const selectEraserOpen = ref(false)
|
||||
const isContinuousDrawingEnabled = ref<Boolean>(false)
|
||||
const isShowPlacedMapObjectPreviewEnabled = ref<Boolean>(mapEditor.isPlacedMapObjectPreviewEnabled.value)
|
||||
const listOpen = computed(() => mapEditor.tool.value === 'pencil' && (mapEditor.drawMode.value === 'tile' || mapEditor.drawMode.value === 'map_object'))
|
||||
const listOpen = computed(() => (mapEditor.tool.value === 'pencil' && (mapEditor.drawMode.value === 'tile' || mapEditor.drawMode.value === 'map_object')) || mapEditor.tool.value === 'paint')
|
||||
|
||||
// drawMode
|
||||
function setDrawMode(value: string) {
|
||||
@ -148,6 +148,7 @@ function handleModeClick(mode: string, type: 'pencil' | 'eraser') {
|
||||
|
||||
function handleClick(tool: string) {
|
||||
mapEditor.setTool(tool)
|
||||
if (tool === 'paint') mapEditor.setDrawMode('tile')
|
||||
selectPencilOpen.value = tool === 'pencil' ? !selectPencilOpen.value : false
|
||||
selectEraserOpen.value = tool === 'eraser' ? !selectEraserOpen.value : false
|
||||
if (mapEditor.drawMode.value === 'teleport') emit('open-teleport')
|
||||
|
@ -26,6 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { login } from '@/services/authenticationService'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||
@ -53,7 +54,7 @@ async function submit() {
|
||||
formError.value = response.error
|
||||
return
|
||||
}
|
||||
gameStore.setToken(response.token)
|
||||
socketManager.setToken(response.token)
|
||||
gameStore.initConnection()
|
||||
return true // Indicate success
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { login, register } from '@/services/authenticationService'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||
@ -67,7 +68,7 @@ async function submit() {
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.setToken(loginResponse.token)
|
||||
socketManager.setToken(loginResponse.token)
|
||||
gameStore.initConnection()
|
||||
}
|
||||
</script>
|
||||
|
@ -10,8 +10,9 @@
|
||||
<div class="filler"></div>
|
||||
<div class="w-2/3 max-w-[860px]" v-if="!isLoading">
|
||||
<div class="mb-5 flex flex-col gap-1">
|
||||
<h1 class="text-white font-bold">SELECT CHARACTER TO PLAY</h1>
|
||||
<p class="m-0">Maximum of 4 characters can be created per player</p>
|
||||
<h1 class="text-white font-bold">{{ isCreatingCharacter ? 'CREATE CHARACTER' : 'SELECT CHARACTER TO PLAY' }}</h1>
|
||||
<p class="m-0" v-if="!isCreatingCharacter">Maximum of 4 characters can be created per player</p>
|
||||
<p class="m-0" v-if="isCreatingCharacter">Customize your new character</p>
|
||||
</div>
|
||||
<div class="flex w-full max-lg:flex-col lg:h-[400px] default-border rounded-md bg-gray">
|
||||
<div class="lg:min-w-[285px] max-lg:min-h-[383px] lg:w-1/3 h-full bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover bg-center border-0 max-lg:border-b lg:border-r border-solid border-gray-500 max-lg:rounded-t-md lg:rounded-l-md relative">
|
||||
@ -20,66 +21,93 @@
|
||||
<img src="/assets/placeholders/head.png" class="w-9 h-9 object-contain center-element" alt="Player head" />
|
||||
<input class="h-full w-full absolute m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 btn-sound" type="radio" name="character" :value="character.id" v-model="selectedCharacterId" />
|
||||
</div>
|
||||
<div class="character relative rounded default-border w-12 h-12 bg-[url('/assets/ui-texture.png')]" :class="{ active: characters.length == 0 }" v-if="characters.length < 4">
|
||||
<button class="p-0 h-full w-full flex flex-col justify-between focus-visible:outline-offset-0 btn-sound" @click="isCreateNewCharacterModalOpen = true">
|
||||
<img class="w-6 h-6 object-contain center-element btn-sound" draggable="false" src="/assets/icons/plus-icon.svg" />
|
||||
<div class="character relative rounded default-border w-12 h-12 bg-[url('/assets/ui-texture.png')]" :class="{ active: isCreatingCharacter }" v-if="characters.length < characterCreationSettings.maxCharacters">
|
||||
<button class="p-0 h-full w-full flex flex-col justify-between focus-visible:outline-offset-0 btn-sound" @click="startCharacterCreation">
|
||||
<img class="w-6 h-6 object-contain center-element btn-sound" draggable="false" src="/assets/icons/plus-icon.svg" alt="Add character" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="py-6 px-8 h-[calc(100%_-_48px)] flex flex-col items-center gap-6 justify-center" v-if="selectedCharacterId">
|
||||
<input class="input-field w-[158px]" type="text" name="name" :placeholder="characters.find((c) => c.id == selectedCharacterId)?.name" />
|
||||
<div class="flex flex-col gap-4 items-center">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="bg-[url('/assets/ui-elements/character-select-ui-shape.svg')] w-[190px] h-52 bg-no-repeat bg-center flex items-center justify-between">
|
||||
<div class="py-6 px-8 h-[calc(100%_-_48px)] flex flex-col items-center gap-6 justify-center">
|
||||
<template v-if="selectedCharacterId && !isCreatingCharacter">
|
||||
<input class="input-field w-[158px]" type="text" name="name" :placeholder="characters.find((c) => c.id == selectedCharacterId)?.name" v-model="newNickname" />
|
||||
<div class="flex flex-col gap-4 items-center">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="bg-[url('/assets/ui-elements/character-select-ui-shape.svg')] w-[190px] h-52 bg-no-repeat bg-center flex items-center justify-between">
|
||||
<button class="ml-6 w-4 h-8 p-0">
|
||||
<img src="/assets/icons/triangle-icon.svg" class="w-3 h-3.5 m-auto" alt="Arrow left" />
|
||||
</button>
|
||||
<img class="w-24 object-contain mb-3.5 max-h-[70%]" alt="Player avatar" :src="config.server_endpoint + '/avatar/s/' + characters.find((c) => c.id === selectedCharacterId)?.characterType + '/' + (selectedHairId ?? 'default')" />
|
||||
<button class="mr-6 w-4 h-8 p-0">
|
||||
<img src="/assets/icons/triangle-icon.svg" class="w-3 h-3.5 -scale-x-100" alt="Arrow right" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="isCreatingCharacter">
|
||||
<div class="flex flex-col gap-4 items-center">
|
||||
<input class="input-field" v-model="newCharacterName" name="name" id="name" placeholder="Enter a nickname..." />
|
||||
<div class="bg-[url('/assets/ui-elements/character-select-ui-shape.svg')] w-[190px] h-52 bg-no-repeat bg-center flex items-center justify-center">
|
||||
<button class="ml-6 w-4 h-8 p-0">
|
||||
<img src="/assets/icons/triangle-icon.svg" class="w-3 h-3.5 m-auto" alt="Arrow left" />
|
||||
</button>
|
||||
<img class="w-24 object-contain mb-3.5 max-h-[70%]" alt="Player avatar" :src="config.server_endpoint + '/avatar/s/' + characters.find((c) => c.id === selectedCharacterId)?.characterType + '/' + (selectedHairId ?? 'default')" />
|
||||
<img class="w-24 object-contain mb-3.5 max-h-[70%]" alt="Player avatar" :src="config.server_endpoint + '/avatar/s/' + defaultCharacterTypeId + '/' + (selectedHairId ?? 'default')" />
|
||||
<button class="mr-6 w-4 h-8 p-0">
|
||||
<img src="/assets/icons/triangle-icon.svg" class="w-3 h-3.5 -scale-x-100" alt="Arrow right" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex justify-between w-[190px]">
|
||||
<button class="btn-empty flex gap-2" :class="{ selected: selectedGender === 'MALE' }" @click="selectedGender = 'MALE'">
|
||||
<img src="/assets/icons/male-icon.svg" class="w-4 h-4 m-auto" alt="Male symbol" />
|
||||
<span class="text-white">Male</span>
|
||||
</button>
|
||||
<button class="btn-empty flex gap-2" :class="{ selected: selectedGender === 'FEMALE' }" @click="selectedGender = 'FEMALE'">
|
||||
<img src="/assets/icons/male-icon.svg" class="w-4 h-4 m-auto" alt="Female symbol" />
|
||||
<span class="text-white">Female</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TODO: update gender on (selected) character -->
|
||||
<!-- <div class="flex justify-between w-[190px]">-->
|
||||
<!-- <button class="btn-empty flex gap-2" :class="{ selected: characters.find((c) => c.id == selectedCharacterId)?.characterType?.gender === 'MALE' }">-->
|
||||
<!-- <img src="/assets/icons/male-icon.svg" class="w-4 h-4 m-auto" alt="Male symbol" />-->
|
||||
<!-- <span class="text-white">Male</span>-->
|
||||
<!-- </button>-->
|
||||
<!-- <button class="btn-empty flex gap-2" :class="{ selected: characters.find((c) => c.id == selectedCharacterId)?.characterType?.gender === 'FEMALE' }">-->
|
||||
<!-- <img src="/assets/icons/male-icon.svg" class="w-4 h-4 m-auto" alt="Male symbol" />-->
|
||||
<!-- <span class="text-white">Female</span>-->
|
||||
<!-- </button>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 lg:w-2/3 max-lg:min-h-[212px] h-full bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover bg-center max-lg:rounded-bl-md rounded-r-md">
|
||||
<div class="py-6 px-8 h-[calc(100%_-_48px)] flex flex-col items-center gap-10" v-if="selectedCharacterId">
|
||||
<div class="py-6 px-8 h-[calc(100%_-_48px)] flex flex-col items-center gap-10" v-if="selectedCharacterId || isCreatingCharacter">
|
||||
<div class="flex flex-col gap-3 w-full">
|
||||
<span class="text-sm">Hairstyle</span>
|
||||
<div class="flex gap-2 flex-wrap max-h-20 overflow-y-auto scrollbar">
|
||||
<span class="text-sm">Hair color</span>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<div
|
||||
class="hair-deselect relative flex justify-center items-center bg-gray default-border w-[18px] h-[18px] p-2 rounded-sm hover:bg-gray-500 hover:border-gray-400 focus-visible:outline-none focus-visible:border-white focus-visible:bg-cyan has-[:checked]:bg-cyan has-[:checked]:border-transparent"
|
||||
>
|
||||
<img src="/assets/icons/x-button-gray.svg" class="w-4 h-4" alt="Empty button" />
|
||||
<input type="radio" name="hair" :value="null" v-model="selectedHairId" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" />
|
||||
<input type="radio" name="hair-color" :value="null" v-model="selectedHairColor" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" />
|
||||
</div>
|
||||
<!-- TODO #255: make radio button so we can set a value, do the same with swatches -->
|
||||
<div
|
||||
v-for="hair in characterHairs"
|
||||
class="relative flex justify-center items-center bg-gray default-border w-[18px] h-[18px] p-2 rounded-sm hover:bg-gray-500 hover:border-gray-400 focus-visible:outline-none focus-visible:border-gray-300 focus-visible:bg-gray-500 has-[:checked]:bg-cyan has-[:checked]:border-transparent"
|
||||
v-for="color in uniqueHairColors"
|
||||
class="relative flex justify-center items-center default-border w-[18px] h-[18px] p-2 rounded-sm hover:bg-gray-500 hover:border-gray-400 focus-visible:outline-none focus-visible:border-gray-300 focus-visible:bg-gray-500 has-[:checked]:bg-cyan has-[:checked]:border-transparent"
|
||||
>
|
||||
<img class="h-4 object-contain" :src="config.server_endpoint + '/textures/sprites/' + hair.sprite + '/front.png'" alt="Hair sprite" />
|
||||
<input type="radio" name="hair" :value="hair.id" v-model="selectedHairId" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" />
|
||||
<div class="w-full h-full rounded-sm" :style="getHairColorStyle(color)"></div>
|
||||
<input type="radio" name="hair-color" :value="color" v-model="selectedHairColor" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 w-full">
|
||||
<span class="text-sm">Hair color</span>
|
||||
<span class="text-sm">Hairstyle</span>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<!-- TODO: replace with hair colors -->
|
||||
<input type="radio" name="hair-color" v-for="n in 10" class="bg-red w-6 h-6 m-0 rounded-sm hover:cursor-pointer checked:outline checked:outline-1 checked:outline-white" />
|
||||
<div
|
||||
class="hair-deselect relative flex justify-center items-center bg-gray default-border w-[18px] h-[18px] p-2 rounded-sm hover:bg-gray-500 hover:border-gray-400 focus-visible:outline-none focus-visible:border-white focus-visible:bg-cyan has-[:checked]:bg-cyan has-[:checked]:border-transparent"
|
||||
>
|
||||
<img src="/assets/icons/x-button-gray.svg" class="w-4 h-4" alt="Empty button" />
|
||||
<input type="radio" name="hair" :value="null" v-model="selectedHairId" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" />
|
||||
</div>
|
||||
<div
|
||||
v-for="hair in filteredHairs"
|
||||
class="relative flex justify-center items-center bg-gray default-border w-[18px] h-[18px] p-2 rounded-sm hover:bg-gray-500 hover:border-gray-400 focus-visible:outline-none focus-visible:border-gray-300 focus-visible:bg-gray-500 has-[:checked]:bg-cyan has-[:checked]:border-transparent overflow-hidden"
|
||||
>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<img class="h-16 object-contain scale-[1] mt-8 origin-center" :src="config.server_endpoint + '/textures/sprites/' + hair.sprite + '/front.png'" alt="Hair sprite" />
|
||||
</div>
|
||||
<input type="radio" name="hair" :value="hair.id" v-model="selectedHairId" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -89,77 +117,102 @@
|
||||
<div v-else>
|
||||
<img class="w-20 invert-80" src="/assets/icons/loading-icon1.svg" alt="Loading" />
|
||||
</div>
|
||||
|
||||
<div class="w-2/3 button-wrapper flex self-center justify-center lg:justify-end gap-4 max-w-[860px]" v-if="!isLoading">
|
||||
<button class="btn-empty min-w-48" @click.stop="gameStore.disconnectSocket()">Back</button>
|
||||
<button class="btn-cyan min-w-48 disabled:bg-cyan-800 disabled:cursor-not-allowed" :disabled="!selectedCharacterId" @click="loginWithCharacter()">Play now</button>
|
||||
<template v-if="!isCreatingCharacter">
|
||||
<button class="btn-empty min-w-48" @click.stop="gameStore.disconnectSocket()">Back</button>
|
||||
<button class="btn-cyan min-w-48 disabled:bg-cyan-800 disabled:cursor-not-allowed" :disabled="!selectedCharacterId" @click="loginWithCharacter()">Play now</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button class="btn-empty min-w-48" @click="cancelCharacterCreation">Cancel</button>
|
||||
<button class="btn-cyan min-w-48" @click="createCharacter">Create</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CREATE CHARACTER MODAL -->
|
||||
<Modal :isModalOpen="isCreateNewCharacterModalOpen" @modal:close="isCreateNewCharacterModalOpen = false" :modal-width="430" :modal-height="275">
|
||||
<template #modalHeader>
|
||||
<h3 class="m-0 font-medium text-white">Create your character</h3>
|
||||
</template>
|
||||
|
||||
<template #modalBody>
|
||||
<div class="p-4 h-[calc(100%_-_32px)]">
|
||||
<form method="post" @submit.prevent="createCharacter" class="h-full flex flex-col justify-between">
|
||||
<div class="form-field-full">
|
||||
<label for="name" class="text-white">Nickname</label>
|
||||
<input class="input-field" v-model="newCharacterName" name="name" id="name" placeholder="Enter a nickname..." />
|
||||
</div>
|
||||
<div class="grid grid-flow-col justify-stretch gap-4">
|
||||
<button type="button" class="btn-empty py-1.5 px-4 inline-block" @click.prevent="isCreateNewCharacterModalOpen = false">Cancel</button>
|
||||
<button class="btn-cyan py-1.5 px-4 inline-block" type="submit">Create</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import { type CharacterHair, type Character as CharacterT, type Map } from '@/application/types'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useSoundComposable } from '@/composables/useSoundComposable'
|
||||
import { CharacterHairStorage } from '@/storage/storages'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { CharacterHairStorage, CharacterTypeStorage } from '@/storage/storages'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const characterCreationSettings = {
|
||||
maxCharacters: 4,
|
||||
minNameLength: 3,
|
||||
maxNameLength: 20,
|
||||
defaultGender: 'MALE' as const
|
||||
}
|
||||
|
||||
const { playSound } = useSoundComposable()
|
||||
const gameStore = useGameStore()
|
||||
const isLoading = ref<boolean>(true)
|
||||
const characters = ref<CharacterT[]>([])
|
||||
const selectedCharacterId = ref<string | null>(null)
|
||||
const isCreateNewCharacterModalOpen = ref<boolean>(false)
|
||||
const newNickname = ref<string>('')
|
||||
const newCharacterName = ref<string>('')
|
||||
const characterHairs = ref<CharacterHair[]>([])
|
||||
const selectedHairId = ref<string | null>(null)
|
||||
const defaultCharacterTypeId = ref<string>('')
|
||||
const isCreatingCharacter = ref<boolean>(false)
|
||||
const selectedGender = ref()
|
||||
|
||||
const selectedHairColor = ref<string | null>(null)
|
||||
const uniqueHairColors = computed(() => {
|
||||
return [...new Set(characterHairs.value.map((hair) => hair.color))]
|
||||
})
|
||||
|
||||
const filteredHairs = computed(() => {
|
||||
if (!selectedHairColor.value) return characterHairs.value
|
||||
return characterHairs.value.filter((hair) => hair.color === selectedHairColor.value)
|
||||
})
|
||||
|
||||
function getHairColorStyle(color: string | null) {
|
||||
return {
|
||||
backgroundColor: color,
|
||||
border: selectedHairColor.value === color ? '1px solid white' : '1px solid rgba(255, 255, 255, 0.2)'
|
||||
}
|
||||
}
|
||||
|
||||
function startCharacterCreation() {
|
||||
isCreatingCharacter.value = true
|
||||
selectedCharacterId.value = null
|
||||
newCharacterName.value = ''
|
||||
selectedHairId.value = null
|
||||
selectedHairColor.value = null
|
||||
selectedGender.value = characterCreationSettings.defaultGender
|
||||
}
|
||||
|
||||
function cancelCharacterCreation() {
|
||||
isCreatingCharacter.value = false
|
||||
newCharacterName.value = ''
|
||||
selectedHairId.value = null
|
||||
selectedHairColor.value = null
|
||||
}
|
||||
|
||||
// Fetch characters
|
||||
setTimeout(() => {
|
||||
console.log(SocketEvent.CHARACTER_LIST)
|
||||
gameStore.connection?.emit(SocketEvent.CHARACTER_LIST)
|
||||
socketManager.emit(SocketEvent.CHARACTER_LIST)
|
||||
}, 750)
|
||||
|
||||
gameStore.connection?.on(SocketEvent.CHARACTER_LIST, (data: any) => {
|
||||
socketManager.on(SocketEvent.CHARACTER_LIST, (data: any) => {
|
||||
characters.value = data
|
||||
isLoading.value = false
|
||||
})
|
||||
|
||||
// Select character logics
|
||||
function loginWithCharacter() {
|
||||
if (!selectedCharacterId.value) return
|
||||
|
||||
gameStore.connection?.emit(
|
||||
socketManager.emit(
|
||||
SocketEvent.CHARACTER_CONNECT,
|
||||
{
|
||||
characterId: selectedCharacterId.value,
|
||||
characterHairId: selectedHairId.value
|
||||
characterHairId: selectedHairId.value,
|
||||
newNickname: newNickname.value
|
||||
},
|
||||
(response: { character: CharacterT; map: Map; characters: CharacterT[] }) => {
|
||||
gameStore.setCharacter(response.character)
|
||||
@ -169,27 +222,51 @@ function loginWithCharacter() {
|
||||
|
||||
// Create character logics
|
||||
function createCharacter() {
|
||||
gameStore.connection?.emit(SocketEvent.CHARACTER_CREATE, { name: newCharacterName.value }, (success: boolean) => {
|
||||
if (success) return
|
||||
isCreateNewCharacterModalOpen.value = false
|
||||
})
|
||||
if (newCharacterName.value.length < characterCreationSettings.minNameLength || newCharacterName.value.length > characterCreationSettings.maxNameLength) {
|
||||
return
|
||||
}
|
||||
|
||||
socketManager.emit(
|
||||
SocketEvent.CHARACTER_CREATE,
|
||||
{
|
||||
name: newCharacterName.value,
|
||||
gender: selectedGender.value,
|
||||
hairId: selectedHairId.value
|
||||
},
|
||||
(success: boolean) => {
|
||||
if (success) {
|
||||
cancelCharacterCreation()
|
||||
socketManager.emit(SocketEvent.CHARACTER_LIST)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Watch changes for selected character and update hairs
|
||||
watch(selectedCharacterId, (characterId) => {
|
||||
if (!characterId) return
|
||||
newNickname.value = ''
|
||||
isCreatingCharacter.value = false
|
||||
selectedHairId.value = characters.value.find((c) => c.id == characterId)?.characterHair ?? null
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
playSound('/assets/music/intro.mp3')
|
||||
await playSound('/assets/music/intro.mp3')
|
||||
const characterHairStorage = new CharacterHairStorage()
|
||||
const characterTypeStorage = new CharacterTypeStorage()
|
||||
characterHairs.value = await characterHairStorage.getAll()
|
||||
|
||||
// Get the first available character type for preview
|
||||
const types = await characterTypeStorage.getAll()
|
||||
const defaultType = types.find((type) => type.isSelectable)
|
||||
if (defaultType) {
|
||||
defaultCharacterTypeId.value = defaultType.id
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
gameStore.connection?.off(SocketEvent.CHARACTER_LIST)
|
||||
gameStore.connection?.off(SocketEvent.CHARACTER_CONNECT)
|
||||
gameStore.connection?.off(SocketEvent.CHARACTER_CREATE)
|
||||
socketManager.off(SocketEvent.CHARACTER_LIST)
|
||||
socketManager.off(SocketEvent.CHARACTER_CONNECT)
|
||||
socketManager.off(SocketEvent.CHARACTER_CREATE)
|
||||
})
|
||||
</script>
|
||||
|
@ -20,8 +20,10 @@
|
||||
<script setup lang="ts">
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import 'phaser'
|
||||
import type { Map as MapT } from '@/application/types'
|
||||
import { downloadCache } from '@/application/utilities'
|
||||
import Map from '@/components/gameMaster/mapEditor/Map.vue'
|
||||
import MapList from '@/components/gameMaster/mapEditor/partials/MapList.vue'
|
||||
import MapObjectList from '@/components/gameMaster/mapEditor/partials/MapObjectList.vue'
|
||||
@ -32,13 +34,10 @@ import Toolbar from '@/components/gameMaster/mapEditor/partials/Toolbar.vue'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { loadAllTileTextures } from '@/services/mapService'
|
||||
import { MapStorage } from '@/storage/storages'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { Game, Scene } from 'phavuer'
|
||||
import { ref, useTemplateRef } from 'vue'
|
||||
import { ref, toRaw, useTemplateRef } from 'vue'
|
||||
|
||||
const mapStorage = new MapStorage()
|
||||
const mapEditor = useMapEditorComposable()
|
||||
const gameStore = useGameStore()
|
||||
|
||||
const mapModal = useTemplateRef('mapModal')
|
||||
const mapSettingsModal = useTemplateRef('mapSettingsModal')
|
||||
@ -83,18 +82,18 @@ const preloadScene = async (scene: Phaser.Scene) => {
|
||||
})
|
||||
}
|
||||
|
||||
function save() {
|
||||
const currentMap = mapEditor.currentMap.value
|
||||
async function save() {
|
||||
const currentMap = toRaw(mapEditor.currentMap.value)
|
||||
if (!currentMap) return
|
||||
|
||||
const data = {
|
||||
...currentMap,
|
||||
mapId: currentMap.id
|
||||
}
|
||||
console.log(data)
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.GM_MAP_UPDATE, data, (response: MapT) => {
|
||||
mapStorage.update(response.id, response)
|
||||
socketManager.emit(SocketEvent.GM_MAP_UPDATE, data, async (response: MapT) => {
|
||||
if (!response.id) return
|
||||
await downloadCache('maps', new MapStorage())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
<template></template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { login } from '@/services/authenticationService'
|
||||
import { CharacterHairStorage, CharacterTypeStorage, MapObjectStorage, MapStorage, SoundStorage, SpriteStorage, TileStorage } from '@/storage/storages'
|
||||
import { TextureStorage } from '@/storage/textureStorage'
|
||||
@ -41,13 +42,13 @@ async function handleKeyPress(event: KeyboardEvent) {
|
||||
}
|
||||
|
||||
if (currentString.includes('11')) {
|
||||
if (gameStore.token) return
|
||||
if (socketManager.token) return
|
||||
const response = await login('root', 'password')
|
||||
|
||||
if (response.success === undefined) {
|
||||
return
|
||||
}
|
||||
gameStore.setToken(response.token)
|
||||
socketManager.setToken(response.token)
|
||||
gameStore.initConnection()
|
||||
}
|
||||
|
||||
|
@ -12,8 +12,9 @@
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onBeforeMount, onBeforeUnmount, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { onMounted, onUnmounted, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
|
||||
@ -36,13 +37,13 @@ function setupNotificationListener(connection: any) {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const connection = gameStore.connection
|
||||
const connection = socketManager.connection
|
||||
if (connection) {
|
||||
setupNotificationListener(connection)
|
||||
} else {
|
||||
// Watch for changes in the socket connection
|
||||
watch(
|
||||
() => gameStore.connection,
|
||||
() => socketManager.connection,
|
||||
(newConnection) => {
|
||||
if (newConnection) setupNotificationListener(newConnection)
|
||||
}
|
||||
@ -51,7 +52,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
const connection = gameStore.connection
|
||||
const connection = socketManager.connection
|
||||
if (connection) {
|
||||
connection.off(SocketEvent.NOTIFICATION)
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { getTile } from '@/services/mapService'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import type { Ref } from 'vue'
|
||||
@ -7,7 +8,77 @@ import { useBaseControlsComposable } from './useBaseControlsComposable'
|
||||
export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
|
||||
const gameStore = useGameStore()
|
||||
const baseHandlers = useBaseControlsComposable(scene, layer, waypoint, camera)
|
||||
const pressedKeys = new Set<string>()
|
||||
|
||||
let moveTimeout: NodeJS.Timeout | null = null
|
||||
let currentPosition = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
|
||||
// Movement constants
|
||||
const MOVEMENT_DELAY = 110 // Milliseconds between moves
|
||||
const ARROW_KEYS = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'] as const
|
||||
|
||||
function updateCurrentPosition() {
|
||||
if (!gameStore.character) return
|
||||
currentPosition = {
|
||||
x: gameStore.character.positionX,
|
||||
y: gameStore.character.positionY
|
||||
}
|
||||
}
|
||||
|
||||
function calculateNewPosition() {
|
||||
let newX = currentPosition.x
|
||||
let newY = currentPosition.y
|
||||
|
||||
if (pressedKeys.has('ArrowLeft')) newX--
|
||||
if (pressedKeys.has('ArrowRight')) newX++
|
||||
if (pressedKeys.has('ArrowUp')) newY--
|
||||
if (pressedKeys.has('ArrowDown')) newY++
|
||||
|
||||
return { newX, newY }
|
||||
}
|
||||
|
||||
function emitMovement(x: number, y: number) {
|
||||
if (x === currentPosition.x && y === currentPosition.y) return
|
||||
|
||||
socketManager.emit(SocketEvent.MAP_CHARACTER_MOVE, [x, y])
|
||||
socketManager.on(SocketEvent.MAP_CHARACTER_MOVE, ([characterId, posX, posY, rot, isMoving]: [string, number, number, number, boolean]) => {
|
||||
if (characterId !== gameStore.character?.id) return
|
||||
currentPosition = { x: posX, y: posY }
|
||||
})
|
||||
|
||||
currentPosition = { x, y }
|
||||
}
|
||||
|
||||
function startMovementLoop() {
|
||||
if (moveTimeout) return
|
||||
|
||||
const move = () => {
|
||||
if (pressedKeys.size === 0) {
|
||||
stopMovementLoop()
|
||||
return
|
||||
}
|
||||
|
||||
updateCurrentPosition()
|
||||
const { newX, newY } = calculateNewPosition()
|
||||
emitMovement(newX, newY)
|
||||
|
||||
moveTimeout = setTimeout(move, MOVEMENT_DELAY)
|
||||
}
|
||||
|
||||
move()
|
||||
}
|
||||
|
||||
function stopMovementLoop() {
|
||||
if (moveTimeout) {
|
||||
clearTimeout(moveTimeout)
|
||||
moveTimeout = null
|
||||
}
|
||||
}
|
||||
|
||||
// Pointer Handlers
|
||||
function handlePointerDown(pointer: Phaser.Input.Pointer) {
|
||||
baseHandlers.startDragging(pointer)
|
||||
}
|
||||
@ -23,70 +94,37 @@ export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Til
|
||||
const pointerTile = getTile(layer, pointer.worldX, pointer.worldY)
|
||||
if (!pointerTile) return
|
||||
|
||||
gameStore.connection?.emit(SocketEvent.MAP_CHARACTER_MOVE, {
|
||||
positionX: pointerTile.x,
|
||||
positionY: pointerTile.y
|
||||
})
|
||||
emitMovement(pointerTile.x, pointerTile.y)
|
||||
}
|
||||
|
||||
const pressedKeys = new Set<string>()
|
||||
let moveInterval: number | null = null
|
||||
|
||||
// Keyboard Handlers
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (!gameStore.character) return
|
||||
|
||||
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.key)) {
|
||||
// Prevent key repeat events
|
||||
if (ARROW_KEYS.includes(event.key as (typeof ARROW_KEYS)[number])) {
|
||||
if (event.repeat) return
|
||||
|
||||
pressedKeys.add(event.key)
|
||||
|
||||
// Start movement loop if not already running
|
||||
if (!moveInterval) {
|
||||
moveInterval = window.setInterval(moveCharacter, 80) // Increased interval to match server throttle `MOVEMENT_THROTTLE`
|
||||
moveCharacter() // Move immediately on first press
|
||||
}
|
||||
updateCurrentPosition()
|
||||
startMovementLoop()
|
||||
}
|
||||
|
||||
// Attack on CTRL
|
||||
if (event.key === 'Control') {
|
||||
gameStore.connection?.emit(SocketEvent.MAP_CHARACTER_ATTACK)
|
||||
socketManager.emit(SocketEvent.MAP_CHARACTER_ATTACK)
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
pressedKeys.delete(event.key)
|
||||
|
||||
// If no movement keys are pressed, clear the interval
|
||||
if (pressedKeys.size === 0 && moveInterval) {
|
||||
clearInterval(moveInterval)
|
||||
moveInterval = null
|
||||
}
|
||||
}
|
||||
|
||||
function moveCharacter() {
|
||||
if (!gameStore.character) return
|
||||
|
||||
const { positionX, positionY } = gameStore.character
|
||||
let newX = positionX
|
||||
let newY = positionY
|
||||
|
||||
// Calculate new position based on pressed keys
|
||||
if (pressedKeys.has('ArrowLeft')) newX--
|
||||
if (pressedKeys.has('ArrowRight')) newX++
|
||||
if (pressedKeys.has('ArrowUp')) newY--
|
||||
if (pressedKeys.has('ArrowDown')) newY++
|
||||
|
||||
// Only emit if position changed
|
||||
if (newX !== positionX || newY !== positionY) {
|
||||
gameStore.connection?.emit(SocketEvent.MAP_CHARACTER_MOVE, {
|
||||
positionX: newX,
|
||||
positionY: newY
|
||||
})
|
||||
if (pressedKeys.size === 0) {
|
||||
stopMovementLoop()
|
||||
}
|
||||
}
|
||||
|
||||
const setupControls = () => {
|
||||
updateCurrentPosition() // Initialize position
|
||||
|
||||
scene.input.on(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
|
||||
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
||||
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
||||
@ -96,6 +134,9 @@ export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Til
|
||||
}
|
||||
|
||||
const cleanupControls = () => {
|
||||
stopMovementLoop()
|
||||
pressedKeys.clear()
|
||||
|
||||
scene.input.off(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
|
||||
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
||||
scene.input.off(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
||||
|
@ -1,9 +1,9 @@
|
||||
import config from '@/application/config'
|
||||
import { Direction } from '@/application/enums'
|
||||
import { type MapCharacter } from '@/application/types'
|
||||
import { type MapCharacter, type SpriteAction } from '@/application/types'
|
||||
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/services/mapService'
|
||||
import { loadSpriteTextures } from '@/services/textureService'
|
||||
import { CharacterTypeStorage } from '@/storage/storages'
|
||||
import { CharacterTypeStorage, SpriteStorage } from '@/storage/storages'
|
||||
import { refObj } from 'phavuer'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
@ -72,7 +72,7 @@ export function useCharacterSpriteComposable(scene: Phaser.Scene, tilemap: Phase
|
||||
// Add new listener
|
||||
characterSprite.value.on(Phaser.Animations.Events.ANIMATION_COMPLETE, () => {
|
||||
characterSprite.value!.setFrame(0)
|
||||
characterSprite.value!.setTexture(charTexture.value)
|
||||
characterSprite.value!.setTexture(spriteSpriteActionId.value)
|
||||
})
|
||||
|
||||
characterSprite.value.anims.play(
|
||||
@ -96,11 +96,23 @@ export function useCharacterSpriteComposable(scene: Phaser.Scene, tilemap: Phase
|
||||
return [0, 6].includes(mapCharacter.character.rotation ?? 0) ? 'left_up' : 'right_down'
|
||||
})
|
||||
|
||||
const getSpriteHeightByAction = async (action: string = '') => {
|
||||
if (!characterSpriteId.value) return 0
|
||||
const spriteStorage = new SpriteStorage()
|
||||
const sprite = await spriteStorage.getById(characterSpriteId.value)
|
||||
let actionWithDirection = `${currentAction.value}_${currentDirection.value}`
|
||||
if (action) actionWithDirection = action
|
||||
|
||||
return sprite?.spriteActions?.find((spriteAction: SpriteAction) => spriteAction.action === actionWithDirection)?.frameHeight ?? 0
|
||||
}
|
||||
|
||||
const spriteHeight = computed(() => getSpriteHeightByAction(currentAction.value))
|
||||
|
||||
const currentAction = computed(() => {
|
||||
return mapCharacter.isMoving ? 'walk' : 'idle'
|
||||
})
|
||||
|
||||
const charTexture = computed(() => {
|
||||
const spriteSpriteActionId = computed(() => {
|
||||
const spriteId = characterSpriteId.value ?? 'idle_right_down'
|
||||
return `${spriteId}-${currentAction.value}_${currentDirection.value}`
|
||||
})
|
||||
@ -109,11 +121,11 @@ export function useCharacterSpriteComposable(scene: Phaser.Scene, tilemap: Phase
|
||||
if (!characterSprite.value) return
|
||||
|
||||
if (mapCharacter.isMoving) {
|
||||
characterSprite.value.anims.play(charTexture.value, true)
|
||||
characterSprite.value.anims.play(spriteSpriteActionId.value, true)
|
||||
} else {
|
||||
characterSprite.value.anims.stop()
|
||||
characterSprite.value.setFrame(0)
|
||||
characterSprite.value.setTexture(charTexture.value)
|
||||
characterSprite.value.setTexture(spriteSpriteActionId.value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,7 +142,8 @@ export function useCharacterSpriteComposable(scene: Phaser.Scene, tilemap: Phase
|
||||
}
|
||||
|
||||
if (characterSprite.value) {
|
||||
characterSprite.value.setTexture(charTexture.value)
|
||||
characterSprite!.value?.setOrigin(0.5, 1)
|
||||
characterSprite.value.setTexture(spriteSpriteActionId.value)
|
||||
characterSprite.value.setFlipX(isFlippedX.value)
|
||||
}
|
||||
|
||||
@ -146,10 +159,15 @@ export function useCharacterSpriteComposable(scene: Phaser.Scene, tilemap: Phase
|
||||
characterContainer,
|
||||
characterSpriteId,
|
||||
characterSprite,
|
||||
spriteHeight,
|
||||
currentAction,
|
||||
spriteSpriteActionId,
|
||||
currentPositionX,
|
||||
currentPositionY,
|
||||
currentDirection,
|
||||
isometricDepth,
|
||||
isFlippedX,
|
||||
getSpriteHeightByAction,
|
||||
updatePosition,
|
||||
playAnimation,
|
||||
calcDirection,
|
||||
|
@ -3,7 +3,7 @@ import { useGameStore } from '@/stores/gameStore'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export type TeleportSettings = {
|
||||
toMapId: string
|
||||
toMap: string
|
||||
toPositionX: number
|
||||
toPositionY: number
|
||||
toRotation: number
|
||||
@ -21,7 +21,7 @@ const movingPlacedObject = ref<PlacedMapObject | null>(null)
|
||||
const selectedPlacedObject = ref<PlacedMapObject | null>(null)
|
||||
const shouldClearTiles = ref(false)
|
||||
const teleportSettings = ref<TeleportSettings>({
|
||||
toMapId: '1000',
|
||||
toMap: '1000',
|
||||
toPositionX: 0,
|
||||
toPositionY: 0,
|
||||
toRotation: 0
|
||||
@ -98,6 +98,7 @@ export function useMapEditorComposable() {
|
||||
selectedTile.value = ''
|
||||
isPlacedMapObjectPreviewEnabled.value = false
|
||||
selectedMapObject.value = null
|
||||
selectedPlacedObject.value = null
|
||||
shouldClearTiles.value = false
|
||||
refreshMapObject.value = 0
|
||||
}
|
||||
|
76
src/managers/SocketManager.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||
import { io, Socket } from 'socket.io-client'
|
||||
import { ref, shallowRef } from 'vue'
|
||||
|
||||
class SocketManager {
|
||||
private static instance: SocketManager
|
||||
private _connection = shallowRef<Socket | null>(null)
|
||||
private _token = ref('')
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): SocketManager {
|
||||
if (!SocketManager.instance) {
|
||||
SocketManager.instance = new SocketManager()
|
||||
}
|
||||
return SocketManager.instance
|
||||
}
|
||||
|
||||
public get connection() {
|
||||
return this._connection.value
|
||||
}
|
||||
|
||||
public get token() {
|
||||
return this._token.value
|
||||
}
|
||||
|
||||
public setToken(token: string) {
|
||||
this._token.value = token
|
||||
}
|
||||
|
||||
public initConnection(): Socket {
|
||||
if (this._connection.value) return this._connection.value
|
||||
|
||||
const socket = io(config.server_endpoint, {
|
||||
secure: config.environment === 'production',
|
||||
withCredentials: true,
|
||||
transports: ['websocket'],
|
||||
reconnectionAttempts: 5
|
||||
})
|
||||
|
||||
this._connection.value = socket
|
||||
return socket
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
if (!this._connection.value) return
|
||||
|
||||
this._connection.value.off(SocketEvent.CONNECT_ERROR)
|
||||
this._connection.value.off(SocketEvent.RECONNECT_FAILED)
|
||||
this._connection.value.off(SocketEvent.DATE)
|
||||
this._connection.value.disconnect()
|
||||
|
||||
useCookies().remove('token', {
|
||||
domain: config.domain
|
||||
})
|
||||
|
||||
this._connection.value = null
|
||||
this._token.value = ''
|
||||
}
|
||||
|
||||
public emit(event: string, ...args: any[]): void {
|
||||
this._connection.value?.emit(event, ...args)
|
||||
}
|
||||
|
||||
public on(event: string, callback: (...args: any[]) => void): void {
|
||||
this._connection.value?.on(event, callback)
|
||||
}
|
||||
|
||||
public off(event: string, callback?: (...args: any[]) => void): void {
|
||||
this._connection.value?.off(event, callback)
|
||||
}
|
||||
}
|
||||
|
||||
export const socketManager = SocketManager.getInstance()
|
@ -62,8 +62,8 @@ export function createTileArray(width: number, height: number, tile: string = 'b
|
||||
return Array.from({ length: height }, () => Array.from({ length: width }, () => tile))
|
||||
}
|
||||
|
||||
export const calculateIsometricDepth = (positionX: number, positionY: number, pivotPoints: { x: number; y: number; }[] = []) => {
|
||||
return Math.max(positionX + positionY);
|
||||
export const calculateIsometricDepth = (positionX: number, positionY: number, pivotPoints: { x: number; y: number }[] = []) => {
|
||||
return Math.max(positionX + positionY)
|
||||
}
|
||||
|
||||
async function loadTileTextures(tiles: TileT[], scene: Phaser.Scene) {
|
||||
|
@ -1,16 +1,12 @@
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Character, Notification, User, WorldSettings } from '@/application/types'
|
||||
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { defineStore } from 'pinia'
|
||||
import { io, Socket } from 'socket.io-client'
|
||||
|
||||
export const useGameStore = defineStore('game', {
|
||||
state: () => {
|
||||
return {
|
||||
notifications: [] as Notification[],
|
||||
token: '',
|
||||
connection: null as Socket | null,
|
||||
user: null as User | null,
|
||||
character: null as Character | null,
|
||||
world: {
|
||||
@ -51,9 +47,6 @@ export const useGameStore = defineStore('game', {
|
||||
removeNotification(id: string) {
|
||||
this.notifications = this.notifications.filter((notification: Notification) => notification.id !== id)
|
||||
},
|
||||
setToken(token: string) {
|
||||
this.token = token
|
||||
},
|
||||
setUser(user: User | null) {
|
||||
this.user = user
|
||||
},
|
||||
@ -73,49 +66,34 @@ export const useGameStore = defineStore('game', {
|
||||
this.uiSettings.isCharacterProfileOpen = !this.uiSettings.isCharacterProfileOpen
|
||||
},
|
||||
initConnection() {
|
||||
this.connection = io(config.server_endpoint, {
|
||||
secure: config.environment === 'production',
|
||||
withCredentials: true,
|
||||
transports: ['websocket'],
|
||||
reconnectionAttempts: 5
|
||||
})
|
||||
const socket = socketManager.initConnection()
|
||||
|
||||
// #99 - If we can't connect, disconnect
|
||||
this.connection.on('connect_error', () => {
|
||||
// Handle connect error
|
||||
socket.on(SocketEvent.CONNECT_ERROR, () => {
|
||||
this.disconnectSocket()
|
||||
})
|
||||
|
||||
// Let the server know the user is logged in
|
||||
this.connection.emit(SocketEvent.LOGIN)
|
||||
// Handle failed reconnection
|
||||
socket.on(SocketEvent.RECONNECT_FAILED, () => {
|
||||
this.disconnectSocket()
|
||||
})
|
||||
|
||||
// set user
|
||||
this.connection.on(SocketEvent.LOGGED_IN, (user: User) => {
|
||||
// Emit login event
|
||||
socketManager.emit(SocketEvent.LOGIN)
|
||||
|
||||
// Handle logged in event
|
||||
socketManager.on(SocketEvent.LOGGED_IN, (user: User) => {
|
||||
this.setUser(user)
|
||||
})
|
||||
|
||||
// When we can't reconnect, disconnect
|
||||
this.connection.on('reconnect_failed', () => {
|
||||
this.disconnectSocket()
|
||||
})
|
||||
|
||||
// Listen for new date from socket
|
||||
this.connection.on(SocketEvent.DATE, (data: Date) => {
|
||||
// Handle date updates
|
||||
socketManager.on(SocketEvent.DATE, (data: Date) => {
|
||||
this.world.date = new Date(data)
|
||||
})
|
||||
},
|
||||
disconnectSocket() {
|
||||
// Remove event listeners
|
||||
this.connection?.off('connect_error')
|
||||
this.connection?.off('reconnect_failed')
|
||||
this.connection?.off(SocketEvent.DATE)
|
||||
this.connection?.disconnect()
|
||||
socketManager.disconnect()
|
||||
|
||||
useCookies().remove('token', {
|
||||
domain: config.domain
|
||||
})
|
||||
|
||||
this.connection = null
|
||||
this.token = ''
|
||||
this.user = null
|
||||
this.character = null
|
||||
|
||||
|
@ -2,7 +2,7 @@ import type { MapObject, Map as MapT } from '@/application/types'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export type TeleportSettings = {
|
||||
toMapId: string
|
||||
toMap: string
|
||||
toPositionX: number
|
||||
toPositionY: number
|
||||
toRotation: number
|
||||
@ -18,7 +18,7 @@ export const useMapEditorStore = defineStore('mapEditor', {
|
||||
selectedMapObject: null as MapObject | null,
|
||||
shouldClearTiles: false,
|
||||
teleportSettings: {
|
||||
toMapId: '',
|
||||
toMap: '',
|
||||
toPositionX: 0,
|
||||
toPositionY: 0,
|
||||
toRotation: 0
|
||||
|
@ -35,13 +35,13 @@ export const useMapStore = defineStore('map', {
|
||||
removeCharacter(characterId: UUID) {
|
||||
this.characters = this.characters.filter((char) => char.character.id !== characterId)
|
||||
},
|
||||
updateCharacterPosition(data: { characterId: UUID; positionX: number; positionY: number; rotation: number; isMoving: boolean }) {
|
||||
const character = this.characters.find((char) => char.character.id === data.characterId)
|
||||
updateCharacterPosition([characterId, posX, posY, rot, isMoving]: [UUID, number, number, number, boolean]) {
|
||||
const character = this.characters.find((char) => char.character.id === characterId)
|
||||
if (character) {
|
||||
character.character.positionX = data.positionX
|
||||
character.character.positionY = data.positionY
|
||||
character.character.rotation = data.rotation
|
||||
character.isMoving = data.isMoving
|
||||
character.character.positionX = posX
|
||||
character.character.positionY = posY
|
||||
character.character.rotation = rot
|
||||
character.isMoving = isMoving
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
|