Compare commits

...

29 Commits

Author SHA1 Message Date
6ca82733eb #324 - Fix Character profile image 2025-01-31 19:04:42 +01:00
eb61f45535 npm update 2025-01-31 18:49:03 +01:00
a181fc7fe3 npm update 2025-01-31 17:06:16 +01:00
507d4226ac Bug fix for character profile, greatly improved javascript 2025-01-31 03:23:23 +01:00
5dd9d1e7af Added temp. offset logic for easier sprite management 2025-01-31 02:16:19 +01:00
15f9e9861e Zoom 2025-01-31 01:55:52 +01:00
7fd334d414 Applied styling fix 2025-01-31 01:19:49 +01:00
c7d4b5f2c3 Updates TS hints 2025-01-31 01:18:49 +01:00
5747166822 Don't toggle accordion on button press 2025-01-31 01:17:44 +01:00
c010373e5b Updated loading indicator to match the other one we have 2025-01-30 18:35:51 +01:00
57ad9d4889 Don't update after closing sprite action img offset modal 2025-01-30 18:32:33 +01:00
f268ac9e5b Removed comment, updated types for sprite actions, minor modal component improvement, added components for better sprite management 2025-01-30 18:29:55 +01:00
8befce7ffb Update packages 2025-01-29 23:28:04 +01:00
014c08b17a Set depth to 9999 to always show above character sprite 2025-01-28 17:54:12 +01:00
bdbda6456c CharHair work 2025-01-28 16:32:16 +01:00
85537840ab Improved code 2025-01-28 05:14:37 +01:00
2b7082ac92 Bug fix caused by formatting 2025-01-28 05:01:52 +01:00
abc58bfa38 npm run format, removed container that character was in 2025-01-28 04:57:21 +01:00
027325f2bf Merge remote-tracking branch 'origin/main' into feature/map-refactor 2025-01-28 01:59:29 +01:00
517e92b07b Fully restored tile picker function 2025-01-27 14:49:27 -06:00
6bede8c44e Map editor ui fixes, switching back to game from map editor, draw/tap checkbox, and partial restoration of tile picker function 2025-01-27 14:33:29 -06:00
9e652868ca Prepping to work on Placed map objects 2025-01-27 00:18:46 -06:00
35f0dcca64 Hierarchical pointer handling logic 2025-01-26 21:21:24 -06:00
9618e07bc6 refactoring pointer events and input handling improvements 2025-01-26 20:50:34 -06:00
791830fd6f Refactoring of modalShown booleans 2025-01-26 18:56:13 -06:00
37acf1782b Removed isAnimated and isLooping fields 2025-01-27 01:51:53 +01:00
14aa696197 Changes to mapeditorcomposable, fix pencil and fill tool for tiles
Locked in, made mapeditor my bi-
2025-01-26 23:28:15 +01:00
cfac1d508b Minor change 2025-01-25 16:49:45 +01:00
82cfe5902f Bye 2025-01-25 16:49:25 +01:00
41 changed files with 1136 additions and 1153 deletions

310
package-lock.json generated
View File

@ -840,9 +840,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@parcel/watcher": { "node_modules/@parcel/watcher": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
"integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==", "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
@ -861,25 +861,25 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
}, },
"optionalDependencies": { "optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.0", "@parcel/watcher-android-arm64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.5.0", "@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-darwin-x64": "2.5.0", "@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-freebsd-x64": "2.5.0", "@parcel/watcher-freebsd-x64": "2.5.1",
"@parcel/watcher-linux-arm-glibc": "2.5.0", "@parcel/watcher-linux-arm-glibc": "2.5.1",
"@parcel/watcher-linux-arm-musl": "2.5.0", "@parcel/watcher-linux-arm-musl": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.0", "@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.0", "@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.0", "@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.0", "@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-win32-arm64": "2.5.0", "@parcel/watcher-win32-arm64": "2.5.1",
"@parcel/watcher-win32-ia32": "2.5.0", "@parcel/watcher-win32-ia32": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.0" "@parcel/watcher-win32-x64": "2.5.1"
} }
}, },
"node_modules/@parcel/watcher-android-arm64": { "node_modules/@parcel/watcher-android-arm64": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
"integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==", "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -898,9 +898,9 @@
} }
}, },
"node_modules/@parcel/watcher-darwin-arm64": { "node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
"integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==", "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -919,9 +919,9 @@
} }
}, },
"node_modules/@parcel/watcher-darwin-x64": { "node_modules/@parcel/watcher-darwin-x64": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
"integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==", "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -940,9 +940,9 @@
} }
}, },
"node_modules/@parcel/watcher-freebsd-x64": { "node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
"integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==", "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -961,9 +961,9 @@
} }
}, },
"node_modules/@parcel/watcher-linux-arm-glibc": { "node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
"integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==", "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -982,9 +982,9 @@
} }
}, },
"node_modules/@parcel/watcher-linux-arm-musl": { "node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
"integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==", "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -1003,9 +1003,9 @@
} }
}, },
"node_modules/@parcel/watcher-linux-arm64-glibc": { "node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
"integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==", "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1024,9 +1024,9 @@
} }
}, },
"node_modules/@parcel/watcher-linux-arm64-musl": { "node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
"integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==", "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1045,9 +1045,9 @@
} }
}, },
"node_modules/@parcel/watcher-linux-x64-glibc": { "node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
"integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==", "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1066,9 +1066,9 @@
} }
}, },
"node_modules/@parcel/watcher-linux-x64-musl": { "node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
"integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==", "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1087,9 +1087,9 @@
} }
}, },
"node_modules/@parcel/watcher-win32-arm64": { "node_modules/@parcel/watcher-win32-arm64": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
"integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==", "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1108,9 +1108,9 @@
} }
}, },
"node_modules/@parcel/watcher-win32-ia32": { "node_modules/@parcel/watcher-win32-ia32": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
"integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==", "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -1129,9 +1129,9 @@
} }
}, },
"node_modules/@parcel/watcher-win32-x64": { "node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
"integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==", "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1161,9 +1161,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.1.tgz",
"integrity": "sha512-G2fUQQANtBPsNwiVFg4zKiPQyjVKZCUdQUol53R8E71J7AsheRMV/Yv/nB8giOcOVqP7//eB5xPqieBYZe9bGg==", "integrity": "sha512-/pqA4DmqyCm8u5YIDzIdlLcEmuvxb0v8fZdFhVMszSpDTgbQKdw3/mB3eMUHIbubtJ6F9j+LtmyCnHTEqIHyzA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -1175,9 +1175,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.32.1.tgz",
"integrity": "sha512-qhFwQ+ljoymC+j5lXRv8DlaJYY/+8vyvYmVx074zrLsu5ZGWYsJNLjPPVJJjhZQpyAKUGPydOq9hRLLNvh1s3A==", "integrity": "sha512-If3PDskT77q7zgqVqYuj7WG3WC08G1kwXGVFi9Jr8nY6eHucREHkfpX79c0ACAjLj3QIWKPJR7w4i+f5EdLH5Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1189,9 +1189,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.32.1.tgz",
"integrity": "sha512-44n/X3lAlWsEY6vF8CzgCx+LQaoqWGN7TzUfbJDiTIOjJm4+L2Yq+r5a8ytQRGyPqgJDs3Rgyo8eVL7n9iW6AQ==", "integrity": "sha512-zCpKHioQ9KgZToFp5Wvz6zaWbMzYQ2LJHQ+QixDKq52KKrF65ueu6Af4hLlLWHjX1Wf/0G5kSJM9PySW9IrvHA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1203,9 +1203,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.32.1.tgz",
"integrity": "sha512-F9ct0+ZX5Np6+ZDztxiGCIvlCaW87HBdHcozUfsHnj1WCUTBUubAoanhHUfnUHZABlElyRikI0mgcw/qdEm2VQ==", "integrity": "sha512-sFvF+t2+TyUo/ZQqUcifrJIgznx58oFZbdHS9TvHq3xhPVL9nOp+yZ6LKrO9GWTP+6DbFtoyLDbjTpR62Mbr3Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1217,9 +1217,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-arm64": { "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.32.1.tgz",
"integrity": "sha512-JpsGxLBB2EFXBsTLHfkZDsXSpSmKD3VxXCgBQtlPcuAqB8TlqtLcbeMhxXQkCDv1avgwNjF8uEIbq5p+Cee0PA==", "integrity": "sha512-NbOa+7InvMWRcY9RG+B6kKIMD/FsnQPH0MWUvDlQB1iXnF/UcKSudCXZtv4lW+C276g3w5AxPbfry5rSYvyeYA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1231,9 +1231,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-x64": { "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.32.1.tgz",
"integrity": "sha512-wegiyBT6rawdpvnD9lmbOpx5Sph+yVZKHbhnSP9MqUEDX08G4UzMU+D87jrazGE7lRSyTRs6NEYHtzfkJ3FjjQ==", "integrity": "sha512-JRBRmwvHPXR881j2xjry8HZ86wIPK2CcDw0EXchE1UgU0ubWp9nvlT7cZYKc6bkypBt745b4bglf3+xJ7hXWWw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1245,9 +1245,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.32.1.tgz",
"integrity": "sha512-3pA7xecItbgOs1A5H58dDvOUEboG5UfpTq3WzAdF54acBbUM+olDJAPkgj1GRJ4ZqE12DZ9/hNS2QZk166v92A==", "integrity": "sha512-PKvszb+9o/vVdUzCCjL0sKHukEQV39tD3fepXxYrHE3sTKrRdCydI7uldRLbjLmDA3TFDmh418XH19NOsDRH8g==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -1259,9 +1259,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.32.1.tgz",
"integrity": "sha512-Y7XUZEVISGyge51QbYyYAEHwpGgmRrAxQXO3siyYo2kmaj72USSG8LtlQQgAtlGfxYiOwu+2BdbPjzEpcOpRmQ==", "integrity": "sha512-9WHEMV6Y89eL606ReYowXuGF1Yb2vwfKWKdD1A5h+OYnPZSJvxbEjxTRKPgi7tkP2DSnW0YLab1ooy+i/FQp/Q==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -1273,9 +1273,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.32.1.tgz",
"integrity": "sha512-r7/OTF5MqeBrZo5omPXcTnjvv1GsrdH8a8RerARvDFiDwFpDVDnJyByYM/nX+mvks8XXsgPUxkwe/ltaX2VH7w==", "integrity": "sha512-tZWc9iEt5fGJ1CL2LRPw8OttkCBDs+D8D3oEM8mH8S1ICZCtFJhD7DZ3XMGM8kpqHvhGUTvNUYVDnmkj4BDXnw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1287,9 +1287,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.32.1.tgz",
"integrity": "sha512-HJbifC9vex9NqnlodV2BHVFNuzKL5OnsV2dvTw6e1dpZKkNjPG6WUq+nhEYV6Hv2Bv++BXkwcyoGlXnPrjAKXw==", "integrity": "sha512-FTYc2YoTWUsBz5GTTgGkRYYJ5NGJIi/rCY4oK/I8aKowx1ToXeoVVbIE4LGAjsauvlhjfl0MYacxClLld1VrOw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1301,9 +1301,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-loongarch64-gnu": { "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.32.1.tgz",
"integrity": "sha512-VAEzZTD63YglFlWwRj3taofmkV1V3xhebDXffon7msNz4b14xKsz7utO6F8F4cqt8K/ktTl9rm88yryvDpsfOw==", "integrity": "sha512-F51qLdOtpS6P1zJVRzYM0v6MrBNypyPEN1GfMiz0gPu9jN8ScGaEFIZQwteSsGKg799oR5EaP7+B2jHgL+d+Kw==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@ -1315,9 +1315,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": { "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.32.1.tgz",
"integrity": "sha512-Sts5DST1jXAc9YH/iik1C9QRsLcCoOScf3dfbY5i4kH9RJpKxiTBXqm7qU5O6zTXBTEZry69bGszr3SMgYmMcQ==", "integrity": "sha512-wO0WkfSppfX4YFm5KhdCCpnpGbtgQNj/tgvYzrVYFKDpven8w2N6Gg5nB6w+wAMO3AIfSTWeTjfVe+uZ23zAlg==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -1329,9 +1329,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.32.1.tgz",
"integrity": "sha512-qhlXeV9AqxIyY9/R1h1hBD6eMvQCO34ZmdYvry/K+/MBs6d1nRFLm6BOiITLVI+nFAAB9kUB6sdJRKyVHXnqZw==", "integrity": "sha512-iWswS9cIXfJO1MFYtI/4jjlrGb/V58oMu4dYJIKnR5UIwbkzR0PJ09O0PDZT0oJ3LYWXBSWahNf/Mjo6i1E5/g==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -1343,9 +1343,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.32.1.tgz",
"integrity": "sha512-8ZGN7ExnV0qjXa155Rsfi6H8M4iBBwNLBM9lcVS+4NcSzOFaNqmt7djlox8pN1lWrRPMRRQ8NeDlozIGx3Omsw==", "integrity": "sha512-RKt8NI9tebzmEthMnfVgG3i/XeECkMPS+ibVZjZ6mNekpbbUmkNWuIN2yHsb/mBPyZke4nlI4YqIdFPgKuoyQQ==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -1357,9 +1357,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.32.1.tgz",
"integrity": "sha512-VDzNHtLLI5s7xd/VubyS10mq6TxvZBp+4NRWoW+Hi3tgV05RtVm4qK99+dClwTN1McA6PHwob6DEJ6PlXbY83A==", "integrity": "sha512-WQFLZ9c42ECqEjwg/GHHsouij3pzLXkFdz0UxHa/0OM12LzvX7DzedlY0SIEly2v18YZLRhCRoHZDxbBSWoGYg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1371,9 +1371,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.32.1.tgz",
"integrity": "sha512-qcb9qYDlkxz9DxJo7SDhWxTWV1gFuwznjbTiov289pASxlfGbaOD54mgbs9+z94VwrXtKTu+2RqwlSTbiOqxGg==", "integrity": "sha512-BLoiyHDOWoS3uccNSADMza6V6vCNiphi94tQlVIL5de+r6r/CCQuNnerf+1g2mnk2b6edp5dk0nhdZ7aEjOBsA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1385,9 +1385,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.32.1.tgz",
"integrity": "sha512-pFDdotFDMXW2AXVbfdUEfidPAk/OtwE/Hd4eYMTNVVaCQ6Yl8et0meDaKNL63L44Haxv4UExpv9ydSf3aSayDg==", "integrity": "sha512-w2l3UnlgYTNNU+Z6wOR8YdaioqfEnwPjIsJ66KxKAf0p+AuL2FHeTX6qvM+p/Ue3XPBVNyVSfCrfZiQh7vZHLQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1399,9 +1399,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.32.1.tgz",
"integrity": "sha512-/TG7WfrCAjeRNDvI4+0AAMoHxea/USWhAzf9PVDFHbcqrQ7hMMKp4jZIy4VEjk72AAfN5k4TiSMRXRKf/0akSw==", "integrity": "sha512-Am9H+TGLomPGkBnaPWie4F3x+yQ2rr4Bk2jpwy+iV+Gel9jLAu/KqT8k3X4jxFPW6Zf8OMnehyutsd+eHoq1WQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -1413,9 +1413,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.32.1.tgz",
"integrity": "sha512-5hqO5S3PTEO2E5VjCePxv40gIgyS2KvO7E7/vvC/NbIW4SIRamkMr1hqj+5Y67fbBWv/bQLB6KelBQmXlyCjWA==", "integrity": "sha512-ar80GhdZb4DgmW3myIS9nRFYcpJRSME8iqWgzH2i44u+IdrzmiXVxeFnExQ5v4JYUSpg94bWjevMG8JHf1Da5Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -2168,9 +2168,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001695", "version": "1.0.30001696",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz",
"integrity": "sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==", "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -2535,9 +2535,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.88", "version": "1.5.90",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.88.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.90.tgz",
"integrity": "sha512-K3C2qf1o+bGzbilTDCTBhTQcMS9KW60yTAaTeeXsfvQuTDDwlokLam/AdqlqcSy9u4UainDgsHV23ksXAOgamw==", "integrity": "sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@ -2743,9 +2743,9 @@
} }
}, },
"node_modules/fastq": { "node_modules/fastq": {
"version": "1.18.0", "version": "1.19.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz",
"integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
@ -3393,9 +3393,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/loupe": { "node_modules/loupe": {
"version": "3.1.2", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz",
"integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -3740,9 +3740,9 @@
"license": "BlueOak-1.0.0" "license": "BlueOak-1.0.0"
}, },
"node_modules/papaparse": { "node_modules/papaparse": {
"version": "5.5.1", "version": "5.5.2",
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.1.tgz", "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.2.tgz",
"integrity": "sha512-EuEKUhyxrHVozD7g3/ztsJn6qaKse8RPfR6buNB2dMJvdtXNhcw8jccVi/LxNEY3HVrV6GO6Z4OoeCG9Iy9wpA==", "integrity": "sha512-PZXg8UuAc4PcVwLosEEDYjPyfWnTEhOrUfdv+3Bx+NuAb+5NhDmXzg5fHWmdCh1mP5p7JAZfFr3IMQfcntNAdA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -3827,9 +3827,9 @@
} }
}, },
"node_modules/phaser3-rex-plugins": { "node_modules/phaser3-rex-plugins": {
"version": "1.80.12", "version": "1.80.13",
"resolved": "https://registry.npmjs.org/phaser3-rex-plugins/-/phaser3-rex-plugins-1.80.12.tgz", "resolved": "https://registry.npmjs.org/phaser3-rex-plugins/-/phaser3-rex-plugins-1.80.13.tgz",
"integrity": "sha512-6vzYVhiSeiQSBBDXl2xQyF1omVhElF9yQTW7DKl0JHXchQ0bmvj4B+YVT5EFcle7y+3reedwOUDlchLvoccxsQ==", "integrity": "sha512-d2P3c+0r7h/GXtOZIjB6DLjBQ2rSEeZ+AOG1D6jgGGn/Txb+PiDmFMhBF3qJ6YoPlVarSE8lw6V8Jy7K7xPnLg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -4250,9 +4250,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.32.0", "version": "4.32.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.32.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.32.1.tgz",
"integrity": "sha512-JmrhfQR31Q4AuNBjjAX4s+a/Pu/Q8Q9iwjWBsjRH1q52SPFE2NqRMK6fUZKKnvKO6id+h7JIRf0oYsph53eATg==", "integrity": "sha512-z+aeEsOeEa3mEbS1Tjl6sAZ8NE3+AalQz1RJGj81M+fizusbdDMoEJwdJNHfaB40Scr4qNu+welOfes7maKonA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -4266,25 +4266,25 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.32.0", "@rollup/rollup-android-arm-eabi": "4.32.1",
"@rollup/rollup-android-arm64": "4.32.0", "@rollup/rollup-android-arm64": "4.32.1",
"@rollup/rollup-darwin-arm64": "4.32.0", "@rollup/rollup-darwin-arm64": "4.32.1",
"@rollup/rollup-darwin-x64": "4.32.0", "@rollup/rollup-darwin-x64": "4.32.1",
"@rollup/rollup-freebsd-arm64": "4.32.0", "@rollup/rollup-freebsd-arm64": "4.32.1",
"@rollup/rollup-freebsd-x64": "4.32.0", "@rollup/rollup-freebsd-x64": "4.32.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.32.0", "@rollup/rollup-linux-arm-gnueabihf": "4.32.1",
"@rollup/rollup-linux-arm-musleabihf": "4.32.0", "@rollup/rollup-linux-arm-musleabihf": "4.32.1",
"@rollup/rollup-linux-arm64-gnu": "4.32.0", "@rollup/rollup-linux-arm64-gnu": "4.32.1",
"@rollup/rollup-linux-arm64-musl": "4.32.0", "@rollup/rollup-linux-arm64-musl": "4.32.1",
"@rollup/rollup-linux-loongarch64-gnu": "4.32.0", "@rollup/rollup-linux-loongarch64-gnu": "4.32.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.32.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.32.1",
"@rollup/rollup-linux-riscv64-gnu": "4.32.0", "@rollup/rollup-linux-riscv64-gnu": "4.32.1",
"@rollup/rollup-linux-s390x-gnu": "4.32.0", "@rollup/rollup-linux-s390x-gnu": "4.32.1",
"@rollup/rollup-linux-x64-gnu": "4.32.0", "@rollup/rollup-linux-x64-gnu": "4.32.1",
"@rollup/rollup-linux-x64-musl": "4.32.0", "@rollup/rollup-linux-x64-musl": "4.32.1",
"@rollup/rollup-win32-arm64-msvc": "4.32.0", "@rollup/rollup-win32-arm64-msvc": "4.32.1",
"@rollup/rollup-win32-ia32-msvc": "4.32.0", "@rollup/rollup-win32-ia32-msvc": "4.32.1",
"@rollup/rollup-win32-x64-msvc": "4.32.0", "@rollup/rollup-win32-x64-msvc": "4.32.1",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@ -4361,9 +4361,9 @@
} }
}, },
"node_modules/semver": { "node_modules/semver": {
"version": "7.6.3", "version": "7.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 453 KiB

After

Width:  |  Height:  |  Size: 454 KiB

View File

@ -2,7 +2,7 @@
<Debug /> <Debug />
<Notifications /> <Notifications />
<BackgroundImageLoader /> <BackgroundImageLoader />
<GmPanel v-if="gameStore.character?.role === 'gm'" /> <GmPanel v-if="gameStore.character?.role === 'gm'" @open-map-editor="mapEditor.toggleActive" />
<component :is="currentScreen" /> <component :is="currentScreen" />
</template> </template>
@ -16,25 +16,26 @@ import MapEditor from '@/components/screens/MapEditor.vue'
import BackgroundImageLoader from '@/components/utilities/BackgroundImageLoader.vue' import BackgroundImageLoader from '@/components/utilities/BackgroundImageLoader.vue'
import Debug from '@/components/utilities/Debug.vue' import Debug from '@/components/utilities/Debug.vue'
import Notifications from '@/components/utilities/Notifications.vue' import Notifications from '@/components/utilities/Notifications.vue'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useMapEditorStore } from '@/stores/mapEditorStore' import { useMapEditorStore } from '@/stores/mapEditorStore'
import { computed, watch } from 'vue' import { computed, ref, useTemplateRef, watch } from 'vue'
const gameStore = useGameStore() const gameStore = useGameStore()
const mapEditorStore = useMapEditorStore() const mapEditor = useMapEditorComposable()
const currentScreen = computed(() => { const currentScreen = computed(() => {
if (!gameStore.game.isLoaded) return Loading if (!gameStore.game.isLoaded) return Loading
if (!gameStore.connection) return Login if (!gameStore.connection) return Login
if (!gameStore.token) return Login if (!gameStore.token) return Login
if (!gameStore.character) return Characters if (!gameStore.character) return Characters
if (mapEditorStore.active) return MapEditor if (mapEditor.active.value) return MapEditor
return Game return Game
}) })
// Watch mapEditorStore.active and empty gameStore.game.loadedAssets // Watch mapEditor.active and empty gameStore.game.loadedAssets
watch( watch(
() => mapEditorStore.active, () => mapEditor.active.value,
() => { () => {
gameStore.game.loadedTextures = [] gameStore.game.loadedTextures = []
} }

View File

@ -19,7 +19,6 @@ export type TextureData = {
updatedAt: Date updatedAt: Date
originX?: number originX?: number
originY?: number originY?: number
isAnimated?: boolean
frameRate?: number frameRate?: number
frameWidth?: number frameWidth?: number
frameHeight?: number frameHeight?: number
@ -40,7 +39,6 @@ export type MapObject = {
tags: any | null tags: any | null
originX: number originX: number
originY: number originY: number
isAnimated: boolean
frameRate: number frameRate: number
frameWidth: number frameWidth: number
frameHeight: number frameHeight: number
@ -68,7 +66,7 @@ export type Map = {
name: string name: string
width: number width: number
height: number height: number
tiles: any | null tiles: string[][]
pvp: boolean pvp: boolean
mapEffects: MapEffect[] mapEffects: MapEffect[]
mapEventTiles: MapEventTile[] mapEventTiles: MapEventTile[]
@ -105,7 +103,7 @@ export enum MapEventTileType {
export type MapEventTile = { export type MapEventTile = {
id: UUID id: UUID
map: Map mapId: UUID
type: MapEventTileType type: MapEventTileType
positionX: number positionX: number
positionY: number positionY: number
@ -218,22 +216,28 @@ export type Sprite = {
characterTypes: CharacterType[] characterTypes: CharacterType[]
} }
export interface SpriteImage {
url: string
offset: {
x: number
y: number
}
}
export type SpriteAction = { export type SpriteAction = {
id: UUID id: string
sprite: Sprite sprite: string
action: string action: string
sprites: string[] sprites: SpriteImage[]
originX: number originX: number
originY: number originY: number
isAnimated: boolean
isLooping: boolean
frameWidth: number frameWidth: number
frameHeight: number frameHeight: number
frameRate: number frameRate: number
} }
export type Chat = { export type Chat = {
id: UUID id: string
character: Character character: Character
map: Map map: Map
message: string message: string

View File

@ -1,14 +1,14 @@
<template> <template>
<ChatBubble :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" /> <ChatBubble :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" />
<Healthbar :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" /> <Healthbar :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" />
<Container ref="charContainer" :depth="isometricDepth" :x="currentPositionX" :y="currentPositionY"> <CharacterHair :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" />
<Sprite ref="charSprite" :origin-y="1" :flipX="isFlippedX" /> <Sprite ref="charSprite" :depth="isometricDepth" :x="currentPositionX" :y="currentPositionY" :origin-y="1" :flipX="isFlippedX" />
</Container>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import config from '@/application/config' import config from '@/application/config'
import { type MapCharacter } from '@/application/types' import { type MapCharacter } from '@/application/types'
import CharacterHair from '@/components/game/character/partials/CharacterHair.vue'
import ChatBubble from '@/components/game/character/partials/ChatBubble.vue' import ChatBubble from '@/components/game/character/partials/ChatBubble.vue'
import Healthbar from '@/components/game/character/partials/Healthbar.vue' import Healthbar from '@/components/game/character/partials/Healthbar.vue'
import { loadSpriteTextures } from '@/composables/gameComposable' import { loadSpriteTextures } from '@/composables/gameComposable'
@ -16,7 +16,7 @@ import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composabl
import { CharacterTypeStorage } from '@/storage/storages' import { CharacterTypeStorage } from '@/storage/storages'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useMapStore } from '@/stores/mapStore' import { useMapStore } from '@/stores/mapStore'
import { Container, refObj, Sprite, useScene } from 'phavuer' import { refObj, Sprite, useScene } from 'phavuer'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
enum Direction { enum Direction {
@ -30,7 +30,6 @@ const props = defineProps<{
mapCharacter: MapCharacter mapCharacter: MapCharacter
}>() }>()
const charContainer = refObj<Phaser.GameObjects.Container>()
const charSprite = refObj<Phaser.GameObjects.Sprite>() const charSprite = refObj<Phaser.GameObjects.Sprite>()
const charSpriteId = ref('') const charSpriteId = ref('')
@ -106,21 +105,41 @@ const calcDirection = (oldPositionX: number, oldPositionY: number, newPositionX:
const isFlippedX = computed(() => [6, 4].includes(props.mapCharacter.character.rotation ?? 0)) const isFlippedX = computed(() => [6, 4].includes(props.mapCharacter.character.rotation ?? 0))
const currentDirection = computed(() => {
return [0, 6].includes(props.mapCharacter.character.rotation ?? 0) ? 'left_up' : 'right_down'
})
const currentAction = computed(() => {
return props.mapCharacter.isMoving ? 'walk' : 'idle'
})
const charTexture = computed(() => { const charTexture = computed(() => {
const spriteId = charSpriteId.value ?? 'idle_right_down' const spriteId = charSpriteId.value ?? 'idle_right_down'
const action = props.mapCharacter.isMoving ? 'walk' : 'idle' return `${spriteId}-${currentAction.value}_${currentDirection.value}`
const direction = [0, 6].includes(props.mapCharacter.character.rotation) ? 'left_up' : 'right_down'
return `${spriteId}-${action}_${direction}`
}) })
const updateSprite = () => { const updateSprite = () => {
if (!charSprite.value) return
if (props.mapCharacter.isMoving) { if (props.mapCharacter.isMoving) {
charSprite.value!.anims.play(charTexture.value, true) charSprite.value.anims.play(charTexture.value, true)
} else { } else {
charSprite.value!.anims.stop() charSprite.value.anims.stop()
charSprite.value!.setFrame(0) charSprite.value.setFrame(0)
charSprite.value!.setTexture(charTexture.value) charSprite.value.setTexture(charTexture.value)
}
}
const handlePositionUpdate = (newValues: any, oldValues: any) => {
if (!newValues) return
if (!oldValues || newValues.positionX !== oldValues.positionX || newValues.positionY !== oldValues.positionY) {
const direction = !oldValues ? Direction.POSITIVE : calcDirection(oldValues.positionX, oldValues.positionY, newValues.positionX, newValues.positionY)
updatePosition(newValues.positionX, newValues.positionY, direction)
}
if (newValues.isMoving !== oldValues?.isMoving || newValues.rotation !== oldValues?.rotation) {
updateSprite()
} }
} }
@ -131,44 +150,35 @@ watch(
isMoving: props.mapCharacter.isMoving, isMoving: props.mapCharacter.isMoving,
rotation: props.mapCharacter.character.rotation rotation: props.mapCharacter.character.rotation
}), }),
(newValues, oldValues) => { handlePositionUpdate
if (!newValues) return
if (!oldValues || newValues.positionX !== oldValues.positionX || newValues.positionY !== oldValues.positionY) {
const direction = !oldValues ? Direction.POSITIVE : calcDirection(oldValues.positionX, oldValues.positionY, newValues.positionX, newValues.positionY)
updatePosition(newValues.positionX, newValues.positionY, direction)
}
// Handle animation updates
if (newValues.isMoving !== oldValues?.isMoving || newValues.rotation !== oldValues?.rotation) {
updateSprite()
}
}
) )
onMounted(async () => { onMounted(async () => {
let character = props.mapCharacter.character
const characterTypeStorage = new CharacterTypeStorage() const characterTypeStorage = new CharacterTypeStorage()
const spriteId = await characterTypeStorage.getSpriteId(props.mapCharacter.character.characterType!) const spriteId = await characterTypeStorage.getSpriteId(character.characterType!)
if (!spriteId) return if (!spriteId) return
charSpriteId.value = spriteId charSpriteId.value = spriteId
await loadSpriteTextures(scene, spriteId) await loadSpriteTextures(scene, spriteId)
charSprite.value!.setTexture(charTexture.value) if (charSprite.value) {
charSprite.value!.setFlipX(isFlippedX.value) charSprite.value.setTexture(charTexture.value)
charSprite.value.setFlipX(isFlippedX.value)
charSprite.value.setName(props.mapCharacter.character.name)
}
charContainer.value!.setName(props.mapCharacter.character!.name) if (character.id === gameStore.character!.id) {
if (props.mapCharacter.character.id === gameStore.character!.id) {
mapStore.setCharacterLoaded(true) mapStore.setCharacterLoaded(true)
// #146 : Set camera position to character, need to be improved still // #146 : Set camera position to character, need to be improved still
scene.cameras.main.startFollow(charContainer.value as Phaser.GameObjects.Container) scene.cameras.main.startFollow(charSprite.value as Phaser.GameObjects.Sprite)
} }
updatePosition(props.mapCharacter.character.positionX, props.mapCharacter.character.positionY, props.mapCharacter.character.rotation) updatePosition(character.positionX, character.positionY, character.rotation)
}) })
onUnmounted(() => { onUnmounted(() => {

View File

@ -1,13 +1,14 @@
<template> <template>
<Image v-bind="imageProps" v-if="gameStore.getLoadedAsset(texture)" /> <Image v-bind="imageProps" v-if="gameStore.isTextureLoaded(texture)" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { MapCharacter, Sprite as SpriteT } from '@/application/types' import type { MapCharacter, Sprite as SpriteT } from '@/application/types'
import { loadSpriteTextures } from '@/composables/gameComposable' import { loadSpriteTextures } from '@/composables/gameComposable'
import { CharacterHairStorage, CharacterTypeStorage, SpriteStorage } from '@/storage/storages'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { Image, useScene } from 'phavuer' import { Image, useScene } from 'phavuer'
import { computed } from 'vue' import { computed, onMounted, ref } from 'vue'
const props = defineProps<{ const props = defineProps<{
mapCharacter: MapCharacter mapCharacter: MapCharacter
@ -17,35 +18,41 @@ const props = defineProps<{
const gameStore = useGameStore() const gameStore = useGameStore()
const scene = useScene() const scene = useScene()
const hairSpriteId = ref('')
const sprite = ref<SpriteT | null>(null)
const texture = computed(() => { const texture = computed(() => {
const { rotation, characterHair } = props.mapCharacter.character const { rotation } = props.mapCharacter.character
const spriteId = characterHair?.sprite?.id
const direction = [0, 6].includes(rotation) ? 'back' : 'front' const direction = [0, 6].includes(rotation) ? 'back' : 'front'
return `${spriteId}-${direction}` return `${hairSpriteId.value}-${direction}`
}) })
const isFlippedX = computed(() => [6, 4].includes(props.mapCharacter.character.rotation ?? 0)) const isFlippedX = computed(() => [6, 4].includes(props.mapCharacter.character.rotation ?? 0))
const imageProps = computed(() => { const imageProps = computed(() => {
// Get the current sprite action based on direction
const direction = [0, 6].includes(props.mapCharacter.character.rotation ?? 0) ? 'back' : 'front' const direction = [0, 6].includes(props.mapCharacter.character.rotation ?? 0) ? 'back' : 'front'
const spriteAction = props.mapCharacter.character.characterHair?.sprite?.spriteActions?.find((spriteAction) => spriteAction.action === direction) const spriteAction = sprite.value?.spriteActions?.find((spriteAction) => spriteAction.action === direction)
return { return {
depth: 1, depth: 9999,
originX: Number(spriteAction?.originX) ?? 0, originX: Number(spriteAction?.originX) ?? 0,
originY: Number(spriteAction?.originY) ?? 0, originY: Number(spriteAction?.originY) ?? 0,
flipX: isFlippedX.value, flipX: isFlippedX.value,
texture: texture.value, texture: texture.value,
y: props.mapCharacter.isMoving ? Math.floor(Date.now() / 250) % 2 : 0 y: props.currentY,
x: props.currentX
} }
}) })
loadSpriteTextures(scene, props.mapCharacter.character.characterHair?.sprite as SpriteT) onMounted(async () => {
.then(() => {}) const characterHairStorage = new CharacterHairStorage()
.catch((error) => { const spriteId = await characterHairStorage.getSpriteId(props.mapCharacter.character.characterHair!)
console.error('Error loading texture:', error) if (!spriteId) return
})
hairSpriteId.value = spriteId
const spriteStorage = new SpriteStorage()
sprite.value = await spriteStorage.get(spriteId)
await loadSpriteTextures(scene, spriteId)
})
</script> </script>

View File

@ -119,111 +119,44 @@
<script setup lang="ts"> <script setup lang="ts">
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { computed, onMounted, onUnmounted, ref } from 'vue'
const gameStore = useGameStore() const gameStore = useGameStore()
let startX = 0 const width = ref(286)
let startY = 0 const height = ref(483)
let initialX = 0 const x = ref(window.innerWidth / 2 - 143)
let initialY = 0 const y = ref(window.innerHeight / 2 - 241)
let modalPositionX = 0
let modalPositionY = 0
let modalWidth = 286
let modalHeight = 483
const width = ref(modalWidth)
const height = ref(modalHeight)
const x = ref(0)
const y = ref(0)
const isDragging = ref(false) const isDragging = ref(false)
const modalStyle = computed(() => ({ const modalStyle = computed(() => ({
top: `${y.value}px`, top: `${y.value}px`,
left: `${x.value}px`, left: `${x.value}px`,
width: `${width.value}px`, width: `${width.value}px`,
height: `${height.value}px`, height: `${height.value}px`
maxWidth: '100vw',
maxHeight: '100vh'
})) }))
function startDrag(event: MouseEvent) { function startDrag(event: MouseEvent) {
isDragging.value = true isDragging.value = true
startX = event.clientX const startX = event.clientX - x.value
startY = event.clientY const startY = event.clientY - y.value
initialX = x.value
initialY = y.value function drag(event: MouseEvent) {
event.preventDefault() if (!isDragging.value) return
x.value = event.clientX - startX
y.value = event.clientY - startY
}
function stopDrag() {
isDragging.value = false
removeEventListener('mousemove', drag)
removeEventListener('mouseup', stopDrag)
}
addEventListener('mousemove', drag)
addEventListener('mouseup', stopDrag)
} }
function drag(event: MouseEvent) {
if (!isDragging.value) return
const dx = event.clientX - startX
const dy = event.clientY - startY
x.value = initialX + dx
y.value = initialY + dy
adjustPosition()
}
function stopDrag() {
isDragging.value = false
}
function adjustPosition() {
x.value = Math.min(x.value, window.innerWidth - width.value)
y.value = Math.min(y.value, window.innerHeight - height.value)
}
function initializePosition() {
width.value = Math.min(modalWidth, window.innerWidth)
height.value = Math.min(modalHeight, window.innerHeight)
if (modalPositionX !== 0 && modalPositionY !== 0) {
x.value = modalPositionX
y.value = modalPositionY
} else {
x.value = (window.innerWidth - width.value) / 2
y.value = (window.innerHeight - height.value) / 2
}
}
watch(
() => gameStore.uiSettings.isCharacterProfileOpen,
(value) => {
gameStore.uiSettings.isCharacterProfileOpen = value
if (value) {
initializePosition()
}
}
)
watch(
() => modalWidth,
(value) => {
width.value = Math.min(value, window.innerWidth)
}
)
watch(
() => modalHeight,
(value) => {
height.value = Math.min(value, window.innerHeight)
}
)
watch(
() => modalPositionX,
(value) => {
x.value = value
}
)
watch(
() => modalPositionY,
(value) => {
y.value = value
}
)
function keyPress(event: KeyboardEvent) { function keyPress(event: KeyboardEvent) {
if (event.altKey && event.key === 'c') { if (event.altKey && event.key === 'c') {
gameStore.toggleCharacterProfile() gameStore.toggleCharacterProfile()
@ -232,14 +165,9 @@ function keyPress(event: KeyboardEvent) {
onMounted(() => { onMounted(() => {
addEventListener('keydown', keyPress) addEventListener('keydown', keyPress)
addEventListener('mousemove', drag)
addEventListener('mouseup', stopDrag)
initializePosition()
}) })
onUnmounted(() => { onUnmounted(() => {
removeEventListener('keydown', keyPress) removeEventListener('keydown', keyPress)
removeEventListener('mousemove', drag)
removeEventListener('mouseup', stopDrag)
}) })
</script> </script>

View File

@ -1,220 +0,0 @@
<template>
<Scene name="effects" @preload="preloadScene" @create="createScene" @update="updateScene" />
</template>
<script setup lang="ts">
import type { Map, WeatherState } from '@/application/types'
import { MapStorage } from '@/storage/storages'
import { useGameStore } from '@/stores/gameStore'
import { useMapStore } from '@/stores/mapStore'
import { Scene } from 'phavuer'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
// Types
interface LightConfig {
SUNRISE_HOUR: number
SUNSET_HOUR: number
DAY_STRENGTH: number
NIGHT_STRENGTH: number
TRANSITION_HOURS: number
}
interface EffectObjects {
light: Phaser.GameObjects.Graphics | null
rain: Phaser.GameObjects.Particles.ParticleEmitter | null
fog: Phaser.GameObjects.Sprite | null
}
interface EffectValues {
light?: number
rain?: number
fog?: number
[key: string]: number | undefined
}
// Constants
const LIGHT_CONFIG: LightConfig = {
SUNRISE_HOUR: 6,
SUNSET_HOUR: 20,
DAY_STRENGTH: 100,
NIGHT_STRENGTH: 30,
TRANSITION_HOURS: 3
}
// Composables
const useEffects = () => {
const effects = ref<EffectObjects>({
light: null,
rain: null,
fog: null
})
const initializeEffects = (scene: Phaser.Scene) => {
effects.value.light = scene.add.graphics().setDepth(1000)
effects.value.rain = scene.add
.particles(0, 0, 'raindrop', {
x: { min: 0, max: window.innerWidth },
y: -50,
quantity: 5,
lifespan: 2000,
speedY: { min: 300, max: 500 },
scale: { start: 0.005, end: 0.005 },
alpha: { start: 0.5, end: 0 },
blendMode: 'ADD'
})
.setDepth(900)
effects.value.rain.stop()
effects.value.fog = scene.add
.sprite(window.innerWidth / 2, window.innerHeight / 2, 'fog')
.setScale(2)
.setAlpha(0)
.setDepth(950)
}
const applyEffects = (effectValues: EffectValues) => {
if (effects.value.light) {
const darkness = 1 - (effectValues.light ?? 0) / 100
effects.value.light.clear().fillStyle(0x000000, darkness).fillRect(0, 0, window.innerWidth, window.innerHeight)
}
if (effects.value.rain) {
if (effectValues.rain) {
effects.value.rain.start().setQuantity(effectValues.rain / 10)
} else {
effects.value.rain.stop()
}
}
if (effects.value.fog && effectValues.fog !== undefined) {
effects.value.fog.setAlpha(effectValues.fog / 100)
}
}
const handleResize = () => {
if (effects.value.rain) {
effects.value.rain.updateConfig({ x: { min: 0, max: window.innerWidth } })
}
if (effects.value.fog) {
effects.value.fog.setPosition(window.innerWidth / 2, window.innerHeight / 2)
}
}
return { effects, initializeEffects, applyEffects, handleResize }
}
// Store instances
const gameStore = useGameStore()
const mapStore = useMapStore()
const mapStorage = new MapStorage()
// State
const sceneRef = ref<Phaser.Scene | null>(null)
const mapObject = ref<Map | null>(null)
const weatherState = ref<WeatherState>({
rainPercentage: 0,
fogDensity: 0
})
// Effects management
const { effects, initializeEffects, applyEffects, handleResize } = useEffects()
// Utility functions
const lerp = (x: number, y: number, a: number): number => x * (1 - a) + y * a
const calculateLightStrength = (time: Date): number => {
const hour = time.getHours()
const minute = time.getMinutes()
if (hour >= LIGHT_CONFIG.SUNSET_HOUR - LIGHT_CONFIG.TRANSITION_HOURS && hour < LIGHT_CONFIG.SUNSET_HOUR) {
return lerp(LIGHT_CONFIG.DAY_STRENGTH, LIGHT_CONFIG.NIGHT_STRENGTH, (hour + minute / 60 - (LIGHT_CONFIG.SUNSET_HOUR - LIGHT_CONFIG.TRANSITION_HOURS)) / LIGHT_CONFIG.TRANSITION_HOURS)
} else if (hour >= LIGHT_CONFIG.SUNRISE_HOUR && hour < LIGHT_CONFIG.SUNRISE_HOUR + LIGHT_CONFIG.TRANSITION_HOURS) {
return lerp(LIGHT_CONFIG.NIGHT_STRENGTH, LIGHT_CONFIG.DAY_STRENGTH, (hour + minute / 60 - LIGHT_CONFIG.SUNRISE_HOUR) / LIGHT_CONFIG.TRANSITION_HOURS)
} else if (hour > LIGHT_CONFIG.SUNRISE_HOUR && hour < LIGHT_CONFIG.SUNSET_HOUR) {
return LIGHT_CONFIG.DAY_STRENGTH
}
return LIGHT_CONFIG.NIGHT_STRENGTH
}
// Scene handlers
const preloadScene = (scene: Phaser.Scene): void => {
scene.load.image('raindrop', 'assets/raindrop.png')
scene.load.image('fog', 'assets/fog.png')
}
const createScene = (scene: Phaser.Scene): void => {
sceneRef.value = scene
initializeEffects(scene)
setupSocketListeners()
}
const updateScene = (): void => {
const timeBasedLight = calculateLightStrength(gameStore.world.date)
const mapEffects =
mapObject.value?.mapEffects?.reduce<Record<string, number>>(
(acc, curr) => ({
...acc,
[curr.effect]: curr.strength
}),
{}
) ?? {}
const finalEffects: EffectValues = {
...mapEffects,
light: timeBasedLight,
rain: weatherState.value.rainPercentage,
fog: weatherState.value.fogDensity
}
applyEffects(finalEffects)
}
// Map management
const loadMap = async (): Promise<void> => {
if (!mapStore.mapId) return
mapObject.value = await mapStorage.get(mapStore.mapId)
}
// Socket handlers
const setupSocketListeners = (): void => {
gameStore.connection?.emit('weather', (response: WeatherState) => {
weatherState.value = response
updateScene()
})
gameStore.connection?.on('weather', (data: WeatherState) => {
weatherState.value = data
updateScene()
})
gameStore.connection?.on('date', updateScene)
}
// Watchers
watch(
() => mapStore.mapId,
async (newMapId) => {
if (newMapId) {
await loadMap()
updateScene()
}
}
)
watch(
() => mapObject.value,
() => {
updateScene()
}
)
// Lifecycle hooks
onMounted(() => window.addEventListener('resize', handleResize))
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
if (sceneRef.value) sceneRef.value.scene.remove('effects')
gameStore.connection?.off('weather')
})
</script>

View File

@ -4,10 +4,10 @@
<script setup lang="ts"> <script setup lang="ts">
import config from '@/application/config' import config from '@/application/config'
import type { UUID } from '@/application/types' import type { Map as MapT, UUID } from '@/application/types'
import { unduplicateArray } from '@/application/utilities' import { unduplicateArray } from '@/application/utilities'
import Controls from '@/components/utilities/Controls.vue' import Controls from '@/components/utilities/Controls.vue'
import { FlattenMapArray, loadMapTilesIntoScene, setLayerTiles } from '@/composables/mapComposable' import { loadMapTilesIntoScene, setLayerTiles } from '@/composables/mapComposable'
import { MapStorage } from '@/storage/storages' import { MapStorage } from '@/storage/storages'
import { useMapStore } from '@/stores/mapStore' import { useMapStore } from '@/stores/mapStore'
import { useScene } from 'phavuer' import { useScene } from 'phavuer'
@ -23,10 +23,10 @@ const mapStorage = new MapStorage()
const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>() const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
const tileLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>() const tileLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
function createTileMap(mapData: any) { function createTileMap(map: MapT) {
const mapConfig = new Phaser.Tilemaps.MapData({ const mapConfig = new Phaser.Tilemaps.MapData({
width: mapData?.width, width: map.width,
height: mapData?.height, height: map.height,
tileWidth: config.tile_size.width, tileWidth: config.tile_size.width,
tileHeight: config.tile_size.height, tileHeight: config.tile_size.height,
orientation: Phaser.Tilemaps.Orientation.ISOMETRIC, orientation: Phaser.Tilemaps.Orientation.ISOMETRIC,
@ -39,7 +39,7 @@ function createTileMap(mapData: any) {
} }
function createTileLayer(currentTileMap: Phaser.Tilemaps.Tilemap, mapData: any) { function createTileLayer(currentTileMap: Phaser.Tilemaps.Tilemap, mapData: any) {
const tilesArray = unduplicateArray(FlattenMapArray(mapData?.tiles ?? [])) const tilesArray = unduplicateArray(mapData?.tiles.flat())
const tilesetImages = tilesArray.map((tile: string, index: number) => { const tilesetImages = tilesArray.map((tile: string, index: number) => {
return currentTileMap.addTilesetImage(tile, tile, config.tile_size.width, config.tile_size.height, 1, 2, index + 1, { x: 0, y: -config.tile_size.height }) return currentTileMap.addTilesetImage(tile, tile, config.tile_size.width, config.tile_size.height, 1, 2, index + 1, { x: 0, y: -config.tile_size.height })
@ -58,9 +58,10 @@ function createTileLayer(currentTileMap: Phaser.Tilemaps.Tilemap, mapData: any)
loadMapTilesIntoScene(mapStore.mapId as UUID, scene) loadMapTilesIntoScene(mapStore.mapId as UUID, scene)
.then(() => mapStorage.get(mapStore.mapId)) .then(() => mapStorage.get(mapStore.mapId))
.then((mapData) => { .then((mapData) => {
if (!mapData || !mapData?.tiles) return
tileMap.value = createTileMap(mapData) tileMap.value = createTileMap(mapData)
tileLayer.value = createTileLayer(tileMap.value, mapData) tileLayer.value = createTileLayer(tileMap.value, mapData)
setLayerTiles(tileMap.value, tileLayer.value, mapData?.tiles) setLayerTiles(tileMap.value, tileLayer.value, mapData.tiles)
}) })
.catch((error) => console.error('Failed to initialize map:', error)) .catch((error) => console.error('Failed to initialize map:', error))

View File

@ -6,7 +6,7 @@
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">Users</button> <button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">Users</button>
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">Chats</button> <button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">Chats</button>
<button @mousedown.stop class="btn-cyan active py-1.5 px-4 min-w-24">Asset manager</button> <button @mousedown.stop class="btn-cyan active py-1.5 px-4 min-w-24">Asset manager</button>
<button class="btn-cyan py-1.5 px-4 min-w-24" type="button" @click="() => mapEditorStore.toggleActive()">Map editor</button> <button class="btn-cyan py-1.5 px-4 min-w-24" type="button" @click="$emit('open-map-editor')">Map editor</button>
</div> </div>
</template> </template>
<template #modalBody> <template #modalBody>
@ -20,12 +20,13 @@
<script setup lang="ts"> <script setup lang="ts">
import AssetManager from '@/components/gameMaster/assetManager/AssetManager.vue' import AssetManager from '@/components/gameMaster/assetManager/AssetManager.vue'
import Modal from '@/components/utilities/Modal.vue' import Modal from '@/components/utilities/Modal.vue'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useMapEditorStore } from '@/stores/mapEditorStore'
import { ref } from 'vue' import { ref } from 'vue'
defineEmits(['open-map-editor'])
const gameStore = useGameStore() const gameStore = useGameStore()
const mapEditorStore = useMapEditorStore() const mapEditor = useMapEditorComposable()
let toggle = ref('asset-manager') let toggle = ref('asset-manager')
</script> </script>

View File

@ -21,13 +21,6 @@
<label for="tags">Tags</label> <label for="tags">Tags</label>
<ChipsInput v-model="mapObjectTags" @update:modelValue="mapObjectTags = $event" /> <ChipsInput v-model="mapObjectTags" @update:modelValue="mapObjectTags = $event" />
</div> </div>
<div class="form-field-full">
<label for="is-animated">Is animated</label>
<select v-model="mapObjectIsAnimated" class="input-field" name="is-animated">
<option :value="false">No</option>
<option :value="true">Yes</option>
</select>
</div>
<div class="form-field-full"> <div class="form-field-full">
<label for="frame-speed">Frame rate</label> <label for="frame-speed">Frame rate</label>
<input v-model="mapObjectFrameRate" class="input-field" type="number" step="any" name="frame-speed" placeholder="Frame rate" /> <input v-model="mapObjectFrameRate" class="input-field" type="number" step="any" name="frame-speed" placeholder="Frame rate" />
@ -66,7 +59,6 @@ const mapObjectName = ref('')
const mapObjectTags = ref<string[]>([]) const mapObjectTags = ref<string[]>([])
const mapObjectOriginX = ref(0) const mapObjectOriginX = ref(0)
const mapObjectOriginY = ref(0) const mapObjectOriginY = ref(0)
const mapObjectIsAnimated = ref(false)
const mapObjectFrameRate = ref(0) const mapObjectFrameRate = ref(0)
const mapObjectFrameWidth = ref(0) const mapObjectFrameWidth = ref(0)
const mapObjectFrameHeight = ref(0) const mapObjectFrameHeight = ref(0)
@ -80,7 +72,6 @@ if (selectedMapObject.value) {
mapObjectTags.value = selectedMapObject.value.tags mapObjectTags.value = selectedMapObject.value.tags
mapObjectOriginX.value = selectedMapObject.value.originX mapObjectOriginX.value = selectedMapObject.value.originX
mapObjectOriginY.value = selectedMapObject.value.originY mapObjectOriginY.value = selectedMapObject.value.originY
mapObjectIsAnimated.value = selectedMapObject.value.isAnimated
mapObjectFrameRate.value = selectedMapObject.value.frameRate mapObjectFrameRate.value = selectedMapObject.value.frameRate
mapObjectFrameWidth.value = selectedMapObject.value.frameWidth mapObjectFrameWidth.value = selectedMapObject.value.frameWidth
mapObjectFrameHeight.value = selectedMapObject.value.frameHeight mapObjectFrameHeight.value = selectedMapObject.value.frameHeight
@ -120,7 +111,6 @@ function saveObject() {
tags: mapObjectTags.value, tags: mapObjectTags.value,
originX: mapObjectOriginX.value, originX: mapObjectOriginX.value,
originY: mapObjectOriginY.value, originY: mapObjectOriginY.value,
isAnimated: mapObjectIsAnimated.value,
frameRate: mapObjectFrameRate.value, frameRate: mapObjectFrameRate.value,
frameWidth: mapObjectFrameWidth.value, frameWidth: mapObjectFrameWidth.value,
frameHeight: mapObjectFrameHeight.value frameHeight: mapObjectFrameHeight.value
@ -141,7 +131,6 @@ watch(selectedMapObject, (mapObject: MapObject | null) => {
mapObjectTags.value = mapObject.tags mapObjectTags.value = mapObject.tags
mapObjectOriginX.value = mapObject.originX mapObjectOriginX.value = mapObject.originX
mapObjectOriginY.value = mapObject.originY mapObjectOriginY.value = mapObject.originY
mapObjectIsAnimated.value = mapObject.isAnimated
mapObjectFrameRate.value = mapObject.frameRate mapObjectFrameRate.value = mapObject.frameRate
mapObjectFrameWidth.value = mapObject.frameWidth mapObjectFrameWidth.value = mapObject.frameWidth
mapObjectFrameHeight.value = mapObject.frameHeight mapObjectFrameHeight.value = mapObject.frameHeight

View File

@ -21,9 +21,12 @@
<button class="btn-cyan py-2 my-4" type="button" @click.prevent="addNewImage">New action</button> <button class="btn-cyan py-2 my-4" type="button" @click.prevent="addNewImage">New action</button>
<Accordion v-for="action in spriteActions" :key="action.id"> <Accordion v-for="action in spriteActions" :key="action.id">
<template #header> <template #header>
<div class="flex justify-between items-center"> <div class="flex items-center">
{{ action.action }} {{ action.action }}
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="() => spriteActions.splice(spriteActions.indexOf(action), 1)">Delete</button> <div class="ml-auto space-x-2">
<button class="btn-cyan px-4 py-1.5 min-w-24" type="button" @click.stop.prevent="openPreviewModal(action)">View</button>
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.stop.prevent="() => spriteActions.splice(spriteActions.indexOf(action), 1)">Delete</button>
</div>
</div> </div>
</template> </template>
<template #content> <template #content>
@ -40,38 +43,38 @@
<label for="origin-y">Origin Y</label> <label for="origin-y">Origin Y</label>
<input v-model.number="action.originY" class="input-field" type="number" step="any" name="origin-y" placeholder="Origin Y" /> <input v-model.number="action.originY" class="input-field" type="number" step="any" name="origin-y" placeholder="Origin Y" />
</div> </div>
<div class="form-field-half"> <div class="form-field-full">
<label for="is-animated">Is animated</label>
<select v-model="action.isAnimated" class="input-field" name="is-animated">
<option :value="false">No</option>
<option :value="true">Yes</option>
</select>
</div>
<div class="form-field-half" v-if="action.isAnimated">
<label for="is-looping">Is looping</label>
<select v-model="action.isLooping" class="input-field" name="is-looping">
<option :value="false">No</option>
<option :value="true">Yes</option>
</select>
</div>
<div class="form-field-full" v-if="action.isAnimated">
<label for="frame-speed">Frame rate</label> <label for="frame-speed">Frame rate</label>
<input v-model.number="action.frameRate" class="input-field" type="number" step="any" name="frame-speed" placeholder="Frame rate" /> <input v-model.number="action.frameRate" class="input-field" type="number" step="any" name="frame-speed" placeholder="Frame rate" />
</div> </div>
<div class="form-field-full"> <div class="form-field-full">
<SpriteActionsInput v-model="action.sprites" /> <SpriteActionsInput
v-model="action.sprites"
@tempOffsetChange="(index, offset) => handleTempOffsetChange(action, index, offset)"
/>
</div> </div>
</form> </form>
</template> </template>
</Accordion> </Accordion>
<SpritePreview
v-if="selectedAction"
:sprites="selectedAction.sprites"
:frame-rate="selectedAction.frameRate"
:is-modal-open="isModalOpen"
:temp-offset-index="tempOffsetData.index"
:temp-offset="tempOffsetData.offset"
@update:frame-rate="updateFrameRate"
@update:is-modal-open="isModalOpen = $event"
/>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { Sprite, SpriteAction } from '@/application/types' import type { Sprite, SpriteAction, UUID } from '@/application/types'
import { uuidv4 } from '@/application/utilities' import { uuidv4 } from '@/application/utilities'
import SpriteActionsInput from '@/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue' 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 Accordion from '@/components/utilities/Accordion.vue'
import { useAssetManagerStore } from '@/stores/assetManagerStore' import { useAssetManagerStore } from '@/stores/assetManagerStore'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
@ -84,6 +87,8 @@ const selectedSprite = computed(() => assetManagerStore.selectedSprite)
const spriteName = ref('') const spriteName = ref('')
const spriteActions = ref<SpriteAction[]>([]) const spriteActions = ref<SpriteAction[]>([])
const isModalOpen = ref(false)
const selectedAction = ref<SpriteAction | null>(null)
if (!selectedSprite.value) { if (!selectedSprite.value) {
console.error('No sprite selected') console.error('No sprite selected')
@ -140,8 +145,6 @@ function saveSprite() {
sprites: action.sprites, sprites: action.sprites,
originX: action.originX, originX: action.originX,
originY: action.originY, originY: action.originY,
isAnimated: action.isAnimated,
isLooping: action.isLooping,
frameRate: action.frameRate, frameRate: action.frameRate,
frameWidth: action.frameWidth, frameWidth: action.frameWidth,
frameHeight: action.frameHeight frameHeight: action.frameHeight
@ -163,14 +166,11 @@ function addNewImage() {
const newImage: SpriteAction = { const newImage: SpriteAction = {
id: uuidv4(), id: uuidv4(),
spriteId: selectedSprite.value.id, sprite: selectedSprite.value.id,
sprite: selectedSprite.value,
action: 'new_action', action: 'new_action',
sprites: [], sprites: [],
originX: 0, originX: 0,
originY: 0, originY: 0,
isAnimated: false,
isLooping: false,
frameRate: 0, frameRate: 0,
frameWidth: 0, frameWidth: 0,
frameHeight: 0 frameHeight: 0
@ -188,12 +188,40 @@ function sortSpriteActions(actions: SpriteAction[]): SpriteAction[] {
return [...actions].sort((a, b) => a.action.localeCompare(b.action)) return [...actions].sort((a, b) => a.action.localeCompare(b.action))
} }
function openPreviewModal(action: SpriteAction) {
selectedAction.value = action
isModalOpen.value = true
}
function updateFrameRate(value: number) {
if (selectedAction.value) {
selectedAction.value.frameRate = value
}
}
const tempOffsetData = ref<{ index: number | undefined; offset: { x: number; y: number } | undefined }>({
index: undefined,
offset: undefined
})
function handleTempOffsetChange(action: SpriteAction, index: number, offset: { x: number; y: number }) {
if (selectedAction.value === action) {
tempOffsetData.value = { index, offset }
}
}
watch(selectedSprite, (sprite: Sprite | null) => { watch(selectedSprite, (sprite: Sprite | null) => {
if (!sprite) return if (!sprite) return
spriteName.value = sprite.name spriteName.value = sprite.name
spriteActions.value = sortSpriteActions(sprite.spriteActions) spriteActions.value = sortSpriteActions(sprite.spriteActions)
}) })
watch(isModalOpen, (newValue) => {
if (!newValue) {
selectedAction.value = null
}
})
onMounted(() => { onMounted(() => {
if (!selectedSprite.value) return if (!selectedSprite.value) return
}) })

View File

@ -1,19 +1,46 @@
<template> <template>
<div class="flex flex-wrap gap-3"> <div class="flex flex-wrap gap-3">
<div v-for="(image, index) in modelValue" :key="index" class="h-20 w-20 p-4 bg-gray-300 bg-opacity-50 rounded text-center relative group cursor-move" draggable="true" @dragstart="dragStart($event, index)" @dragover.prevent @dragenter.prevent @drop="drop($event, index)"> <div v-for="(image, index) in modelValue" :key="index" class="h-20 w-20 p-4 bg-gray-300 bg-opacity-50 rounded text-center relative group cursor-move" draggable="true" @dragstart="dragStart($event, index)" @dragover.prevent @dragenter.prevent @drop="drop($event, index)">
<img :src="image" class="max-w-full max-h-full object-contain pointer-events-none" alt="Uploaded image" /> <img :src="image.url" class="max-w-full max-h-full object-contain pointer-events-none" alt="Uploaded image" @load="updateImageDimensions($event, index)" />
<div v-if="image.dimensions" class="absolute bottom-1 right-1 bg-black/50 text-white text-xs px-1 py-0.5 rounded transition-opacity font-default">
{{ image.dimensions.width }}x{{ image.dimensions.height }}
</div>
<div class="absolute top-1 left-1 flex-row space-y-1"> <div class="absolute top-1 left-1 flex-row space-y-1">
<button @click.stop="deleteImage(index)" class="bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity" aria-label="Delete image"> <button @click.stop="deleteImage(index)" class="bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity" aria-label="Delete image">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg> </svg>
</button> </button>
<button class="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity" aria-label="Scope image"> <button @click.stop="openOffsetModal(index)" class="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity" aria-label="Scope image">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg width="50px" height="50px" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> <path d="M8.29289 3.70711L1 11V15H5L12.2929 7.70711L8.29289 3.70711Z" fill="white" />
<path d="M9.70711 2.29289L13.7071 6.29289L15.1716 4.82843C15.702 4.29799 16 3.57857 16 2.82843C16 1.26633 14.7337 0 13.1716 0C12.4214 0 11.702 0.297995 11.1716 0.828428L9.70711 2.29289Z" fill="white" />
</svg> </svg>
</button> </button>
</div> </div>
<Modal :is-modal-open="selectedImageIndex === index" :modal-width="300" :modal-height="210" :is-resizable="false" :bg-style="'none'" @modal:close="closeOffsetModal">
<template #modalHeader>
<h3 class="m-0 font-medium shrink-0 text-white">Action offset ({{ selectedImageIndex }})</h3>
</template>
<template #modalBody>
<div class="m-4">
<form method="post" @submit.prevent="saveOffset(index)" class="inline">
<div class="gap-2.5 flex flex-wrap">
<div class="form-field-half">
<label for="offsetX">Offset X</label>
<input class="input-field max-w-64" v-model="tempOffset.x" name="offsetX" id="offsetX" type="number" />
</div>
<div class="form-field-half">
<label for="offsetY">Offset Y</label>
<input class="input-field max-w-64" v-model="tempOffset.y" name="offsetY" id="offsetY" type="number" />
</div>
</div>
<button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button>
</form>
</div>
</template>
</Modal>
</div> </div>
<div class="h-20 w-20 p-4 bg-gray-200 bg-opacity-50 rounded justify-center items-center flex hover:cursor-pointer" @click="triggerFileInput" @drop.prevent="onDrop" @dragover.prevent> <div class="h-20 w-20 p-4 bg-gray-200 bg-opacity-50 rounded justify-center items-center flex hover:cursor-pointer" @click="triggerFileInput" @drop.prevent="onDrop" @dragover.prevent>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 invert" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 invert" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -25,10 +52,23 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import Modal from '@/components/utilities/Modal.vue'
import { ref, watch } from 'vue'
interface SpriteImage {
url: string
offset: {
x: number
y: number
}
dimensions?: {
width: number
height: number
}
}
interface Props { interface Props {
modelValue: string[] modelValue: SpriteImage[]
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@ -36,11 +76,15 @@ const props = withDefaults(defineProps<Props>(), {
}) })
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', value: string[]): void (e: 'update:modelValue', value: SpriteImage[]): void
(e: 'close'): void
(e: 'tempOffsetChange', index: number, offset: { x: number, y: number }): void
}>() }>()
const fileInput = ref<HTMLInputElement | null>(null) const fileInput = ref<HTMLInputElement | null>(null)
const draggedIndex = ref<number | null>(null) const draggedIndex = ref<number | null>(null)
const selectedImageIndex = ref<number | null>(null)
const tempOffset = ref({ x: 0, y: 0 })
const triggerFileInput = () => { const triggerFileInput = () => {
fileInput.value?.click() fileInput.value?.click()
@ -61,19 +105,25 @@ const onDrop = (event: DragEvent) => {
const handleFiles = (files: FileList) => { const handleFiles = (files: FileList) => {
Array.from(files).forEach((file) => { Array.from(files).forEach((file) => {
if (file.type.startsWith('image/')) { if (!file.type.startsWith('image/')) {
const reader = new FileReader() return
reader.onload = (e) => {
if (typeof e.target?.result === 'string') {
updateImages([...props.modelValue, e.target.result])
}
}
reader.readAsDataURL(file)
} }
const reader = new FileReader()
reader.onload = (e) => {
if (typeof e.target?.result === 'string') {
const newImage: SpriteImage = {
url: e.target.result,
offset: { x: 0, y: 0 }
}
updateImages([...props.modelValue, newImage])
}
}
reader.readAsDataURL(file)
}) })
} }
const updateImages = (newImages: string[]) => { const updateImages = (newImages: SpriteImage[]) => {
emit('update:modelValue', newImages) emit('update:modelValue', newImages)
} }
@ -101,4 +151,44 @@ const drop = (event: DragEvent, dropIndex: number) => {
} }
draggedIndex.value = null draggedIndex.value = null
} }
const openOffsetModal = (index: number) => {
selectedImageIndex.value = index
tempOffset.value = { ...props.modelValue[index].offset }
}
const closeOffsetModal = () => {
selectedImageIndex.value = null
}
const saveOffset = (index: number) => {
const newImages = [...props.modelValue]
newImages[index] = {
...newImages[index],
offset: { ...tempOffset.value }
}
updateImages(newImages)
closeOffsetModal()
}
const onOffsetChange = () => {
if (selectedImageIndex.value !== null) {
emit('tempOffsetChange', selectedImageIndex.value, tempOffset.value)
}
}
watch(tempOffset, onOffsetChange, { deep: true })
const updateImageDimensions = (event: Event, index: number) => {
const img = event.target as HTMLImageElement
const newImages = [...props.modelValue]
newImages[index] = {
...newImages[index],
dimensions: {
width: img.naturalWidth,
height: img.naturalHeight
}
}
updateImages(newImages)
}
</script> </script>

View File

@ -0,0 +1,154 @@
<template>
<Modal :is-modal-open="isModalOpen" :modal-width="700" :modal-height="330" :bg-style="'none'" @modal:close="closeModal">
<template #modalHeader>
<h3 class="m-0 font-medium shrink-0 text-white">View sprite</h3>
</template>
<template #modalBody>
<div class="m-4 flex gap-8">
<div class="relative">
<div
class="sprite-container bg-gray-800"
:style="{
width: `${maxWidth}px`,
height: `${maxHeight}px`,
position: 'relative',
overflow: 'hidden'
}"
>
<img
v-for="(sprite, index) in spritesWithTempOffset"
:key="index"
:src="sprite.url"
alt="Sprite"
:style="{
position: 'absolute',
left: `${sprite.offset?.x || 0}px`,
bottom: `${sprite.offset?.y || 0}px`,
display: currentFrame === index ? 'block' : 'none',
transform: `scale(${zoomLevel / 100})`,
transformOrigin: 'bottom left'
}"
@load="updateContainerSize"
/>
</div>
</div>
<div class="flex flex-col justify-center gap-8 flex-1">
<div class="flex flex-col">
<label class="block mb-2 text-white">Frame Rate: {{ frameRate }} FPS</label>
<input type="range" v-model.number="localFrameRate" min="0" max="60" step="1" class="w-full accent-cyan-500" @input="updateFrameRate" />
</div>
<div class="flex flex-col">
<label class="block mb-2 text-white">Frame: {{ currentFrame + 1 }} of {{ sprites.length }}</label>
<input
type="range"
v-model.number="currentFrame"
:min="0"
:max="sprites.length - 1"
step="1"
class="w-full accent-cyan-500"
@input="stopAnimation"
/>
</div>
<div class="flex flex-col">
<label class="block mb-2 text-white">Zoom: {{ zoomLevel }}%</label>
<input type="range" v-model.number="zoomLevel" min="10" max="200" step="10" class="w-full accent-cyan-500" />
</div>
</div>
</div>
</template>
</Modal>
</template>
<script setup lang="ts">
import type { SpriteImage } from '@/application/types'
import Modal from '@/components/utilities/Modal.vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
const props = defineProps<{
sprites: SpriteImage[]
frameRate: number
isModalOpen?: boolean
tempOffsetIndex?: number
tempOffset?: { x: number, y: number }
}>()
const emit = defineEmits<{
(e: 'update:frameRate', value: number): void
(e: 'update:isModalOpen', value: boolean): void
}>()
const currentFrame = ref(0)
const maxWidth = ref(250)
const maxHeight = ref(250)
const localFrameRate = ref(props.frameRate)
const zoomLevel = ref(100)
let animationInterval: number | null = null
const spritesWithTempOffset = computed(() => {
return props.sprites.map((sprite, index) => {
if (index === props.tempOffsetIndex && props.tempOffset) {
return { ...sprite, offset: props.tempOffset }
}
return sprite
})
})
function updateContainerSize(event: Event) {
const img = event.target as HTMLImageElement
maxWidth.value = Math.max(maxWidth.value, img.naturalWidth)
maxHeight.value = Math.max(maxHeight.value, img.naturalHeight)
}
function updateAnimation() {
stopAnimation()
if (props.frameRate <= 0 || props.sprites.length === 0) {
currentFrame.value = 0
return
}
animationInterval = window.setInterval(() => {
currentFrame.value = (currentFrame.value + 1) % props.sprites.length
}, 1000 / props.frameRate)
}
function stopAnimation() {
if (animationInterval) {
clearInterval(animationInterval)
animationInterval = null
}
}
function updateFrameRate() {
emit('update:frameRate', localFrameRate.value)
}
function closeModal() {
emit('update:isModalOpen', false)
}
watch(
() => props.frameRate,
(newValue) => {
localFrameRate.value = newValue
updateAnimation()
},
{ immediate: true }
)
watch(() => props.sprites, updateAnimation, { immediate: true })
onMounted(() => {
updateAnimation()
})
onUnmounted(() => {
stopAnimation()
})
</script>
<style scoped>
.sprite-container {
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
</style>

View File

@ -29,7 +29,6 @@ import ChipsInput from '@/components/forms/ChipsInput.vue'
import { TileStorage } from '@/storage/storages' import { TileStorage } from '@/storage/storages'
import { useAssetManagerStore } from '@/stores/assetManagerStore' import { useAssetManagerStore } from '@/stores/assetManagerStore'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useMapEditorStore } from '@/stores/mapEditorStore'
import { computed, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue' import { computed, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue'
const gameStore = useGameStore() const gameStore = useGameStore()

View File

@ -1,20 +1,54 @@
<template> <template>
<MapTiles @tileMap:create="tileMap = $event" /> <MapTiles ref="mapTiles" @tileMap:create="tileMap = $event" />
<PlacedMapObjects v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" /> <PlacedMapObjects ref="mapObjects" v-if="tileMap" :tileMap="tileMap as Phaser.Tilemaps.Tilemap" />
<MapEventTiles v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" /> <MapEventTiles ref="eventTiles" v-if="tileMap" :tileMap="tileMap as Phaser.Tilemaps.Tilemap" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import MapEventTiles from '@/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue' import MapEventTiles from '@/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue'
import MapTiles from '@/components/gameMaster/mapEditor/mapPartials/MapTiles.vue' import MapTiles from '@/components/gameMaster/mapEditor/mapPartials/MapTiles.vue'
import PlacedMapObjects from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue' import PlacedMapObjects from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue'
import { useMapEditorStore } from '@/stores/mapEditorStore' import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { onMounted, onUnmounted, shallowRef } from 'vue' import { useScene } from 'phavuer'
import { onMounted, onUnmounted, shallowRef, useTemplateRef } from 'vue'
const mapEditorStore = useMapEditorStore()
const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>() const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
const mapEditor = useMapEditorComposable()
const scene = useScene()
const mapTiles = useTemplateRef('mapTiles')
const mapObjects = useTemplateRef('mapObjects')
const eventTiles = useTemplateRef('eventTiles')
function handlePointer(pointer: Phaser.Input.Pointer) {
if (!mapTiles.value || !mapObjects.value || !eventTiles.value) return
// Check if left mouse button is pressed
if (!pointer.isDown) return
// Check if shift is not pressed, this means we are moving the camera
if (pointer.event.shiftKey) return
// Check if draw mode is tile
switch (mapEditor.drawMode.value) {
case 'tile':
mapTiles.value.handlePointer(pointer)
case 'object':
mapObjects.value.handlePointer(pointer)
case 'teleport':
eventTiles.value.handlePointer(pointer)
}
}
onMounted(() => {
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointer)
scene.input.on(Phaser.Input.Events.POINTER_DOWN, handlePointer)
})
onUnmounted(() => { onUnmounted(() => {
mapEditorStore.reset() scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointer)
scene.input.off(Phaser.Input.Events.POINTER_DOWN, handlePointer)
mapEditor.reset()
}) })
</script> </script>

View File

@ -1,88 +1,86 @@
<template> <template>
<Image v-for="tile in mapEditorStore.map?.mapEventTiles" v-bind="getImageProps(tile)" /> <Image v-for="tile in mapEditor.currentMap.value?.mapEventTiles" v-bind="getImageProps(tile)" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { MapEventTileType, type MapEventTile } from '@/application/types' import { MapEventTileType, type MapEventTile, type Map as MapT } from '@/application/types'
import { uuidv4 } from '@/application/utilities' import { uuidv4 } from '@/application/utilities'
import { getTile, tileToWorldX, tileToWorldY } from '@/composables/mapComposable' import { getTile, tileToWorldX, tileToWorldY } from '@/composables/mapComposable'
import { useMapEditorStore } from '@/stores/mapEditorStore' import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { Image, useScene } from 'phavuer' import { Image, useScene } from 'phavuer'
import { onMounted, onUnmounted } from 'vue' import { shallowRef } from 'vue'
const scene = useScene() const mapEditor = useMapEditorComposable()
const mapEditorStore = useMapEditorStore()
defineExpose({ handlePointer })
const props = defineProps<{ const props = defineProps<{
tilemap: Phaser.Tilemaps.Tilemap tileMap: Phaser.Tilemaps.Tilemap
}>() }>()
const tileLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
function getImageProps(tile: MapEventTile) { function getImageProps(tile: MapEventTile) {
return { return {
x: tileToWorldX(props.tilemap, tile.positionX, tile.positionY), x: tileToWorldX(props.tileMap, tile.positionX, tile.positionY),
y: tileToWorldY(props.tilemap, tile.positionX, tile.positionY), y: tileToWorldY(props.tileMap, tile.positionX, tile.positionY),
texture: tile.type, texture: tile.type,
depth: 999 depth: 999
} }
} }
function pencil(pointer: Phaser.Input.Pointer) { function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
// Check if map is set if (!tileLayer.value) return
if (!mapEditorStore.map) return
// Check if tool is pencil
if (mapEditorStore.tool !== 'pencil') return
// Check if draw mode is blocking tile or teleport
if (mapEditorStore.drawMode !== 'blocking tile' && mapEditorStore.drawMode !== 'teleport') return
// Check if left mouse button is pressed
if (!pointer.isDown) return
// Check if shift is not pressed, this means we are moving the camera
if (pointer.event.shiftKey) return
// Check if there is a tile // Check if there is a tile
const tile = getTile(props.tilemap, pointer.worldX, pointer.worldY) const tile = getTile(tileLayer.value, pointer.worldX, pointer.worldY)
if (!tile) return if (!tile) return
// Check if event tile already exists on position // Check if event tile already exists on position
const existingEventTile = mapEditorStore.map.mapEventTiles.find((eventTile) => eventTile.positionX === tile.x && eventTile.positionY === tile.y) const existingEventTile = map.mapEventTiles.find((eventTile) => eventTile.positionX === tile.x && eventTile.positionY === tile.y)
if (existingEventTile) return if (existingEventTile) return
// If teleport, check if there is a selected map // If teleport, check if there is a selected map
if (mapEditorStore.drawMode === 'teleport' && !mapEditorStore.teleportSettings.toMap) return if (mapEditor.drawMode.value === 'teleport' && !mapEditor.teleportSettings.value.toMapId) return
const newEventTile = { const newEventTile = {
id: uuidv4(), id: uuidv4(),
mapId: mapEditorStore.map.id, mapId: map?.id,
map: mapEditorStore.map, map: map?.id,
type: mapEditorStore.drawMode === 'blocking tile' ? MapEventTileType.BLOCK : MapEventTileType.TELEPORT, type: mapEditor.drawMode.value === 'blocking tile' ? MapEventTileType.BLOCK : MapEventTileType.TELEPORT,
positionX: tile.x, positionX: tile.x,
positionY: tile.y, positionY: tile.y,
teleport: teleport:
mapEditorStore.drawMode === 'teleport' mapEditor.drawMode.value === 'teleport'
? { ? {
toMap: mapEditorStore.teleportSettings.toMap, toMap: mapEditor.teleportSettings.value.toMapId,
toPositionX: mapEditorStore.teleportSettings.toPositionX, toPositionX: mapEditor.teleportSettings.value.toPositionX,
toPositionY: mapEditorStore.teleportSettings.toPositionY, toPositionY: mapEditor.teleportSettings.value.toPositionY,
toRotation: mapEditorStore.teleportSettings.toRotation toRotation: mapEditor.teleportSettings.value.toRotation
} }
: undefined : undefined
} }
mapEditorStore.map.mapEventTiles = mapEditorStore.map.mapEventTiles.concat(newEventTile as MapEventTile) map!.mapEventTiles = map!.mapEventTiles.concat(newEventTile as MapEventTile)
} }
function eraser(pointer: Phaser.Input.Pointer) { function erase(pointer: Phaser.Input.Pointer, map: MapT) {
// Check if map is set if (!tileLayer.value) return
if (!mapEditorStore.map) return // Check if there is a tile
const tile = getTile(tileLayer.value, pointer.worldX, pointer.worldY)
if (!tile) return
// Check if tool is pencil // Check if event tile already exists on position
if (mapEditorStore.tool !== 'eraser') return const existingEventTile = map.mapEventTiles.find((eventTile) => eventTile.positionX === tile.x && eventTile.positionY === tile.y)
if (!existingEventTile) return
// Check if draw mode is blocking tile or teleport // Remove existing event tile
if (mapEditorStore.eraserMode !== 'blocking tile' && mapEditorStore.eraserMode !== 'teleport') return map.mapEventTiles = map.mapEventTiles.filter((eventTile) => eventTile.id !== existingEventTile.id)
}
function handlePointer(pointer: Phaser.Input.Pointer) {
const map = mapEditor.currentMap.value
if (!map) return
// Check if left mouse button is pressed // Check if left mouse button is pressed
if (!pointer.isDown) return if (!pointer.isDown) return
@ -90,29 +88,13 @@ function eraser(pointer: Phaser.Input.Pointer) {
// Check if shift is not pressed, this means we are moving the camera // Check if shift is not pressed, this means we are moving the camera
if (pointer.event.shiftKey) return if (pointer.event.shiftKey) return
// Check if there is a tile switch (mapEditor.tool.value) {
const tile = getTile(props.tilemap, pointer.worldX, pointer.worldY) case 'pencil':
if (!tile) return pencil(pointer, map)
break
// Check if event tile already exists on position case 'eraser':
const existingEventTile = mapEditorStore.map.mapEventTiles.find((eventTile) => eventTile.positionX === tile.x && eventTile.positionY === tile.y) erase(pointer, map)
if (!existingEventTile) return break
}
// Remove existing event tile
mapEditorStore.map.mapEventTiles = mapEditorStore.map.mapEventTiles.filter((eventTile) => eventTile.id !== existingEventTile.id)
} }
onMounted(() => {
scene.input.on(Phaser.Input.Events.POINTER_DOWN, pencil)
scene.input.on(Phaser.Input.Events.POINTER_MOVE, pencil)
scene.input.on(Phaser.Input.Events.POINTER_DOWN, eraser)
scene.input.on(Phaser.Input.Events.POINTER_MOVE, eraser)
})
onUnmounted(() => {
scene.input.off(Phaser.Input.Events.POINTER_DOWN, pencil)
scene.input.off(Phaser.Input.Events.POINTER_MOVE, pencil)
scene.input.off(Phaser.Input.Events.POINTER_DOWN, eraser)
scene.input.off(Phaser.Input.Events.POINTER_MOVE, eraser)
})
</script> </script>

View File

@ -8,7 +8,6 @@ import Controls from '@/components/utilities/Controls.vue'
import { createTileArray, getTile, placeTile, setLayerTiles } from '@/composables/mapComposable' import { createTileArray, getTile, placeTile, setLayerTiles } from '@/composables/mapComposable'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable' import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { TileStorage } from '@/storage/storages' import { TileStorage } from '@/storage/storages'
import { useMapEditorStore } from '@/stores/mapEditorStore'
import { useScene } from 'phavuer' import { useScene } from 'phavuer'
import { onMounted, onUnmounted, shallowRef, watch } from 'vue' import { onMounted, onUnmounted, shallowRef, watch } from 'vue'
@ -18,12 +17,13 @@ const emit = defineEmits(['tileMap:create'])
const scene = useScene() const scene = useScene()
const mapEditor = useMapEditorComposable() const mapEditor = useMapEditorComposable()
const mapEditorStore = useMapEditorStore()
const tileStorage = new TileStorage() const tileStorage = new TileStorage()
const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>() const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
const tileLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>() const tileLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
defineExpose({ handlePointer })
function createTileMap() { function createTileMap() {
const mapData = new Phaser.Tilemaps.MapData({ const mapData = new Phaser.Tilemaps.MapData({
width: mapEditor.currentMap.value?.width, width: mapEditor.currentMap.value?.width,
@ -58,58 +58,31 @@ async function createTileLayer(currentTileMap: Phaser.Tilemaps.Tilemap) {
} }
function pencil(pointer: Phaser.Input.Pointer) { function pencil(pointer: Phaser.Input.Pointer) {
if (!tileMap.value || !tileLayer.value) return let map = mapEditor.currentMap.value
if (!map) return
// Check if map is set
if (!mapEditor.currentMap.value) return
// Check if tool is pencil
if (mapEditorStore.tool !== 'pencil') return
// Check if draw mode is tile
if (mapEditorStore.drawMode !== 'tile') return
// Check if there is a selected tile // Check if there is a selected tile
if (!mapEditorStore.selectedTile) return if (!mapEditor.selectedTile.value) return
// Check if left mouse button is pressed if (!tileMap.value || !tileLayer.value) return
if (!pointer.isDown) return
// Check if shift is not pressed, this means we are moving the camera
if (pointer.event.shiftKey) return
// Check if there is a tile // Check if there is a tile
const tile = getTile(tileLayer.value, pointer.worldX, pointer.worldY) const tile = getTile(tileLayer.value, pointer.worldX, pointer.worldY)
if (!tile) return if (!tile) return
// Place tile // Place tile
placeTile(tileMap.value, tileLayer.value, tile.x, tile.y, mapEditorStore.selectedTile) placeTile(tileMap.value, tileLayer.value, tile.x, tile.y, mapEditor.selectedTile.value)
// Adjust mapEditorStore.map.tiles // Adjust mapEditorStore.map.tiles
mapEditor.currentMap.value.tiles[tile.y][tile.x] = mapEditor.currentMap.value.tiles[tile.y][tile.x] map.tiles[tile.y][tile.x] = mapEditor.selectedTile.value
} }
function eraser(pointer: Phaser.Input.Pointer) { function eraser(pointer: Phaser.Input.Pointer) {
let map = mapEditor.currentMap.value
if (!map) return
if (!tileMap.value || !tileLayer.value) return if (!tileMap.value || !tileLayer.value) return
// Check if map is set
if (!mapEditor.currentMap.value) return
// Check if tool is pencil
if (mapEditorStore.tool !== 'eraser') return
// Check if draw mode is tile
if (mapEditorStore.eraserMode !== 'tile') return
// Check if left mouse button is pressed
if (!pointer.isDown) return
// Check if shift is not pressed, this means we are moving the camera
if (pointer.event.shiftKey) return
// Check if alt is pressed
if (pointer.event.altKey) return
// Check if there is a tile // Check if there is a tile
const tile = getTile(tileLayer.value, pointer.worldX, pointer.worldY) const tile = getTile(tileLayer.value, pointer.worldX, pointer.worldY)
if (!tile) return if (!tile) return
@ -118,82 +91,79 @@ function eraser(pointer: Phaser.Input.Pointer) {
placeTile(tileMap.value, tileLayer.value, tile.x, tile.y, 'blank_tile') placeTile(tileMap.value, tileLayer.value, tile.x, tile.y, 'blank_tile')
// Adjust mapEditorStore.map.tiles // Adjust mapEditorStore.map.tiles
mapEditor.currentMap.value.tiles[tile.y][tile.x] = 'blank_tile' map.tiles[tile.y][tile.x] = 'blank_tile'
} }
function paint(pointer: Phaser.Input.Pointer) { function paint(pointer: Phaser.Input.Pointer) {
if (!tileMap.value || !tileLayer.value) return if (!tileMap.value || !tileLayer.value) return
// Check if map is set
if (!mapEditor.currentMap.value) return
// Check if tool is pencil
if (mapEditorStore.tool !== 'paint') return
// Check if there is a selected tile
if (!mapEditorStore.selectedTile) return
// Check if left mouse button is pressed
if (!pointer.isDown) return
// Check if shift is not pressed, this means we are moving the camera
if (pointer.event.shiftKey) return
// Check if alt is pressed
if (pointer.event.altKey) return
// Set new tileArray with selected tile // Set new tileArray with selected tile
setLayerTiles(tileMap.value, tileLayer.value, createTileArray(tileMap.value.width, tileMap.value.height, mapEditorStore.selectedTile)) const tileArray = createTileArray(tileMap.value.width, tileMap.value.height, mapEditor.selectedTile.value)
setLayerTiles(tileMap.value, tileLayer.value, tileArray)
// Adjust mapEditorStore.map.tiles // Adjust mapEditorStore.map.tiles
mapEditor.currentMap.value.tiles = createTileArray(tileMap.value.width, tileMap.value.height, mapEditor.currentMap.value.tiles) if (mapEditor.currentMap.value) {
mapEditor.currentMap.value.tiles = tileArray
}
} }
// When alt is pressed, and the pointer is down, select the tile that the pointer is over // When alt is pressed, and the pointer is down, select the tile that the pointer is over
function tilePicker(pointer: Phaser.Input.Pointer) { function tilePicker(pointer: Phaser.Input.Pointer) {
let map = mapEditor.currentMap.value
if (!map) return
if (!tileMap.value || !tileLayer.value) return if (!tileMap.value || !tileLayer.value) return
// Check if map is set
if (!mapEditor.currentMap.value) return
// Check if tool is pencil
if (mapEditorStore.tool !== 'pencil') return
// Check if draw mode is tile
if (mapEditorStore.drawMode !== 'tile') return
// Check if left mouse button is pressed
if (!pointer.isDown) return
// Check if shift is not pressed, this means we are moving the camera
if (pointer.event.shiftKey) return
// Check if alt is pressed
if (!pointer.event.altKey) return
// Check if there is a tile // Check if there is a tile
const tile = getTile(tileLayer.value, pointer.worldX, pointer.worldY) const tile = getTile(tileLayer.value, pointer.worldX, pointer.worldY)
if (!tile) return if (!tile) return
// Select the tile // Select the tile
mapEditorStore.setSelectedMapObject(mapEditor.currentMap.value.tiles[tile.y][tile.x]) mapEditor.setSelectedTile(map.tiles[tile.y][tile.x])
} }
watch( watch(
() => mapEditorStore.shouldClearTiles, () => mapEditor.shouldClearTiles,
(shouldClear) => { (shouldClear) => {
if (shouldClear && mapEditor.currentMap.value && tileMap.value && tileLayer.value) { if (shouldClear && mapEditor.currentMap.value && tileMap.value && tileLayer.value) {
const blankTiles = createTileArray(tileMap.value.width, tileMap.value.height, 'blank_tile') const blankTiles = createTileArray(tileLayer.value.width, tileLayer.value.height, 'blank_tile')
setLayerTiles(tileMap.value, tileLayer.value, blankTiles) setLayerTiles(tileMap.value, tileLayer.value, blankTiles)
mapEditor.currentMap.value.tiles = blankTiles mapEditor.currentMap.value.tiles = blankTiles
mapEditorStore.resetClearTilesFlag() mapEditor.resetClearTilesFlag()
} }
} }
) )
function handlePointer(pointer: Phaser.Input.Pointer) {
if (!tileMap.value || !tileLayer.value) return
// Check if left mouse button is pressed
if (!pointer.isDown) return
// Check if shift is not pressed, this means we are moving the camera
if (pointer.event.shiftKey) return
// Check if alt is pressed
if (pointer.event.altKey) {
tilePicker(pointer)
return
}
// Check if draw mode is tile
switch (mapEditor.tool.value) {
case 'pencil':
pencil(pointer)
break
case 'eraser':
eraser(pointer)
break
case 'paint':
paint(pointer)
break
}
}
onMounted(async () => { onMounted(async () => {
if (!mapEditor.currentMap.value?.tiles) return if (!mapEditor.currentMap.value) return
console.log(mapEditor.currentMap.value)
tileMap.value = createTileMap() tileMap.value = createTileMap()
tileLayer.value = await createTileLayer(tileMap.value) tileLayer.value = await createTileLayer(tileMap.value)
@ -212,19 +182,9 @@ onMounted(async () => {
} }
setLayerTiles(tileMap.value, tileLayer.value, blankTiles) setLayerTiles(tileMap.value, tileLayer.value, blankTiles)
scene.input.on(Phaser.Input.Events.POINTER_MOVE, pencil)
scene.input.on(Phaser.Input.Events.POINTER_MOVE, eraser)
scene.input.on(Phaser.Input.Events.POINTER_DOWN, paint)
scene.input.on(Phaser.Input.Events.POINTER_DOWN, tilePicker)
}) })
onUnmounted(() => { onUnmounted(() => {
scene.input.off(Phaser.Input.Events.POINTER_MOVE, pencil)
scene.input.off(Phaser.Input.Events.POINTER_MOVE, eraser)
scene.input.off(Phaser.Input.Events.POINTER_DOWN, paint)
scene.input.off(Phaser.Input.Events.POINTER_DOWN, tilePicker)
if (tileMap.value) { if (tileMap.value) {
tileMap.value.destroyLayer('tiles') tileMap.value.destroyLayer('tiles')
tileMap.value.removeAllLayers() tileMap.value.removeAllLayers()

View File

@ -11,7 +11,7 @@ import { Image, useScene } from 'phavuer'
import { computed } from 'vue' import { computed } from 'vue'
const props = defineProps<{ const props = defineProps<{
tilemap: Phaser.Tilemaps.Tilemap tileMap: Phaser.Tilemaps.Tilemap
placedMapObject: PlacedMapObject placedMapObject: PlacedMapObject
selectedPlacedMapObject: PlacedMapObject | null selectedPlacedMapObject: PlacedMapObject | null
movingPlacedMapObject: PlacedMapObject | null movingPlacedMapObject: PlacedMapObject | null
@ -24,8 +24,8 @@ const imageProps = computed(() => ({
alpha: props.movingPlacedMapObject?.id === props.placedMapObject.id ? 0.5 : 1, alpha: props.movingPlacedMapObject?.id === props.placedMapObject.id ? 0.5 : 1,
tint: props.selectedPlacedMapObject?.id === props.placedMapObject.id ? 0x00ff00 : 0xffffff, tint: props.selectedPlacedMapObject?.id === props.placedMapObject.id ? 0x00ff00 : 0xffffff,
depth: calculateIsometricDepth(props.placedMapObject.positionX, props.placedMapObject.positionY, props.placedMapObject.mapObject.frameWidth, props.placedMapObject.mapObject.frameHeight), depth: calculateIsometricDepth(props.placedMapObject.positionX, props.placedMapObject.positionY, props.placedMapObject.mapObject.frameWidth, props.placedMapObject.mapObject.frameHeight),
x: tileToWorldX(props.tilemap, props.placedMapObject.positionX, props.placedMapObject.positionY), x: tileToWorldX(props.tileMap, props.placedMapObject.positionX, props.placedMapObject.positionY),
y: tileToWorldY(props.tilemap, props.placedMapObject.positionX, props.placedMapObject.positionY), y: tileToWorldY(props.tileMap, props.placedMapObject.positionX, props.placedMapObject.positionY),
flipX: props.placedMapObject.isRotated, flipX: props.placedMapObject.isRotated,
texture: props.placedMapObject.mapObject.id, texture: props.placedMapObject.mapObject.id,
originY: Number(props.placedMapObject.mapObject.originX), originY: Number(props.placedMapObject.mapObject.originX),

View File

@ -1,146 +1,77 @@
<template> <template>
<SelectedPlacedMapObjectComponent v-if="selectedPlacedMapObject" :placedMapObject="selectedPlacedMapObject" @move="moveMapObject" @rotate="rotatePlacedMapObject" @delete="deletePlacedMapObject" /> <SelectedPlacedMapObjectComponent v-if="selectedPlacedMapObject" :placedMapObject="selectedPlacedMapObject" @move="moveMapObject" @rotate="rotatePlacedMapObject" @delete="deletePlacedMapObject" />
<PlacedMapObject v-for="placedMapObject in mapEditorStore.map?.placedMapObjects" :tilemap="tilemap" :placedMapObject :selectedPlacedMapObject :movingPlacedMapObject @pointerup="clickPlacedMapObject(placedMapObject)" /> <PlacedMapObject v-for="placedMapObject in mapEditor.currentMap.value?.placedMapObjects" :tileMap="tileMap" :placedMapObject :selectedPlacedMapObject :movingPlacedMapObject @pointerup="clickPlacedMapObject(placedMapObject)" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { PlacedMapObject as PlacedMapObjectT } from '@/application/types' import type { Map as MapT, PlacedMapObject as PlacedMapObjectT } from '@/application/types'
import { uuidv4 } from '@/application/utilities' import { uuidv4 } from '@/application/utilities'
import PlacedMapObject from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObject.vue' import PlacedMapObject from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObject.vue'
import SelectedPlacedMapObjectComponent from '@/components/gameMaster/mapEditor/partials/SelectedPlacedMapObject.vue' import SelectedPlacedMapObjectComponent from '@/components/gameMaster/mapEditor/partials/SelectedPlacedMapObject.vue'
import { getTile } from '@/composables/mapComposable' import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { useMapEditorStore } from '@/stores/mapEditorStore'
import { useScene } from 'phavuer' import { useScene } from 'phavuer'
import { onMounted, onUnmounted, ref, watch } from 'vue' import { ref, watch } from 'vue'
const scene = useScene() const scene = useScene()
const mapEditorStore = useMapEditorStore() const mapEditor = useMapEditorComposable()
const selectedPlacedMapObject = ref<PlacedMapObjectT | null>(null) const selectedPlacedMapObject = ref<PlacedMapObjectT | null>(null)
const movingPlacedMapObject = ref<PlacedMapObjectT | null>(null) const movingPlacedMapObject = ref<PlacedMapObjectT | null>(null)
const props = defineProps<{ const props = defineProps<{
tilemap: Phaser.Tilemaps.Tilemap tileMap: Phaser.Tilemaps.Tilemap
}>() }>()
function pencil(pointer: Phaser.Input.Pointer) { defineExpose({ handlePointer })
// Check if map is set
if (!mapEditorStore.map) return
// Check if tool is pencil
if (mapEditorStore.tool !== 'pencil') return
// Check if draw mode is map_object
if (mapEditorStore.drawMode !== 'map_object') return
// Check if there is a selected object
if (!mapEditorStore.selectedMapObject) return
// Check if left mouse button is pressed
if (!pointer.isDown) return
// Check if shift is not pressed, this means we are moving the camera
if (pointer.event.shiftKey) return
// Check if alt is pressed, this means we are selecting the object
if (pointer.event.altKey) return
// Check if there is a tile
const tile = getTile(props.tilemap, pointer.worldX, pointer.worldY)
if (!tile) return
function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
// Check if object already exists on position // Check if object already exists on position
const existingPlacedMapObject = mapEditorStore.map.placedMapObjects.find((placedMapObject) => placedMapObject.positionX === tile.x && placedMapObject.positionY === tile.y) const existingPlacedMapObject = findInMap(pointer, map)
if (existingPlacedMapObject) return if (existingPlacedMapObject) return
const newPlacedMapObject = { const newPlacedMapObject: PlacedMapObjectT = {
id: uuidv4(), id: uuidv4(),
map: mapEditorStore.map, depth: 0,
mapObject: mapEditorStore.selectedMapObject, map: map,
mapObject: mapEditor.selectedMapObject.value!,
isRotated: false, isRotated: false,
positionX: tile.x, positionX: pointer.x,
positionY: tile.y positionY: pointer.y
} }
// Add new object to mapObjects // Add new object to mapObjects
mapEditorStore.map.placedMapObjects = mapEditorStore.map.placedMapObjects.concat(newPlacedMapObject as PlacedMapObjectT) map.placedMapObjects.concat(newPlacedMapObject)
} }
function eraser(pointer: Phaser.Input.Pointer) { function eraser(pointer: Phaser.Input.Pointer, map: MapT) {
// Check if map is set
if (!mapEditorStore.map) return
// Check if tool is eraser
if (mapEditorStore.tool !== 'eraser') return
// Check if draw mode is map_object
if (mapEditorStore.eraserMode !== 'map_object') return
// Check if left mouse button is pressed
if (!pointer.isDown) return
// Check if shift is not pressed, this means we are moving the camera
if (pointer.event.shiftKey) return
// Check if alt is pressed, this means we are selecting the object
if (pointer.event.altKey) return
// Check if there is a tile
const tile = getTile(props.tilemap, pointer.worldX, pointer.worldY)
if (!tile) return
// Check if object already exists on position // Check if object already exists on position
const existingPlacedMapObject = mapEditorStore.map.placedMapObjects.find((placedMapObject) => placedMapObject.positionX === tile.x && placedMapObject.positionY === tile.y) const existingPlacedMapObject = findInMap(pointer, map)
if (!existingPlacedMapObject) return if (!existingPlacedMapObject) return
// Remove existing object // Remove existing object
mapEditorStore.map.placedMapObjects = mapEditorStore.map.placedMapObjects.filter((placedMapObject) => placedMapObject.id !== existingPlacedMapObject.id) map.placedMapObjects = map.placedMapObjects.filter((placedMapObject) => placedMapObject.id !== existingPlacedMapObject.id)
} }
function objectPicker(pointer: Phaser.Input.Pointer) { function findInMap(pointer: Phaser.Input.Pointer, map: MapT) {
// Check if map is set return map.placedMapObjects.find((placedMapObject) => placedMapObject.positionX === pointer.worldX && placedMapObject.positionY === pointer.worldY)
if (!mapEditorStore.map) return }
// Check if tool is pencil
if (mapEditorStore.tool !== 'pencil') return
// Check if draw mode is map_object
if (mapEditorStore.drawMode !== 'map_object') return
// Check if left mouse button is pressed
if (!pointer.isDown) return
// Check if shift is not pressed, this means we are moving the camera
if (pointer.event.shiftKey) return
// If alt is not pressed, return
if (!pointer.event.altKey) return
// Check if there is a tile
const tile = getTile(props.tilemap, pointer.worldX, pointer.worldY)
if (!tile) return
function objectPicker(pointer: Phaser.Input.Pointer, map: MapT) {
// Check if object already exists on position // Check if object already exists on position
const existingPlacedMapObject = mapEditorStore.map.placedMapObjects.find((placedMapObject) => placedMapObject.positionX === tile.x && placedMapObject.positionY === tile.y) const existingPlacedMapObject = findInMap(pointer, map)
if (!existingPlacedMapObject) return if (!existingPlacedMapObject) return
// Select the object // Select the object
mapEditorStore.setSelectedMapObject(existingPlacedMapObject.mapObject) mapEditor.setSelectedMapObject(existingPlacedMapObject.mapObject)
} }
function moveMapObject(id: string) { function moveMapObject(id: string, map: MapT) {
// Check if map is set movingPlacedMapObject.value = map.placedMapObjects.find((object) => object.id === id) as PlacedMapObjectT
if (!mapEditorStore.map) return
movingPlacedMapObject.value = mapEditorStore.map.placedMapObjects.find((object) => object.id === id) as PlacedMapObjectT
function handlePointerMove(pointer: Phaser.Input.Pointer) { function handlePointerMove(pointer: Phaser.Input.Pointer) {
if (!movingPlacedMapObject.value) return if (!movingPlacedMapObject.value) return
const tile = getTile(props.tilemap, pointer.worldX, pointer.worldY) movingPlacedMapObject.value.positionX = pointer.worldX
if (!tile) return movingPlacedMapObject.value.positionY = pointer.worldY
movingPlacedMapObject.value.positionX = tile.x
movingPlacedMapObject.value.positionY = tile.y
} }
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove) scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
@ -153,11 +84,8 @@ function moveMapObject(id: string) {
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp) scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
} }
function rotatePlacedMapObject(id: string) { function rotatePlacedMapObject(id: string, map: MapT) {
// Check if map is set map.placedMapObjects = map.placedMapObjects.map((placedMapObject) => {
if (!mapEditorStore.map) return
mapEditorStore.map.placedMapObjects = mapEditorStore.map.placedMapObjects.map((placedMapObject) => {
if (placedMapObject.id === id) { if (placedMapObject.id === id) {
return { return {
...placedMapObject, ...placedMapObject,
@ -168,11 +96,8 @@ function rotatePlacedMapObject(id: string) {
}) })
} }
function deletePlacedMapObject(id: string) { function deletePlacedMapObject(id: string, map: MapT) {
// Check if map is set map.placedMapObjects = map.placedMapObjects.filter((object) => object.id !== id)
if (!mapEditorStore.map) return
mapEditorStore.map.placedMapObjects = mapEditorStore.map.placedMapObjects.filter((object) => object.id !== id)
selectedPlacedMapObject.value = null selectedPlacedMapObject.value = null
} }
@ -181,41 +106,55 @@ function clickPlacedMapObject(placedMapObject: PlacedMapObjectT) {
// If alt is pressed, select the object // If alt is pressed, select the object
if (scene.input.activePointer.event.altKey) { if (scene.input.activePointer.event.altKey) {
mapEditorStore.setSelectedMapObject(placedMapObject.mapObject) mapEditor.setSelectedMapObject(placedMapObject.mapObject)
} }
} }
onMounted(() => { function handlePointer(pointer: Phaser.Input.Pointer) {
scene.input.on(Phaser.Input.Events.POINTER_DOWN, pencil) const map = mapEditor.currentMap.value
scene.input.on(Phaser.Input.Events.POINTER_MOVE, pencil) if (!map) return
scene.input.on(Phaser.Input.Events.POINTER_DOWN, eraser)
scene.input.on(Phaser.Input.Events.POINTER_MOVE, eraser)
scene.input.on(Phaser.Input.Events.POINTER_DOWN, objectPicker)
})
onUnmounted(() => { if (mapEditor.drawMode.value !== 'map_object') return
scene.input.off(Phaser.Input.Events.POINTER_DOWN, pencil)
scene.input.off(Phaser.Input.Events.POINTER_MOVE, pencil) // Check if left mouse button is pressed
scene.input.off(Phaser.Input.Events.POINTER_DOWN, eraser) if (!pointer.isDown) return
scene.input.off(Phaser.Input.Events.POINTER_MOVE, eraser)
scene.input.off(Phaser.Input.Events.POINTER_DOWN, objectPicker) // Check if shift is not pressed, this means we are moving the camera
}) if (pointer.event.shiftKey) return
// Check if alt is pressed, this means we are selecting the object
if (pointer.event.altKey) return
// Check if tool is pencil
switch (mapEditor.tool.value) {
case 'pencil':
if (mapEditor.selectedMapObject.value) pencil(pointer, map)
break
case 'eraser':
eraser(pointer, map)
break
case 'object picker':
objectPicker(pointer, map)
break
}
}
// watch mapEditorStore.mapObjectList and update originX and originY of objects in mapObjects // watch mapEditorStore.mapObjectList and update originX and originY of objects in mapObjects
watch( watch(
() => mapEditorStore.mapObjectList, () => mapEditor.currentMap.value,
(newMapObjects) => { () => {
if (!mapEditorStore.map) return const map = mapEditor.currentMap.value
if (!map) return
const updatedMapObjects = mapEditorStore.map.placedMapObjects.map((mapObject) => { const updatedMapObjects = map.placedMapObjects.map((mapObject) => {
const updatedMapObject = newMapObjects.find((obj) => obj.id === mapObject.mapObject.id) const updatedMapObject = map.placedMapObjects.find((obj) => obj.id === mapObject.mapObject.id)
if (updatedMapObject) { if (updatedMapObject) {
return { return {
...mapObject, ...mapObject,
mapObject: { mapObject: {
...mapObject.mapObject, ...mapObject.mapObject,
originX: updatedMapObject.originX, originX: updatedMapObject.positionX,
originY: updatedMapObject.originY originY: updatedMapObject.positionY
} }
} }
} }
@ -223,19 +162,16 @@ watch(
}) })
// Update the map with the new mapObjects // Update the map with the new mapObjects
mapEditorStore.setMap({ map.placedMapObjects = [...map.placedMapObjects, ...updatedMapObjects]
...mapEditorStore.map,
placedMapObjects: updatedMapObjects
})
// Update selectedMapObject if it's set // Update mapObject if it's set
if (mapEditorStore.selectedMapObject) { if (mapEditor.selectedMapObject.value) {
const updatedMapObject = newMapObjects.find((obj) => obj.id === mapEditorStore.selectedMapObject?.id) const updatedMapObject = map.placedMapObjects.find((obj) => obj.id === mapEditor.selectedMapObject.value?.id)
if (updatedMapObject) { if (updatedMapObject) {
mapEditorStore.setSelectedMapObject({ mapEditor.setSelectedMapObject({
...mapEditorStore.selectedMapObject, ...mapEditor.selectedMapObject.value,
originX: updatedMapObject.originX, originX: updatedMapObject.positionX,
originY: updatedMapObject.originY originY: updatedMapObject.positionY
}) })
} }
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<Modal :isModalOpen="mapEditorStore.isCreateMapModalShown" @modal:close="() => mapEditorStore.toggleCreateMapModal()" :modal-width="300" :modal-height="420" :is-resizable="false" :bg-style="'none'"> <Modal ref="modalRef" :modal-width="300" :modal-height="420" :is-resizable="false" :bg-style="'none'">
<template #modalHeader> <template #modalHeader>
<h3 class="m-0 font-medium shrink-0 text-white">Create new map</h3> <h3 class="m-0 font-medium shrink-0 text-white">Create new map</h3>
</template> </template>
@ -13,11 +13,11 @@
</div> </div>
<div class="form-field-half"> <div class="form-field-half">
<label for="name">Width</label> <label for="name">Width</label>
<input class="input-field max-w-64" v-model="width" name="name" id="name" type="number" /> <input class="input-field max-w-64" v-model="width" name="width" id="width" type="number" />
</div> </div>
<div class="form-field-half"> <div class="form-field-half">
<label for="name">Height</label> <label for="name">Height</label>
<input class="input-field max-w-64" v-model="height" name="name" id="name" type="number" /> <input class="input-field max-w-64" v-model="height" name="height" id="height" type="number" />
</div> </div>
<div class="form-field-full"> <div class="form-field-full">
<label for="name">PVP enabled</label> <label for="name">PVP enabled</label>
@ -37,22 +37,24 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Map } from '@/application/types' import type { Map } from '@/application/types'
import Modal from '@/components/utilities/Modal.vue' import Modal from '@/components/utilities/Modal.vue'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { MapStorage } from '@/storage/storages' import { MapStorage } from '@/storage/storages'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useMapEditorStore } from '@/stores/mapEditorStore' import { ref, useTemplateRef } from 'vue'
import { ref } from 'vue'
const emit = defineEmits(['create']) const emit = defineEmits(['create'])
const gameStore = useGameStore() const gameStore = useGameStore()
const mapEditorStore = useMapEditorStore()
const mapStorage = new MapStorage() const mapStorage = new MapStorage()
const modalRef = useTemplateRef('modalRef')
const name = ref('') const name = ref('')
const width = ref(0) const width = ref(0)
const height = ref(0) const height = ref(0)
const pvp = ref(false) const pvp = ref(false)
defineExpose({ open: () => modalRef.value?.open() })
async function submit() { async function submit() {
gameStore.connection?.emit('gm:map:create', { name: name.value, width: width.value, height: height.value }, async (response: Map | false) => { gameStore.connection?.emit('gm:map:create', { name: name.value, width: width.value, height: height.value }, async (response: Map | false) => {
if (!response) { if (!response) {
@ -79,6 +81,6 @@ async function submit() {
}) })
// Close modal // Close modal
mapEditorStore.toggleCreateMapModal() modalRef.value?.close()
} }
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<Modal :is-modal-open="mapEditorStore.isMapListModalShown" @modal:close="() => mapEditorStore.toggleMapListModal()" :is-resizable="false" :modal-width="300" :modal-height="360" :bg-style="'none'"> <Modal ref="modalRef" :is-resizable="false" :modal-width="300" :modal-height="360" :bg-style="'none'">
<template #modalHeader> <template #modalHeader>
<h3 class="text-lg text-white">Maps</h3> <h3 class="text-lg text-white">Maps</h3>
</template> </template>
@ -7,7 +7,7 @@
<div class="my-4 mx-auto h-full"> <div class="my-4 mx-auto h-full">
<div class="text-center mb-4 px-2 flex gap-2.5"> <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="fetchMaps">Refresh</button>
<button class="btn-cyan py-1.5 min-w-[calc(50%_-_5px)]" @click="() => mapEditorStore.toggleCreateMapModal()">New</button> <button class="btn-cyan py-1.5 min-w-[calc(50%_-_5px)]" @click="createMapModal?.open">New</button>
</div> </div>
<div class="overflow-y-auto h-[calc(100%-20px)]"> <div class="overflow-y-auto h-[calc(100%-20px)]">
<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="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">
@ -25,7 +25,7 @@
</template> </template>
</Modal> </Modal>
<CreateMap @create="fetchMaps" v-if="mapEditorStore.isMapListModalShown" /> <CreateMap ref="createMapModal" @create="fetchMaps" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -36,14 +36,21 @@ import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { MapStorage } from '@/storage/storages' import { MapStorage } from '@/storage/storages'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useMapEditorStore } from '@/stores/mapEditorStore' import { useMapEditorStore } from '@/stores/mapEditorStore'
import { onMounted, ref } from 'vue' import { onMounted, ref, useTemplateRef } from 'vue'
const gameStore = useGameStore() const gameStore = useGameStore()
const mapEditorStore = useMapEditorStore()
const mapEditor = useMapEditorComposable() const mapEditor = useMapEditorComposable()
const mapStorage = new MapStorage() const mapStorage = new MapStorage()
const mapList = ref<Map[]>([]) const mapList = ref<Map[]>([])
const modalRef = useTemplateRef('modalRef')
const createMapModal = useTemplateRef('createMapModal')
defineEmits(['open-create-map'])
defineExpose({
open: () => modalRef.value?.open()
})
onMounted(async () => { onMounted(async () => {
await fetchMaps() await fetchMaps()
@ -57,7 +64,7 @@ function loadMap(id: UUID) {
gameStore.connection?.emit('gm:map:request', { mapId: id }, (response: Map) => { gameStore.connection?.emit('gm:map:request', { mapId: id }, (response: Map) => {
mapEditor.loadMap(response) mapEditor.loadMap(response)
}) })
mapEditorStore.toggleMapListModal() modalRef.value?.close()
} }
async function deleteMap(id: UUID) { async function deleteMap(id: UUID) {

View File

@ -1,5 +1,5 @@
<template> <template>
<Modal :isModalOpen="mapEditorStore.isMapObjectListModalShown" :modal-width="645" :modal-height="260" @modal:close="() => (mapEditorStore.isMapObjectListModalShown = false)" :bg-style="'none'"> <Modal ref="modalRef" :modal-width="645" :modal-height="260" :bg-style="'none'">
<template #modalHeader> <template #modalHeader>
<h3 class="text-lg text-white">Map objects</h3> <h3 class="text-lg text-white">Map objects</h3>
</template> </template>
@ -25,11 +25,11 @@
class="border-2 border-solid max-w-full" class="border-2 border-solid max-w-full"
:src="`${config.server_endpoint}/textures/map_objects/${mapObject.id}.png`" :src="`${config.server_endpoint}/textures/map_objects/${mapObject.id}.png`"
alt="Object" alt="Object"
@click="mapEditorStore.setSelectedMapObject(mapObject)" @click="mapEditor.setSelectedMapObject(mapObject)"
:class="{ :class="{
'cursor-pointer transition-all duration-300': true, 'cursor-pointer transition-all duration-300': true,
'border-cyan shadow-lg scale-105': mapEditorStore.selectedMapObject?.id === mapObject.id, 'border-cyan shadow-lg scale-105': mapEditor.selectedMapObject.value?.id === mapObject.id,
'border-transparent hover:border-gray-300': mapEditorStore.selectedMapObject?.id !== mapObject.id 'border-transparent hover:border-gray-300': mapEditor.selectedMapObject.value?.id !== mapObject.id
}" }"
/> />
</div> </div>
@ -44,18 +44,23 @@
import config from '@/application/config' import config from '@/application/config'
import type { MapObject } from '@/application/types' import type { MapObject } from '@/application/types'
import Modal from '@/components/utilities/Modal.vue' import Modal from '@/components/utilities/Modal.vue'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { MapObjectStorage } from '@/storage/storages' import { MapObjectStorage } from '@/storage/storages'
import { useGameStore } from '@/stores/gameStore'
import { useMapEditorStore } from '@/stores/mapEditorStore'
import { liveQuery } from 'dexie' import { liveQuery } from 'dexie'
import { computed, onMounted, onUnmounted, ref } from 'vue' import { computed, onMounted, onUnmounted, ref, useTemplateRef } from 'vue'
const mapObjectStorage = new MapObjectStorage() const mapObjectStorage = new MapObjectStorage()
const isModalOpen = ref(false) const isModalOpen = ref(false)
const mapEditorStore = useMapEditorStore() const mapEditor = useMapEditorComposable()
const searchQuery = ref('') const searchQuery = ref('')
const selectedTags = ref<string[]>([]) const selectedTags = ref<string[]>([])
const mapObjectList = ref<MapObject[]>([]) const mapObjectList = ref<MapObject[]>([])
const modalRef = useTemplateRef('modalRef')
defineExpose({
open: () => modalRef.value?.open(),
close: () => modalRef.value?.close()
})
const uniqueTags = computed(() => { const uniqueTags = computed(() => {
const allTags = mapObjectList.value.flatMap((obj) => obj.tags || []) const allTags = mapObjectList.value.flatMap((obj) => obj.tags || [])

View File

@ -1,5 +1,5 @@
<template> <template>
<Modal :is-modal-open="mapEditorStore.isSettingsModalShown" @modal:close="() => mapEditorStore.toggleSettingsModal()" :modal-width="600" :modal-height="430" :bg-style="'none'"> <Modal ref="modalRef" :modal-width="600" :modal-height="430" :bg-style="'none'">
<template #modalHeader> <template #modalHeader>
<h3 class="m-0 font-medium shrink-0 text-white">Map settings</h3> <h3 class="m-0 font-medium shrink-0 text-white">Map settings</h3>
</template> </template>
@ -51,11 +51,9 @@ import type { UUID } from '@/application/types'
import { uuidv4 } from '@/application/utilities' import { uuidv4 } from '@/application/utilities'
import Modal from '@/components/utilities/Modal.vue' import Modal from '@/components/utilities/Modal.vue'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable' import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { useMapEditorStore } from '@/stores/mapEditorStore' import { ref, useTemplateRef, watch } from 'vue'
import { ref, watch } from 'vue'
const mapEditor = useMapEditorComposable() const mapEditor = useMapEditorComposable()
const mapEditorStore = useMapEditorStore()
const screen = ref('settings') const screen = ref('settings')
const name = ref(mapEditor.currentMap.value?.name) const name = ref(mapEditor.currentMap.value?.name)
@ -63,6 +61,11 @@ const width = ref(mapEditor.currentMap.value?.width)
const height = ref(mapEditor.currentMap.value?.height) const height = ref(mapEditor.currentMap.value?.height)
const pvp = ref(mapEditor.currentMap.value?.pvp) const pvp = ref(mapEditor.currentMap.value?.pvp)
const mapEffects = ref(mapEditor.currentMap.value?.mapEffects || []) const mapEffects = ref(mapEditor.currentMap.value?.mapEffects || [])
const modalRef = useTemplateRef('modalRef')
defineExpose({
open: () => modalRef.value?.open()
})
watch(name, (value) => { watch(name, (value) => {
mapEditor.updateProperty('name', value!) mapEditor.updateProperty('name', value!)

View File

@ -1,5 +1,5 @@
<template> <template>
<Modal :is-modal-open="showTeleportModal" @modal:close="() => mapEditorStore.setTool('move')" :modal-width="300" :modal-height="350" :is-resizable="false" :bg-style="'none'"> <Modal ref="modalRef" @modal:close="() => mapEditorStore.setTool('move')" :modal-width="300" :modal-height="350" :is-resizable="false" :bg-style="'none'">
<template #modalHeader> <template #modalHeader>
<h3 class="m-0 font-medium shrink-0 text-white">Teleport settings</h3> <h3 class="m-0 font-medium shrink-0 text-white">Teleport settings</h3>
</template> </template>
@ -28,7 +28,7 @@
<label for="toMap">Map to teleport to</label> <label for="toMap">Map to teleport to</label>
<select v-model="toMap" 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 :value="null">Select map</option>
<option v-for="map in mapEditorStore.mapList" :key="map.id" :value="map">{{ map.name }}</option> <option v-for="map in mapList" :key="map.id" :value="map">{{ map.name }}</option>
</select> </select>
</div> </div>
</div> </div>
@ -43,17 +43,23 @@ import type { Map } from '@/application/types'
import Modal from '@/components/utilities/Modal.vue' import Modal from '@/components/utilities/Modal.vue'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useMapEditorStore } from '@/stores/mapEditorStore' import { useMapEditorStore } from '@/stores/mapEditorStore'
import { computed, onMounted, ref, watch } from 'vue' import { computed, onMounted, ref, useTemplateRef, watch } from 'vue'
const showTeleportModal = computed(() => mapEditorStore.tool === 'pencil' && mapEditorStore.drawMode === 'teleport') const showTeleportModal = computed(() => mapEditorStore.tool === 'pencil' && mapEditorStore.drawMode === 'teleport')
const mapEditorStore = useMapEditorStore() const mapEditorStore = useMapEditorStore()
const gameStore = useGameStore() const gameStore = useGameStore()
const mapList = ref<Map[]>([])
const modalRef = useTemplateRef('modalRef')
defineExpose({
open: () => modalRef.value?.open()
})
onMounted(fetchMaps) onMounted(fetchMaps)
function fetchMaps() { function fetchMaps() {
gameStore.connection?.emit('gm:map:list', {}, (response: Map[]) => { gameStore.connection?.emit('gm:map:list', {}, (response: Map[]) => {
mapEditorStore.setMapList(response) mapList.value = response
}) })
} }
@ -65,7 +71,7 @@ function useRefTeleportSettings() {
toPositionX: ref(settings.toPositionX), toPositionX: ref(settings.toPositionX),
toPositionY: ref(settings.toPositionY), toPositionY: ref(settings.toPositionY),
toRotation: ref(settings.toRotation), toRotation: ref(settings.toRotation),
toMap: ref(settings.toMap) toMap: ref(settings.toMapId)
} }
} }
@ -76,7 +82,7 @@ function updateTeleportSettings() {
toPositionX: toPositionX.value, toPositionX: toPositionX.value,
toPositionY: toPositionY.value, toPositionY: toPositionY.value,
toRotation: toRotation.value, toRotation: toRotation.value,
toMap: toMap.value toMapId: toMap.value
}) })
} }
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<Modal :isModalOpen="mapEditorStore.isTileListModalShown" :modal-width="645" :modal-height="600" @modal:close="() => (mapEditorStore.isTileListModalShown = false)" :bg-style="'none'"> <Modal ref="modalRef" :modal-width="645" :modal-height="600" :bg-style="'none'">
<template #modalHeader> <template #modalHeader>
<h3 class="text-lg text-white">Tiles</h3> <h3 class="text-lg text-white">Tiles</h3>
</template> </template>
@ -84,18 +84,24 @@
import config from '@/application/config' import config from '@/application/config'
import type { Tile } from '@/application/types' import type { Tile } from '@/application/types'
import Modal from '@/components/utilities/Modal.vue' import Modal from '@/components/utilities/Modal.vue'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { TileStorage } from '@/storage/storages' import { TileStorage } from '@/storage/storages'
import { useMapEditorStore } from '@/stores/mapEditorStore'
import { liveQuery } from 'dexie' import { liveQuery } from 'dexie'
import { computed, onMounted, onUnmounted, ref } from 'vue' import { computed, onMounted, onUnmounted, ref, useTemplateRef } from 'vue'
const tileStorage = new TileStorage() const tileStorage = new TileStorage()
const mapEditorStore = useMapEditorStore() const mapEditor = useMapEditorComposable()
const searchQuery = ref('') const searchQuery = ref('')
const selectedTags = ref<string[]>([]) const selectedTags = ref<string[]>([])
const tileCategories = ref<Map<string, string>>(new Map()) const tileCategories = ref<Map<string, string>>(new Map())
const selectedGroup = ref<{ parent: Tile; children: Tile[] } | null>(null) const selectedGroup = ref<{ parent: Tile; children: Tile[] } | null>(null)
const tiles = ref<Tile[]>([]) const tiles = ref<Tile[]>([])
const modalRef = useTemplateRef('modalRef')
defineExpose({
open: () => modalRef.value?.open(),
close: () => modalRef.value?.close()
})
const uniqueTags = computed(() => { const uniqueTags = computed(() => {
const allTags = tiles.value.flatMap((tile) => tile.tags || []) const allTags = tiles.value.flatMap((tile) => tile.tags || [])
@ -222,11 +228,11 @@ function closeGroup() {
} }
function selectTile(tile: string) { function selectTile(tile: string) {
mapEditorStore.setSelectedTile(tile) mapEditor.setSelectedTile(tile)
} }
function isActiveTile(tile: Tile): boolean { function isActiveTile(tile: Tile): boolean {
return mapEditorStore.selectedTile === tile.id return mapEditor.selectedTile.value === tile.id
} }
let subscription: any = null let subscription: any = null

View File

@ -2,80 +2,85 @@
<div class="flex justify-center p-5"> <div class="flex justify-center p-5">
<div class="toolbar fixed bottom-0 left-0 m-3 rounded flex bg-gray solid border-solid border-2 border-gray-500 text-gray-300 p-1.5 px-3 h-10"> <div class="toolbar fixed bottom-0 left-0 m-3 rounded flex bg-gray solid border-solid border-2 border-gray-500 text-gray-300 p-1.5 px-3 h-10">
<div ref="toolbar" class="tools flex gap-2.5" v-if="mapEditor.currentMap.value"> <div ref="toolbar" class="tools flex gap-2.5" v-if="mapEditor.currentMap.value">
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': mapEditorStore.tool === 'move' }" @click="handleClick('move')"> <button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': mapEditor.tool.value === 'move' }" @click="handleClick('move')">
<img class="invert w-5 h-5" src="/assets/icons/mapEditor/move.svg" alt="Move camera" /> <span class="h-5" :class="{ 'ml-2.5': mapEditorStore.tool !== 'move' }">(M)</span> <img class="invert w-5 h-5" src="/assets/icons/mapEditor/move.svg" alt="Move camera" /> <span class="h-5" :class="{ 'ml-2.5': mapEditor.tool.value !== 'move' }">(M)</span>
</button> </button>
<div class="w-px bg-cyan"></div> <div class="w-px bg-cyan"></div>
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': mapEditorStore.tool === 'pencil' }" @click="handleClick('pencil')"> <button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': mapEditor.tool.value === 'pencil' }" @click="handleClick('pencil')">
<img class="invert w-5 h-5" src="/assets/icons/mapEditor/pencil.svg" alt="Pencil" /> <span class="h-5" :class="{ 'ml-2.5': mapEditorStore.tool !== 'pencil' }">(P)</span> <img class="invert w-5 h-5" src="/assets/icons/mapEditor/pencil.svg" alt="Pencil" /> <span class="h-5" :class="{ 'ml-2.5': mapEditor.tool.value !== 'pencil' }">(P)</span>
<div class="select" v-if="mapEditorStore.tool === 'pencil'"> <div class="select" v-if="mapEditor.tool.value === 'pencil'">
<div class="select-trigger group capitalize flex gap-3.5" :class="{ open: selectPencilOpen }"> <div class="select-trigger group capitalize flex gap-3.5" :class="{ open: selectPencilOpen }">
{{ mapEditorStore.drawMode.replace('_', ' ') }} {{ mapEditor.drawMode.value.replace('_', ' ') }}
<img class="group-[.open]:rotate-180 invert w-5 h-5 rotate-0 transition ease-in-out duration-200" src="/assets/icons/mapEditor/chevron.svg" alt="" /> <img class="group-[.open]:rotate-180 invert w-5 h-5 rotate-0 transition ease-in-out duration-200" src="/assets/icons/mapEditor/chevron.svg" alt="" />
</div> </div>
<div class="flex flex-col absolute bottom-full mb-5 left-1/2 -translate-x-1/2 bg-gray rounded min-w-28 border border-gray-500 border-solid text-left" v-show="selectPencilOpen && mapEditorStore.tool === 'pencil'"> <div class="flex flex-col absolute bottom-full mb-5 left-1/2 -translate-x-1/2 bg-gray rounded min-w-28 border border-gray-500 border-solid text-left" v-show="selectPencilOpen && mapEditor.tool.value === 'pencil'">
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setDrawMode('tile')"> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('tile', 'pencil')">
Tile Tile
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div> <div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
</span> </span>
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setDrawMode('map_object')"> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('map_object', 'pencil')">
Map object Map object
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div> <div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
</span> </span>
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setDrawMode('teleport')"> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('teleport', 'pencil')">
Teleport Teleport
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div> <div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
</span> </span>
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setDrawMode('blocking tile')">Blocking tile</span> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('blocking tile', 'pencil')">Blocking tile</span>
</div> </div>
</div> </div>
</button> </button>
<div class="w-px bg-cyan"></div> <div class="w-px bg-cyan"></div>
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': mapEditorStore.tool === 'eraser' }" @click="handleClick('eraser')"> <button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': mapEditor.tool.value === 'eraser' }" @click="handleClick('eraser')">
<img class="invert w-5 h-5" src="/assets/icons/mapEditor/eraser.svg" alt="Eraser" /> <span class="h-5" :class="{ 'ml-2.5': mapEditorStore.tool !== 'eraser' }">(E)</span> <img class="invert w-5 h-5" src="/assets/icons/mapEditor/eraser.svg" alt="Eraser" /> <span class="h-5" :class="{ 'ml-2.5': mapEditor.tool.value !== 'eraser' }">(E)</span>
<div class="select" v-if="mapEditorStore.tool === 'eraser'"> <div class="select" v-if="mapEditor.tool.value === 'eraser'">
<div class="select-trigger group capitalize flex gap-3.5" :class="{ open: selectEraserOpen }"> <div class="select-trigger group capitalize flex gap-3.5" :class="{ open: selectEraserOpen }">
{{ mapEditorStore.eraserMode.replace('_', ' ') }} {{ mapEditor.drawMode.value.replace('_', ' ') }}
<img class="group-[.open]:rotate-180 invert w-5 h-5 rotate-0 transition ease-in-out duration-200" src="/assets/icons/mapEditor/chevron.svg" /> <img class="group-[.open]:rotate-180 invert w-5 h-5 rotate-0 transition ease-in-out duration-200" src="/assets/icons/mapEditor/chevron.svg" />
</div> </div>
<div class="flex flex-col absolute bottom-full mb-5 left-1/2 -translate-x-1/2 bg-gray rounded min-w-28 border border-gray-500 border-solid text-left" v-show="selectEraserOpen"> <div class="flex flex-col absolute bottom-full mb-5 left-1/2 -translate-x-1/2 bg-gray rounded min-w-28 border border-gray-500 border-solid text-left" v-show="selectEraserOpen">
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setEraserMode('tile')"> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('tile', 'eraser')">
Tile Tile
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div> <div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
</span> </span>
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setEraserMode('map_object')"> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('map_object', 'eraser')">
Map object Map object
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div> <div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
</span> </span>
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setEraserMode('teleport')"> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('teleport', 'eraser')">
Teleport Teleport
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div> <div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
</span> </span>
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setEraserMode('blocking tile')">Blocking tile</span> <span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="() => handleModeClick('blocking tile', 'eraser')">Blocking tile</span>
</div> </div>
</div> </div>
</button> </button>
<div class="w-px bg-cyan"></div> <div class="w-px bg-cyan"></div>
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': mapEditorStore.tool === 'paint' }" @click="handleClick('paint')"> <button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': mapEditor.tool.value === 'paint' }" @click="handleClick('paint')">
<img class="invert w-5 h-5" src="/assets/icons/mapEditor/paint.svg" alt="Paint bucket" /> <span class="h-5" :class="{ 'ml-2.5': mapEditorStore.tool !== 'paint' }">(B)</span> <img class="invert w-5 h-5" src="/assets/icons/mapEditor/paint.svg" alt="Paint bucket" /> <span class="h-5" :class="{ 'ml-2.5': mapEditor.tool.value !== 'paint' }">(B)</span>
</button> </button>
<div class="w-px bg-cyan"></div> <div class="w-px bg-cyan"></div>
<button class="flex justify-center items-center min-w-10 p-0 relative" @click="handleClick('settings')"><img class="invert w-5 h-5" src="/assets/icons/mapEditor/gear.svg" alt="Map settings" /> <span class="h-5 ml-2.5">(Z)</span></button> <button class="flex justify-center items-center min-w-10 p-0 relative" @click="handleClick('settings')"><img class="invert w-5 h-5" src="/assets/icons/mapEditor/gear.svg" alt="Map settings" /> <span class="h-5 ml-2.5">(Z)</span></button>
<div class="w-px bg-cyan"></div>
<label class="my-auto gap-0" for="checkbox">Continuous Drawing</label>
<input type="checkbox" />
</div> </div>
<div class="toolbar fixed bottom-0 right-0 m-3 rounded flex bg-gray solid border-solid border-2 border-gray-500 text-gray-300 p-1.5 px-3 h-10 space-x-2"> <div class="toolbar fixed bottom-0 right-0 m-3 rounded flex bg-gray solid border-solid border-2 border-gray-500 text-gray-300 p-1.5 px-3 h-10 space-x-2">
<button class="btn-cyan px-3.5" @click="() => mapEditorStore.toggleMapListModal()">Load</button> <button class="btn-cyan px-3.5" @click="() => emit('open-maps')">Load</button>
<button class="btn-cyan px-3.5" @click="() => emit('save')" v-if="mapEditor.currentMap.value">Save</button> <button class="btn-cyan px-3.5" @click="() => emit('save')" v-if="mapEditor.currentMap.value">Save</button>
<button class="btn-cyan px-3.5" @click="() => emit('clear')" v-if="mapEditor.currentMap.value">Clear</button> <button class="btn-cyan px-3.5" @click="() => emit('clear')" v-if="mapEditor.currentMap.value">Clear</button>
<button class="btn-cyan px-3.5" @click="() => mapEditorStore.toggleActive()">Exit</button> <button class="btn-cyan px-3.5" @click="() => emit('close-editor')">Exit</button>
</div> </div>
</div> </div>
</div> </div>
@ -83,14 +88,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { useMapEditorComposable } from '@/composables/useMapEditorComposable' import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { useMapEditorStore } from '@/stores/mapEditorStore'
import { onClickOutside } from '@vueuse/core' import { onClickOutside } from '@vueuse/core'
import { onBeforeUnmount, onMounted, ref } from 'vue' import { onBeforeUnmount, onMounted, ref } from 'vue'
const mapEditor = useMapEditorComposable() const mapEditor = useMapEditorComposable()
const mapEditorStore = useMapEditorStore()
const emit = defineEmits(['save', 'clear']) const emit = defineEmits(['save', 'clear', 'open-maps', 'open-settings', 'close-editor', 'open-tile-list', 'open-map-object-list', 'close-lists'])
// track when clicked outside of toolbar items // track when clicked outside of toolbar items
const toolbar = ref(null) const toolbar = ref(null)
@ -99,26 +102,45 @@ const toolbar = ref(null)
let selectPencilOpen = ref(false) let selectPencilOpen = ref(false)
let selectEraserOpen = ref(false) let selectEraserOpen = ref(false)
let tileListShown = ref(false)
let mapObjectListShown = ref(false)
defineExpose({ tileListShown, mapObjectListShown })
// drawMode // drawMode
function setDrawMode(value: string) { function setDrawMode(value: string) {
mapEditorStore.isTileListModalShown = value === 'tile' if (mapEditor.tool.value === 'paint' || mapEditor.tool.value === 'pencil') {
mapEditorStore.isMapObjectListModalShown = value === 'map_object' emit('close-lists')
if (value === 'tile') emit('open-tile-list')
if (value === 'map_object') emit('open-map-object-list')
}
mapEditorStore.setDrawMode(value) mapEditor.setDrawMode(value)
selectPencilOpen.value = false
selectEraserOpen.value = false
}
function setPencilMode() {
mapEditor.setTool('pencil')
selectPencilOpen.value = false selectPencilOpen.value = false
} }
// drawMode // drawMode
function setEraserMode(value: string) { function setEraserMode() {
mapEditorStore.setEraserMode(value) mapEditor.setTool('eraser')
selectEraserOpen.value = false selectEraserOpen.value = false
} }
function handleModeClick(mode: string, type: 'pencil' | 'eraser') {
setDrawMode(mode)
type === 'pencil' ? setPencilMode() : setEraserMode()
}
function handleClick(tool: string) { function handleClick(tool: string) {
if (tool === 'settings') { if (tool === 'settings') {
mapEditorStore.toggleSettingsModal() emit('open-settings')
} else { } else {
mapEditorStore.setTool(tool) mapEditor.setTool(tool)
} }
selectPencilOpen.value = tool === 'pencil' ? !selectPencilOpen.value : false selectPencilOpen.value = tool === 'pencil' ? !selectPencilOpen.value : false
@ -126,22 +148,17 @@ function handleClick(tool: string) {
} }
function cycleToolMode(tool: 'pencil' | 'eraser') { function cycleToolMode(tool: 'pencil' | 'eraser') {
const modes = ['tile', 'object', 'teleport', 'blocking tile'] const modes = ['tile', 'map_object', 'teleport', 'blocking tile']
const currentMode = tool === 'pencil' ? mapEditorStore.drawMode : mapEditorStore.eraserMode const currentIndex = modes.indexOf(mapEditor.drawMode.value)
const currentIndex = modes.indexOf(currentMode)
const nextIndex = (currentIndex + 1) % modes.length const nextIndex = (currentIndex + 1) % modes.length
const nextMode = modes[nextIndex] const nextMode = modes[nextIndex]
if (tool === 'pencil') { setDrawMode(nextMode)
setDrawMode(nextMode)
} else {
setEraserMode(nextMode)
}
} }
function initKeyShortcuts(event: KeyboardEvent) { function initKeyShortcuts(event: KeyboardEvent) {
// Check if map is set // Check if map is set
if (!mapEditorStore.map) return if (!mapEditor.currentMap.value) return
// prevent if focused on composables // prevent if focused on composables
if (document.activeElement?.tagName === 'INPUT') return if (document.activeElement?.tagName === 'INPUT') return
@ -156,7 +173,7 @@ function initKeyShortcuts(event: KeyboardEvent) {
if (keyActions.hasOwnProperty(event.key)) { if (keyActions.hasOwnProperty(event.key)) {
const tool = keyActions[event.key] const tool = keyActions[event.key]
if ((tool === 'pencil' || tool === 'eraser') && mapEditorStore.tool === tool) { if ((tool === 'pencil' || tool === 'eraser') && mapEditor.tool.value === tool) {
cycleToolMode(tool) cycleToolMode(tool)
} else { } else {
handleClick(tool) handleClick(tool)

View File

@ -34,7 +34,7 @@
<button class="ml-6 w-4 h-8 p-0"> <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" /> <img src="/assets/icons/triangle-icon.svg" class="w-3 h-3.5 m-auto" alt="Arrow left" />
</button> </button>
<img class="w-24 object-contain mb-3.5" 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/' + characters.find((c) => c.id === selectedCharacterId)?.characterType + '/' + (selectedHairId ?? 'default')" />
<button class="mr-6 w-4 h-8 p-0"> <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" /> <img src="/assets/icons/triangle-icon.svg" class="w-3 h-3.5 -scale-x-100" alt="Arrow right" />
</button> </button>
@ -87,7 +87,7 @@
</div> </div>
</div> </div>
<div v-else> <div v-else>
<img class="w-20 invert-80" src="/assets/icons/loading-icon1.svg" /> <img class="w-20 invert-80" src="/assets/icons/loading-icon1.svg" alt="Loading" />
</div> </div>
<div class="w-2/3 button-wrapper flex self-center justify-center lg:justify-end gap-4 max-w-[860px]" v-if="!isLoading"> <div class="w-2/3 button-wrapper flex self-center justify-center lg:justify-end gap-4 max-w-[860px]" v-if="!isLoading">
@ -131,11 +131,11 @@ import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
const gameStore = useGameStore() const gameStore = useGameStore()
const isLoading = ref<boolean>(true) const isLoading = ref<boolean>(true)
const characters = ref<CharacterT[]>([]) const characters = ref<CharacterT[]>([])
const selectedCharacterId = ref<number | null>(null) const selectedCharacterId = ref<string | null>(null)
const isCreateNewCharacterModalOpen = ref<boolean>(false) const isCreateNewCharacterModalOpen = ref<boolean>(false)
const newCharacterName = ref<string>('') const newCharacterName = ref<string>('')
const characterHairs = ref<CharacterHair[]>([]) const characterHairs = ref<CharacterHair[]>([])
const selectedHairId = ref<number | null>(null) const selectedHairId = ref<string | null>(null)
// Fetch characters // Fetch characters
setTimeout(() => { setTimeout(() => {

View File

@ -11,7 +11,6 @@
<ExpBar /> <ExpBar />
<CharacterProfile /> <CharacterProfile />
<Effects />
</Scene> </Scene>
</Game> </Game>
</div> </div>
@ -27,7 +26,6 @@ import ExpBar from '@/components/game/gui/ExpBar.vue'
import Hotkeys from '@/components/game/gui/Hotkeys.vue' import Hotkeys from '@/components/game/gui/Hotkeys.vue'
import Hud from '@/components/game/gui/Hud.vue' import Hud from '@/components/game/gui/Hud.vue'
import Menu from '@/components/game/gui/Menu.vue' import Menu from '@/components/game/gui/Menu.vue'
import Effects from '@/components/game/map/Effects.vue'
import Map from '@/components/game/map/Map.vue' import Map from '@/components/game/map/Map.vue'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { Game, Scene } from 'phavuer' import { Game, Scene } from 'phavuer'

View File

@ -1,16 +1,6 @@
<template> <template>
<div class="flex flex-col justify-center items-center h-dvh relative col"> <div class="flex flex-col justify-center items-center h-dvh relative col">
<svg width="40" height="40" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <img class="w-20 invert-80" src="/assets/icons/loading-icon1.svg" alt="Loading" />
<circle cx="4" cy="12" r="3" fill="white">
<animate id="spinner_qFRN" begin="0;spinner_OcgL.end+0.25s" attributeName="cy" calcMode="spline" dur="0.6s" values="12;6;12" keySplines=".33,.66,.66,1;.33,0,.66,.33" />
</circle>
<circle cx="12" cy="12" r="3" fill="white">
<animate begin="spinner_qFRN.begin+0.1s" attributeName="cy" calcMode="spline" dur="0.6s" values="12;6;12" keySplines=".33,.66,.66,1;.33,0,.66,.33" />
</circle>
<circle cx="20" cy="12" r="3" fill="white">
<animate id="spinner_OcgL" begin="spinner_qFRN.begin+0.2s" attributeName="cy" calcMode="spline" dur="0.6s" values="12;6;12" keySplines=".33,.66,.66,1;.33,0,.66,.33" />
</circle>
</svg>
</div> </div>
</template> </template>

View File

@ -5,12 +5,23 @@
<div v-if="!isLoaded" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-white text-3xl font-ui">Loading...</div> <div v-if="!isLoaded" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-white text-3xl font-ui">Loading...</div>
<div v-else> <div v-else>
<Map :key="mapEditor.currentMap.value?.id" /> <Map :key="mapEditor.currentMap.value?.id" />
<Toolbar @save="save" @clear="clear" /> <Toolbar
<MapList /> ref="toolbar"
<TileList /> @save="save"
<ObjectList /> @clear="clear"
<MapSettings /> @open-maps="mapModal?.open"
<TeleportModal /> @open-settings="mapSettingsModal?.open"
@close-editor="mapEditor.toggleActive"
@close-lists="tileModal?.close"
@closeLists="objectModal?.close"
@open-tile-list="tileModal?.open"
@open-map-object-list="objectModal?.open"
/>
<MapList ref="mapModal" @open-create-map="mapSettingsModal?.open" />
<TileList ref="tileModal" />
<ObjectList ref="objectModal" />
<MapSettings ref="mapSettingsModal" />
<TeleportModal ref="teleportModal" />
</div> </div>
</Scene> </Scene>
</Game> </Game>
@ -32,14 +43,19 @@ import { loadAllTilesIntoScene } from '@/composables/mapComposable'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable' import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { MapStorage } from '@/storage/storages' import { MapStorage } from '@/storage/storages'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useMapEditorStore } from '@/stores/mapEditorStore'
import { Game, Scene } from 'phavuer' import { Game, Scene } from 'phavuer'
import { ref, watch } from 'vue' import { ref, useTemplateRef, watch } from 'vue'
const mapStorage = new MapStorage() const mapStorage = new MapStorage()
const mapEditor = useMapEditorComposable() const mapEditor = useMapEditorComposable()
const gameStore = useGameStore() const gameStore = useGameStore()
const mapEditorStore = useMapEditorStore()
const toolbar = useTemplateRef('toolbar')
const mapModal = useTemplateRef('mapModal')
const tileModal = useTemplateRef('tileModal')
const objectModal = useTemplateRef('objectModal')
const mapSettingsModal = useTemplateRef('mapSettingsModal')
const teleportModal = useTemplateRef('teleportModal')
const isLoaded = ref(false) const isLoaded = ref(false)
@ -53,7 +69,7 @@ const gameConfig = {
const createGame = (game: Phaser.Game) => { const createGame = (game: Phaser.Game) => {
// Resize the game when the window is resized // Resize the game when the window is resized
addEventListener('resize', () => { window.addEventListener('resize', () => {
game.scale.resize(window.innerWidth, window.innerHeight) game.scale.resize(window.innerWidth, window.innerHeight)
}) })
} }
@ -78,22 +94,19 @@ const preloadScene = async (scene: Phaser.Scene) => {
} }
function save() { function save() {
if (!mapEditor.currentMap.value) return const currentMap = mapEditor.currentMap.value
if (!currentMap) return
const data = { const data = {
mapId: mapEditor.currentMap.value.id, mapId: currentMap.id,
name: mapEditor.currentMap.value.name, name: currentMap.name,
width: mapEditor.currentMap.value.width, width: currentMap.width,
height: mapEditor.currentMap.value.height, height: currentMap.height,
tiles: mapEditor.currentMap.value.tiles, tiles: currentMap.tiles,
pvp: mapEditor.currentMap.value.pvp, pvp: currentMap.pvp,
mapEffects: mapEditor.currentMap.value.mapEffects?.map(({ id, effect, strength }) => ({ id, effect, strength })) ?? [], mapEffects: currentMap.mapEffects?.map(({ id, effect, strength }) => ({ id, effect, strength })) ?? [],
mapEventTiles: mapEditor.currentMap.value.mapEventTiles?.map(({ id, type, positionX, positionY, teleport }) => ({ id, type, positionX, positionY, teleport })) ?? [], mapEventTiles: currentMap.mapEventTiles?.map(({ id, type, positionX, positionY, teleport }) => ({ id, type, positionX, positionY, teleport })) ?? [],
placedMapObjects: mapEditor.currentMap.value.placedMapObjects?.map(({ id, mapObject, depth, isRotated, positionX, positionY }) => ({ id, mapObject, depth, isRotated, positionX, positionY })) ?? [] placedMapObjects: currentMap.placedMapObjects?.map(({ id, mapObject, depth, isRotated, positionX, positionY }) => ({ id, mapObject, depth, isRotated, positionX, positionY })) ?? []
}
if (mapEditorStore.isSettingsModalShown) {
mapEditorStore.toggleSettingsModal()
} }
gameStore.connection?.emit('gm:map:update', data, (response: MapT) => { gameStore.connection?.emit('gm:map:update', data, (response: MapT) => {
@ -106,6 +119,5 @@ function clear() {
// Clear placed objects, event tiles and tiles // Clear placed objects, event tiles and tiles
mapEditor.clearMap() mapEditor.clearMap()
mapEditorStore.triggerClearTiles()
} }
</script> </script>

View File

@ -17,7 +17,7 @@
<button v-if="canFullScreen" @click="toggleFullScreen" class="w-5 h-5 m-0 p-0 relative hover:scale-110 transition-transform duration-300 ease-in-out"> <button v-if="canFullScreen" @click="toggleFullScreen" class="w-5 h-5 m-0 p-0 relative hover:scale-110 transition-transform duration-300 ease-in-out">
<img :alt="isFullScreen ? 'exit full-screen' : 'full-screen'" :src="isFullScreen ? '/assets/icons/modal/minimize.svg' : '/assets/icons/modal/increase-size-option.svg'" class="w-3.5 h-3.5 invert" draggable="false" /> <img :alt="isFullScreen ? 'exit full-screen' : 'full-screen'" :src="isFullScreen ? '/assets/icons/modal/minimize.svg' : '/assets/icons/modal/increase-size-option.svg'" class="w-3.5 h-3.5 invert" draggable="false" />
</button> </button>
<button v-if="closable" @click="emit('modal:close')" class="w-3.5 h-3.5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out"> <button v-if="closable" @click="closeModal" class="w-3.5 h-3.5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out">
<img alt="close" src="/assets/icons/modal/close-button-white.svg" class="w-full h-full" draggable="false" /> <img alt="close" src="/assets/icons/modal/close-button-white.svg" class="w-full h-full" draggable="false" />
</button> </button>
</div> </div>
@ -46,7 +46,7 @@
import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
interface ModalProps { interface ModalProps {
isModalOpen: boolean isModalOpen?: boolean
closable?: boolean closable?: boolean
isResizable?: boolean isResizable?: boolean
isFullScreen?: boolean isFullScreen?: boolean
@ -79,10 +79,16 @@ const props = withDefaults(defineProps<ModalProps>(), {
}) })
const emit = defineEmits<{ const emit = defineEmits<{
'modal:open': []
'modal:close': [] 'modal:close': []
'character:create': []
}>() }>()
defineExpose({
open: () => (isModalOpenRef.value = true),
close: () => (isModalOpenRef.value = false),
toggle: () => (isModalOpenRef.value = !isModalOpenRef.value)
})
const isModalOpenRef = ref(props.isModalOpen) const isModalOpenRef = ref(props.isModalOpen)
const width = ref(props.modalWidth) const width = ref(props.modalWidth)
const height = ref(props.modalHeight) const height = ref(props.modalHeight)
@ -150,6 +156,11 @@ function drag(event: MouseEvent) {
y.value = dragState.initialY + (event.clientY - dragState.startY) y.value = dragState.initialY + (event.clientY - dragState.startY)
} }
function closeModal() {
isModalOpenRef.value = false
emit('modal:close')
}
function toggleFullScreen() { function toggleFullScreen() {
if (isFullScreen.value) { if (isFullScreen.value) {
Object.assign({ x, y, width, height }, preFullScreenState) Object.assign({ x, y, width, height }, preFullScreenState)

View File

@ -59,7 +59,6 @@ export async function loadTexture(scene: Phaser.Scene, textureData: TextureData)
export async function loadSpriteTextures(scene: Phaser.Scene, sprite_id: string) { export async function loadSpriteTextures(scene: Phaser.Scene, sprite_id: string) {
if (!sprite_id) return false if (!sprite_id) return false
// @TODO: Fix this
const spriteStorage = new SpriteStorage() const spriteStorage = new SpriteStorage()
const sprite = await spriteStorage.get(sprite_id) const sprite = await spriteStorage.get(sprite_id)
@ -73,18 +72,17 @@ export async function loadSpriteTextures(scene: Phaser.Scene, sprite_id: string)
await loadTexture(scene, { await loadTexture(scene, {
key, key,
data: '/textures/sprites/' + sprite.id + '/' + sprite_action.action + '.png', data: '/textures/sprites/' + sprite.id + '/' + sprite_action.action + '.png',
group: sprite_action.isAnimated ? 'sprite_animations' : 'sprites', group: sprite_action.frameCount > 1 ? 'sprite_animations' : 'sprites',
updatedAt: sprite_action.updatedAt, updatedAt: sprite_action.updatedAt,
originX: sprite_action.originX, originX: sprite_action.originX,
originY: sprite_action.originY, originY: sprite_action.originY,
isAnimated: sprite_action.isAnimated,
frameWidth: sprite_action.frameWidth, frameWidth: sprite_action.frameWidth,
frameHeight: sprite_action.frameHeight, frameHeight: sprite_action.frameHeight,
frameRate: sprite_action.frameRate frameRate: sprite_action.frameRate
} as TextureData) } as TextureData)
// If the sprite is not animated, skip // If the sprite has no more than one frame, skip
if (!sprite_action.isAnimated) continue if (sprite_action.frameCount <= 1) continue
// Check if animation already exists // Check if animation already exists
if (scene.anims.get(key)) continue if (scene.anims.get(key)) continue

View File

@ -1,5 +1,5 @@
import config from '@/application/config' import config from '@/application/config'
import type { HttpResponse, TextureData, UUID } from '@/application/types' import type { HttpResponse, TextureData, Tile as TileT, UUID } from '@/application/types'
import { unduplicateArray } from '@/application/utilities' import { unduplicateArray } from '@/application/utilities'
import { loadTexture } from '@/composables/gameComposable' import { loadTexture } from '@/composables/gameComposable'
import { MapStorage, TileStorage } from '@/storage/storages' import { MapStorage, TileStorage } from '@/storage/storages'
@ -10,9 +10,7 @@ import Tileset = Phaser.Tilemaps.Tileset
import Tile = Phaser.Tilemaps.Tile import Tile = Phaser.Tilemaps.Tile
export function getTile(layer: TilemapLayer | Tilemap, positionX: number, positionY: number): Tile | null { export function getTile(layer: TilemapLayer | Tilemap, positionX: number, positionY: number): Tile | null {
const tile = layer?.getTileAtWorldXY(positionX, positionY) return layer.getTileAtWorldXY(positionX, positionY)
if (!tile) return null
return tile
} }
export function tileToWorldXY(layer: TilemapLayer | Tilemap, positionX: number, positionY: number) { export function tileToWorldXY(layer: TilemapLayer | Tilemap, positionX: number, positionY: number) {
@ -77,14 +75,18 @@ export const calculateIsometricDepth = (positionX: number, positionY: number, wi
return baseDepth + (width + height) / (2 * config.tile_size.width) return baseDepth + (width + height) / (2 * config.tile_size.width)
} }
export function FlattenMapArray(tiles: string[][]) { async function getTiles(tiles: TileT[], scene: Phaser.Scene) {
const normalArray = [] // Load each tile into the scene
for (const tile of tiles) {
for (const row of tiles) { if (!tile) continue
normalArray.push(...row) const textureData = {
key: tile.id,
data: '/textures/tiles/' + tile.id + '.png',
group: 'tiles',
updatedAt: tile.updatedAt
} as TextureData
await loadTexture(scene, textureData)
} }
return normalArray
} }
export async function loadMapTilesIntoScene(map_id: UUID, scene: Phaser.Scene) { export async function loadMapTilesIntoScene(map_id: UUID, scene: Phaser.Scene) {
@ -93,50 +95,22 @@ export async function loadMapTilesIntoScene(map_id: UUID, scene: Phaser.Scene) {
const map = await mapStorage.get(map_id) const map = await mapStorage.get(map_id)
if (!map) return if (!map) return
const tileArray = unduplicateArray(FlattenMapArray(map.tiles)) const tileArray = unduplicateArray(map.tiles)
const tiles = await tileStorage.getByIds(tileArray) const tiles = await tileStorage.getByIds(tileArray)
// Load each tile into the scene await getTiles(tiles, scene)
for (const tile of tiles) {
const textureData = {
key: tile.id,
data: '/textures/tiles/' + tile.id + '.png',
group: 'tiles',
updatedAt: tile.updatedAt
} as TextureData
await loadTexture(scene, textureData)
}
} }
export async function loadTilesIntoScene(tileIds: string[], scene: Phaser.Scene) { export async function loadTilesIntoScene(tileIds: string[], scene: Phaser.Scene) {
const tileStorage = new TileStorage() const tileStorage = new TileStorage()
const tiles = await tileStorage.getByIds(tileIds) const tiles = await tileStorage.getByIds(tileIds)
// Load each tile into the scene await getTiles(tiles, scene)
for (const tile of tiles) {
const textureData = {
key: tile.id,
data: '/textures/tiles/' + tile.id + '.png',
group: 'tiles',
updatedAt: tile.updatedAt
} as TextureData
await loadTexture(scene, textureData)
}
} }
export async function loadAllTilesIntoScene(scene: Phaser.Scene) { export async function loadAllTilesIntoScene(scene: Phaser.Scene) {
const tileStorage = new TileStorage() const tileStorage = new TileStorage()
const tiles = await tileStorage.getAll() const tiles = await tileStorage.getAll()
// Load each tile into the scene await getTiles(tiles, scene)
for (const tile of tiles) {
const textureData = {
key: tile.id,
data: '/textures/tiles/' + tile.id + '.png',
group: 'tiles',
updatedAt: tile.updatedAt
} as TextureData
await loadTexture(scene, textureData)
}
} }

View File

@ -25,7 +25,7 @@ export function useGamePointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilema
} }
function handlePointerDown(pointer: Phaser.Input.Pointer) { function handlePointerDown(pointer: Phaser.Input.Pointer) {
pointerStartPosition.value = { x: pointer.x, y: pointer.y } pointerStartPosition.value = pointer.position
gameStore.setPlayerDraggingCamera(true) gameStore.setPlayerDraggingCamera(true)
} }
@ -34,10 +34,9 @@ export function useGamePointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilema
if (!gameStore.game.isPlayerDraggingCamera) return if (!gameStore.game.isPlayerDraggingCamera) return
const distance = Phaser.Math.Distance.Between(pointerStartPosition.value.x, pointerStartPosition.value.y, pointer.x, pointer.y)
// If the distance is less than the drag threshold, return // If the distance is less than the drag threshold, return
// We do this to prevent the camera from scrolling too quickly // We do this to prevent the camera from scrolling too quickly
const distance = Phaser.Math.Distance.Between(pointerStartPosition.value.x, pointerStartPosition.value.y, pointer.x, pointer.y)
if (distance <= dragThreshold) return if (distance <= dragThreshold) return
camera.setScroll(camera.scrollX - (pointer.x - pointer.prevPosition.x) / camera.zoom, camera.scrollY - (pointer.y - pointer.prevPosition.y) / camera.zoom) camera.setScroll(camera.scrollX - (pointer.x - pointer.prevPosition.x) / camera.zoom, camera.scrollY - (pointer.y - pointer.prevPosition.y) / camera.zoom)
@ -46,12 +45,6 @@ export function useGamePointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilema
function handlePointerUp(pointer: Phaser.Input.Pointer) { function handlePointerUp(pointer: Phaser.Input.Pointer) {
gameStore.setPlayerDraggingCamera(false) gameStore.setPlayerDraggingCamera(false)
const distance = Phaser.Math.Distance.Between(pointerStartPosition.value.x, pointerStartPosition.value.y, pointer.x, pointer.y)
// If the distance is greater than the drag threshold, return
// We do this to prevent the camera from scrolling too quickly
if (distance > dragThreshold) return
const pointerTile = getTile(layer, pointer.worldX, pointer.worldY) const pointerTile = getTile(layer, pointer.worldX, pointer.worldY)
if (!pointerTile) return if (!pointerTile) return

View File

@ -1,13 +1,13 @@
import config from '@/application/config' import config from '@/application/config'
import { getTile, tileToWorldXY } from '@/composables/mapComposable' import { getTile, tileToWorldXY } from '@/composables/mapComposable'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useMapEditorStore } from '@/stores/mapEditorStore'
import { computed, ref, type Ref } from 'vue' import { computed, ref, type Ref } from 'vue'
export function useMapEditorPointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) { export function useMapEditorPointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
const gameStore = useGameStore() const gameStore = useGameStore()
const mapEditorStore = useMapEditorStore() const mapEditor = useMapEditorComposable()
const isMoveTool = computed(() => mapEditorStore.tool === 'move') const isMoveTool = computed(() => mapEditor.tool.value === 'move')
const pointerStartPosition = ref({ x: 0, y: 0 }) const pointerStartPosition = ref({ x: 0, y: 0 })
const dragThreshold = 5 // pixels const dragThreshold = 5 // pixels
@ -39,6 +39,9 @@ export function useMapEditorPointerHandlers(scene: Phaser.Scene, layer: Phaser.T
const distance = Phaser.Math.Distance.Between(pointerStartPosition.value.x, pointerStartPosition.value.y, pointer.x, pointer.y) const distance = Phaser.Math.Distance.Between(pointerStartPosition.value.x, pointerStartPosition.value.y, pointer.x, pointer.y)
// If the distance is less than the drag threshold, return
// We do this to prevent the camera from scrolling too quickly
if (distance <= dragThreshold) return if (distance <= dragThreshold) return
camera.setScroll(camera.scrollX - (pointer.x - pointer.prevPosition.x) / camera.zoom, camera.scrollY - (pointer.y - pointer.prevPosition.y) / camera.zoom) camera.setScroll(camera.scrollX - (pointer.x - pointer.prevPosition.x) / camera.zoom, camera.scrollY - (pointer.y - pointer.prevPosition.y) / camera.zoom)

View File

@ -1,7 +1,26 @@
import type { Map } from '@/application/types' import type { Map, MapObject } from '@/application/types'
import { ref } from 'vue' import { ref } from 'vue'
export type TeleportSettings = {
toMapId: string
toPositionX: number
toPositionY: number
toRotation: number
}
const currentMap = ref<Map | null>(null) const currentMap = ref<Map | null>(null)
const active = ref(false)
const tool = ref('move')
const drawMode = ref('tile')
const selectedTile = ref('')
const selectedMapObject = ref<MapObject | null>(null)
const shouldClearTiles = ref(false)
const teleportSettings = ref<TeleportSettings>({
toMapId: '',
toPositionX: 0,
toPositionY: 0,
toRotation: 0
})
export function useMapEditorComposable() { export function useMapEditorComposable() {
const loadMap = (map: Map) => { const loadMap = (map: Map) => {
@ -16,16 +35,75 @@ export function useMapEditorComposable() {
const clearMap = () => { const clearMap = () => {
if (!currentMap.value) return if (!currentMap.value) return
currentMap.value.placedMapObjects = [] currentMap.value.placedMapObjects = []
currentMap.value.mapEventTiles = [] currentMap.value.mapEventTiles = []
currentMap.value.tiles = [] currentMap.value.tiles = []
} }
const toggleActive = () => {
if (active.value) reset()
active.value = !active.value
}
const setTool = (newTool: string) => {
tool.value = newTool
}
const setDrawMode = (mode: string) => {
drawMode.value = mode
}
const setSelectedTile = (tile: string) => {
selectedTile.value = tile
}
const setSelectedMapObject = (object: MapObject) => {
selectedMapObject.value = object
}
const setTeleportSettings = (settings: TeleportSettings) => {
teleportSettings.value = settings
}
const triggerClearTiles = () => {
shouldClearTiles.value = true
}
const resetClearTilesFlag = () => {
shouldClearTiles.value = false
}
const reset = () => {
tool.value = 'move'
drawMode.value = 'tile'
selectedTile.value = ''
selectedMapObject.value = null
shouldClearTiles.value = false
}
return { return {
// State
currentMap, currentMap,
active,
tool,
drawMode,
selectedTile,
selectedMapObject,
shouldClearTiles,
teleportSettings,
// Methods
loadMap, loadMap,
updateProperty, updateProperty,
clearMap clearMap,
toggleActive,
setTool,
setDrawMode,
setSelectedTile,
setSelectedMapObject,
setTeleportSettings,
triggerClearTiles,
resetClearTilesFlag,
reset
} }
} }

View File

@ -1,20 +1,20 @@
import { useMapEditorStore } from '@/stores/mapEditorStore' import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { computed, watch, type Ref } from 'vue' import { computed, watch, type Ref } from 'vue'
import { useGamePointerHandlers } from './pointerHandlers/useGamePointerHandlers' import { useGamePointerHandlers } from './pointerHandlers/useGamePointerHandlers'
import { useMapEditorPointerHandlers } from './pointerHandlers/useMapEditorPointerHandlers' import { useMapEditorPointerHandlers } from './pointerHandlers/useMapEditorPointerHandlers'
export function usePointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) { export function usePointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
const mapEditorStore = useMapEditorStore() const mapEditor = useMapEditorComposable()
const gameHandlers = useGamePointerHandlers(scene, layer, waypoint, camera) const gameHandlers = useGamePointerHandlers(scene, layer, waypoint, camera)
const mapEditorHandlers = useMapEditorPointerHandlers(scene, layer, waypoint, camera) const mapEditorHandlers = useMapEditorPointerHandlers(scene, layer, waypoint, camera)
const currentHandlers = computed(() => (mapEditorStore.active ? mapEditorHandlers : gameHandlers)) const currentHandlers = computed(() => (mapEditor.active.value ? mapEditorHandlers : gameHandlers))
const setupPointerHandlers = () => currentHandlers.value.setupPointerHandlers() const setupPointerHandlers = () => currentHandlers.value.setupPointerHandlers()
const cleanupPointerHandlers = () => currentHandlers.value.cleanupPointerHandlers() const cleanupPointerHandlers = () => currentHandlers.value.cleanupPointerHandlers()
watch( watch(
() => mapEditorStore.active, () => mapEditor.active.value,
() => { () => {
cleanupPointerHandlers() cleanupPointerHandlers()
setupPointerHandlers() setupPointerHandlers()

View File

@ -40,4 +40,9 @@ export class CharacterHairStorage extends BaseStorage<any> {
constructor() { constructor() {
super('characterHairs', 'id, name, createdAt, updatedAt') super('characterHairs', 'id, name, createdAt, updatedAt')
} }
async getSpriteId(characterTypeId: string) {
const characterType = await this.get(characterTypeId)
return characterType?.sprite
}
} }

View File

@ -32,7 +32,6 @@ export class TextureStorage {
updatedAt: texture.updatedAt, updatedAt: texture.updatedAt,
originX: texture.originX, originX: texture.originX,
originY: texture.originY, originY: texture.originY,
isAnimated: texture.isAnimated,
frameRate: texture.frameRate, frameRate: texture.frameRate,
frameWidth: texture.frameWidth, frameWidth: texture.frameWidth,
frameHeight: texture.frameHeight, frameHeight: texture.frameHeight,

View File

@ -1,4 +1,4 @@
import type { MapObject } from '@/application/types' import type { MapObject, Map as MapT } from '@/application/types'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
export type TeleportSettings = { export type TeleportSettings = {
@ -11,17 +11,11 @@ export type TeleportSettings = {
export const useMapEditorStore = defineStore('mapEditor', { export const useMapEditorStore = defineStore('mapEditor', {
state: () => { state: () => {
return { return {
active: false, active: true,
tool: 'move', tool: 'move',
drawMode: 'tile', drawMode: 'tile',
eraserMode: 'tile',
selectedTile: '', selectedTile: '',
selectedMapObject: null as MapObject | null, selectedMapObject: null as MapObject | null,
isTileListModalShown: false,
isMapObjectListModalShown: false,
isMapListModalShown: false,
isCreateMapModalShown: false,
isSettingsModalShown: false,
shouldClearTiles: false, shouldClearTiles: false,
teleportSettings: { teleportSettings: {
toMapId: '', toMapId: '',
@ -32,35 +26,18 @@ export const useMapEditorStore = defineStore('mapEditor', {
} }
}, },
actions: { actions: {
toggleActive() {
if (this.active) this.reset()
this.active = !this.active
},
setTool(tool: string) { setTool(tool: string) {
this.tool = tool this.tool = tool
}, },
setDrawMode(mode: string) { setDrawMode(mode: string) {
this.drawMode = mode this.drawMode = mode
}, },
setEraserMode(mode: string) {
this.eraserMode = mode
},
setSelectedTile(tile: string) { setSelectedTile(tile: string) {
this.selectedTile = tile this.selectedTile = tile
}, },
setSelectedMapObject(object: MapObject) { setSelectedMapObject(object: MapObject) {
this.selectedMapObject = object this.selectedMapObject = object
}, },
toggleSettingsModal() {
this.isSettingsModalShown = !this.isSettingsModalShown
},
toggleMapListModal() {
this.isMapListModalShown = !this.isMapListModalShown
this.isCreateMapModalShown = false
},
toggleCreateMapModal() {
this.isCreateMapModalShown = !this.isCreateMapModalShown
},
setTeleportSettings(teleportSettings: TeleportSettings) { setTeleportSettings(teleportSettings: TeleportSettings) {
this.teleportSettings = teleportSettings this.teleportSettings = teleportSettings
}, },
@ -75,11 +52,6 @@ export const useMapEditorStore = defineStore('mapEditor', {
this.drawMode = 'tile' this.drawMode = 'tile'
this.selectedTile = '' this.selectedTile = ''
this.selectedMapObject = null this.selectedMapObject = null
this.isTileListModalShown = false
this.isMapObjectListModalShown = false
this.isSettingsModalShown = false
this.isMapListModalShown = false
this.isCreateMapModalShown = false
this.shouldClearTiles = false this.shouldClearTiles = false
} }
} }