Compare commits
30 Commits
feature/13
...
feature/re
Author | SHA1 | Date | |
---|---|---|---|
aff32c33c7 | |||
774871510e | |||
e61b705031 | |||
3902c611fa | |||
3765cfe5e9 | |||
2fad54fd26 | |||
be3cbf77bf | |||
f24a498246 | |||
9686381745 | |||
e42c530685 | |||
e56e078042 | |||
13be1a38fa | |||
27e857b9a6 | |||
2b2c290db0 | |||
245b50c1fd | |||
32dc7a2963 | |||
a9c2b209d9 | |||
a6c22df528 | |||
7dd2d70eca | |||
5fc3547d9c | |||
b5c222cc05 | |||
8b1efca7b8 | |||
1bdd2bc75a | |||
24dff8d920 | |||
8b98fc5c4e | |||
934ae50d8e | |||
7504e3719e | |||
34bd103ec2 | |||
295ce98e33 | |||
1cfdf1857e |
@ -3,8 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
|
<meta name="viewport" content="width=device-width">
|
||||||
<title>New Quest - Play</title>
|
<title>Sylvan Quest - Play</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
206
package-lock.json
generated
@ -11,11 +11,11 @@
|
|||||||
"@vueuse/core": "^10.5.0",
|
"@vueuse/core": "^10.5.0",
|
||||||
"@vueuse/integrations": "^10.5.0",
|
"@vueuse/integrations": "^10.5.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"phaser": "^3.85.2",
|
"phaser": "^3.86.0",
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.6",
|
||||||
"socket.io-client": "^4.8.0",
|
"socket.io-client": "^4.8.0",
|
||||||
"universal-cookie": "^6.1.3",
|
"universal-cookie": "^6.1.3",
|
||||||
"vue": "^3.5.10",
|
"vue": "^3.5.12",
|
||||||
"zod": "^3.22.2"
|
"zod": "^3.22.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -41,8 +41,8 @@
|
|||||||
"sass": "^1.79.4",
|
"sass": "^1.79.4",
|
||||||
"tailwindcss": "^3.4.13",
|
"tailwindcss": "^3.4.13",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"vite": "^5.4.8",
|
"vite": "^5.4.9",
|
||||||
"vite-plugin-vue-devtools": "^7.4.6",
|
"vite-plugin-vue-devtools": "^7.5.2",
|
||||||
"vitest": "^2.0.3",
|
"vitest": "^2.0.3",
|
||||||
"vue-tsc": "^1.6.5"
|
"vue-tsc": "^1.6.5"
|
||||||
}
|
}
|
||||||
@ -1991,9 +1991,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.16.11",
|
"version": "20.16.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.12.tgz",
|
||||||
"integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==",
|
"integrity": "sha512-LfPFB0zOeCeCNQV3i+67rcoVvoN5n0NVuR2vLG0O5ySQMgchuZlC4lgz546ZOJyDtj5KIgOxy+lacOimfqZAIA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -2228,14 +2228,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/expect": {
|
"node_modules/@vitest/expect": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.3.tgz",
|
||||||
"integrity": "sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==",
|
"integrity": "sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/spy": "2.1.2",
|
"@vitest/spy": "2.1.3",
|
||||||
"@vitest/utils": "2.1.2",
|
"@vitest/utils": "2.1.3",
|
||||||
"chai": "^5.1.1",
|
"chai": "^5.1.1",
|
||||||
"tinyrainbow": "^1.2.0"
|
"tinyrainbow": "^1.2.0"
|
||||||
},
|
},
|
||||||
@ -2244,13 +2244,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/mocker": {
|
"node_modules/@vitest/mocker": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.3.tgz",
|
||||||
"integrity": "sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==",
|
"integrity": "sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/spy": "^2.1.0-beta.1",
|
"@vitest/spy": "2.1.3",
|
||||||
"estree-walker": "^3.0.3",
|
"estree-walker": "^3.0.3",
|
||||||
"magic-string": "^0.30.11"
|
"magic-string": "^0.30.11"
|
||||||
},
|
},
|
||||||
@ -2258,7 +2258,7 @@
|
|||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@vitest/spy": "2.1.2",
|
"@vitest/spy": "2.1.3",
|
||||||
"msw": "^2.3.5",
|
"msw": "^2.3.5",
|
||||||
"vite": "^5.0.0"
|
"vite": "^5.0.0"
|
||||||
},
|
},
|
||||||
@ -2282,9 +2282,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/pretty-format": {
|
"node_modules/@vitest/pretty-format": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.3.tgz",
|
||||||
"integrity": "sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==",
|
"integrity": "sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -2295,13 +2295,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/runner": {
|
"node_modules/@vitest/runner": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.3.tgz",
|
||||||
"integrity": "sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==",
|
"integrity": "sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/utils": "2.1.2",
|
"@vitest/utils": "2.1.3",
|
||||||
"pathe": "^1.1.2"
|
"pathe": "^1.1.2"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
@ -2309,13 +2309,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/snapshot": {
|
"node_modules/@vitest/snapshot": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.3.tgz",
|
||||||
"integrity": "sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==",
|
"integrity": "sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/pretty-format": "2.1.2",
|
"@vitest/pretty-format": "2.1.3",
|
||||||
"magic-string": "^0.30.11",
|
"magic-string": "^0.30.11",
|
||||||
"pathe": "^1.1.2"
|
"pathe": "^1.1.2"
|
||||||
},
|
},
|
||||||
@ -2324,9 +2324,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/spy": {
|
"node_modules/@vitest/spy": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.3.tgz",
|
||||||
"integrity": "sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==",
|
"integrity": "sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -2337,13 +2337,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/utils": {
|
"node_modules/@vitest/utils": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.3.tgz",
|
||||||
"integrity": "sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==",
|
"integrity": "sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/pretty-format": "2.1.2",
|
"@vitest/pretty-format": "2.1.3",
|
||||||
"loupe": "^3.1.1",
|
"loupe": "^3.1.1",
|
||||||
"tinyrainbow": "^1.2.0"
|
"tinyrainbow": "^1.2.0"
|
||||||
},
|
},
|
||||||
@ -2490,14 +2490,14 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@vue/devtools-core": {
|
"node_modules/@vue/devtools-core": {
|
||||||
"version": "7.4.6",
|
"version": "7.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.5.2.tgz",
|
||||||
"integrity": "sha512-7ATNPEbVqThOOAp2bg/YUIm9MqqgimbSk24D05hdXUp89JlXX12aTzdrWd9xZRwS78hDR+wCToHl1C/8sopBrg==",
|
"integrity": "sha512-J7vcCb2P7bH3TvikqSe3BquCZsgWC7PL0t9yO88c3LUK3cyhQdJoWcn0Tkgop55UztHWs40+7uQNDmTkcdNZAQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/devtools-kit": "^7.4.6",
|
"@vue/devtools-kit": "^7.5.2",
|
||||||
"@vue/devtools-shared": "^7.4.6",
|
"@vue/devtools-shared": "^7.5.2",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"nanoid": "^3.3.4",
|
"nanoid": "^3.3.4",
|
||||||
"pathe": "^1.1.2",
|
"pathe": "^1.1.2",
|
||||||
@ -2508,14 +2508,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/devtools-kit": {
|
"node_modules/@vue/devtools-kit": {
|
||||||
"version": "7.4.6",
|
"version": "7.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.5.2.tgz",
|
||||||
"integrity": "sha512-NbYBwPWgEic1AOd9bWExz9weBzFdjiIfov0yRn4DrRfR+EQJCI9dn4I0XS7IxYGdkmUJi8mFW42LLk18WsGqew==",
|
"integrity": "sha512-0leUOE2HBfl8sHf9ePKzxqnCFskkU22tWWqd9OfeSlslAKE30/TViYvWcF4vgQmPlJnAAdHU0WfW5dYlCeOiuw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/devtools-shared": "^7.4.6",
|
"@vue/devtools-shared": "^7.5.2",
|
||||||
"birpc": "^0.2.17",
|
"birpc": "^0.2.19",
|
||||||
"hookable": "^5.5.3",
|
"hookable": "^5.5.3",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"perfect-debounce": "^1.0.0",
|
"perfect-debounce": "^1.0.0",
|
||||||
@ -2524,9 +2524,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/devtools-shared": {
|
"node_modules/@vue/devtools-shared": {
|
||||||
"version": "7.4.6",
|
"version": "7.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.5.2.tgz",
|
||||||
"integrity": "sha512-rPeSBzElnHYMB05Cc056BQiJpgocQjY8XVulgni+O9a9Gr9tNXgPteSzFFD+fT/iWMxNuUgGKs9CuW5DZewfIg==",
|
"integrity": "sha512-+zmcixnD6TAo+zwm30YuwZckhL9iIi4u+gFwbq9C8zpm3SMndTlEYZtNhAHUhOXB+bCkzyunxw80KQ/T0trF4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -2858,9 +2858,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.12.1",
|
"version": "8.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz",
|
||||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
"integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -3186,9 +3186,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001668",
|
"version": "1.0.30001669",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz",
|
||||||
"integrity": "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==",
|
"integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -3642,9 +3642,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.36",
|
"version": "1.5.41",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.36.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz",
|
||||||
"integrity": "sha512-HYTX8tKge/VNp6FGO+f/uVDmUkq+cEfcxYhKf15Akc4M5yxt5YmorwlAitKWjWhWQnKcDRBAQKXkhqqXMqcrjw==",
|
"integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
@ -5764,9 +5764,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/phaser3-rex-plugins": {
|
"node_modules/phaser3-rex-plugins": {
|
||||||
"version": "1.80.8",
|
"version": "1.80.9",
|
||||||
"resolved": "https://registry.npmjs.org/phaser3-rex-plugins/-/phaser3-rex-plugins-1.80.8.tgz",
|
"resolved": "https://registry.npmjs.org/phaser3-rex-plugins/-/phaser3-rex-plugins-1.80.9.tgz",
|
||||||
"integrity": "sha512-/uZMauVrOC/uiywQF37yH5Q6eUMOL2knMCnfEDWnc5ek40jvQOKjsenjB6q6hYuMvV54C8m0q2p1oL6B/rz12g==",
|
"integrity": "sha512-yx+WSAf4MOF2AimVL/Dv7eN65/YuO4LVNlYihDP0tAgFfCoTBunAd0YDbv82eoSkOAd0gy6w3Qh71p3kq1eRTA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -5798,9 +5798,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
@ -6409,9 +6409,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/sass": {
|
"node_modules/sass": {
|
||||||
"version": "1.79.5",
|
"version": "1.80.1",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.79.5.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.80.1.tgz",
|
||||||
"integrity": "sha512-W1h5kp6bdhqFh2tk3DsI771MoEJjvrSY/2ihJRJS4pjIyfJCw0nTsxqhnrUzaLMOJjFchj8rOvraI/YUVjtx5g==",
|
"integrity": "sha512-9lBwDZ7j3y/1DKj5Ec249EVGo5CVpwnzIyIj+cqlCjKkApLnzsJ/l9SnV4YnORvW9dQwQN+gQvh/mFZ8CnDs7Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -6818,9 +6818,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.13",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz",
|
||||||
"integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==",
|
"integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -6944,9 +6944,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tinyexec": {
|
"node_modules/tinyexec": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz",
|
||||||
"integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==",
|
"integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@ -7062,9 +7062,9 @@
|
|||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.7.0",
|
"version": "2.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz",
|
||||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
"integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
@ -7195,9 +7195,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.8",
|
"version": "5.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz",
|
||||||
"integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
|
"integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -7268,9 +7268,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite-node": {
|
"node_modules/vite-node": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.3.tgz",
|
||||||
"integrity": "sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==",
|
"integrity": "sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -7322,15 +7322,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite-plugin-vue-devtools": {
|
"node_modules/vite-plugin-vue-devtools": {
|
||||||
"version": "7.4.6",
|
"version": "7.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.5.2.tgz",
|
||||||
"integrity": "sha512-lOKur3qovCB3BQStL0qfHEoIusqya1ngfxfWuqn9DTa6h9rlw6+S3PV4geOP5YBGYQ4NW1hRX70OD8I+sYr1dA==",
|
"integrity": "sha512-+lQOKW0kZAvLxy9KcsmtOk5Hsu0ibVAot9odFwCCASE4jukb0zaWGIyZwFLk4IsWNDT3iISvajIr704UYcZL6g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/devtools-core": "^7.4.6",
|
"@vue/devtools-core": "^7.5.2",
|
||||||
"@vue/devtools-kit": "^7.4.6",
|
"@vue/devtools-kit": "^7.5.2",
|
||||||
"@vue/devtools-shared": "^7.4.6",
|
"@vue/devtools-shared": "^7.5.2",
|
||||||
"execa": "^8.0.1",
|
"execa": "^8.0.1",
|
||||||
"sirv": "^2.0.4",
|
"sirv": "^2.0.4",
|
||||||
"vite-plugin-inspect": "^0.8.7",
|
"vite-plugin-inspect": "^0.8.7",
|
||||||
@ -7365,19 +7365,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vitest": {
|
"node_modules/vitest": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.3.tgz",
|
||||||
"integrity": "sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==",
|
"integrity": "sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/expect": "2.1.2",
|
"@vitest/expect": "2.1.3",
|
||||||
"@vitest/mocker": "2.1.2",
|
"@vitest/mocker": "2.1.3",
|
||||||
"@vitest/pretty-format": "^2.1.2",
|
"@vitest/pretty-format": "^2.1.3",
|
||||||
"@vitest/runner": "2.1.2",
|
"@vitest/runner": "2.1.3",
|
||||||
"@vitest/snapshot": "2.1.2",
|
"@vitest/snapshot": "2.1.3",
|
||||||
"@vitest/spy": "2.1.2",
|
"@vitest/spy": "2.1.3",
|
||||||
"@vitest/utils": "2.1.2",
|
"@vitest/utils": "2.1.3",
|
||||||
"chai": "^5.1.1",
|
"chai": "^5.1.1",
|
||||||
"debug": "^4.3.6",
|
"debug": "^4.3.6",
|
||||||
"magic-string": "^0.30.11",
|
"magic-string": "^0.30.11",
|
||||||
@ -7388,7 +7388,7 @@
|
|||||||
"tinypool": "^1.0.0",
|
"tinypool": "^1.0.0",
|
||||||
"tinyrainbow": "^1.2.0",
|
"tinyrainbow": "^1.2.0",
|
||||||
"vite": "^5.0.0",
|
"vite": "^5.0.0",
|
||||||
"vite-node": "2.1.2",
|
"vite-node": "2.1.3",
|
||||||
"why-is-node-running": "^2.3.0"
|
"why-is-node-running": "^2.3.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -7403,8 +7403,8 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@edge-runtime/vm": "*",
|
"@edge-runtime/vm": "*",
|
||||||
"@types/node": "^18.0.0 || >=20.0.0",
|
"@types/node": "^18.0.0 || >=20.0.0",
|
||||||
"@vitest/browser": "2.1.2",
|
"@vitest/browser": "2.1.3",
|
||||||
"@vitest/ui": "2.1.2",
|
"@vitest/ui": "2.1.3",
|
||||||
"happy-dom": "*",
|
"happy-dom": "*",
|
||||||
"jsdom": "*"
|
"jsdom": "*"
|
||||||
},
|
},
|
||||||
@ -7794,9 +7794,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "2.5.1",
|
"version": "2.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
|
||||||
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
|
"integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -18,11 +18,11 @@
|
|||||||
"@vueuse/core": "^10.5.0",
|
"@vueuse/core": "^10.5.0",
|
||||||
"@vueuse/integrations": "^10.5.0",
|
"@vueuse/integrations": "^10.5.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"phaser": "^3.85.2",
|
"phaser": "^3.86.0",
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.6",
|
||||||
"socket.io-client": "^4.8.0",
|
"socket.io-client": "^4.8.0",
|
||||||
"universal-cookie": "^6.1.3",
|
"universal-cookie": "^6.1.3",
|
||||||
"vue": "^3.5.10",
|
"vue": "^3.5.12",
|
||||||
"zod": "^3.22.2"
|
"zod": "^3.22.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -48,8 +48,8 @@
|
|||||||
"sass": "^1.79.4",
|
"sass": "^1.79.4",
|
||||||
"tailwindcss": "^3.4.13",
|
"tailwindcss": "^3.4.13",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"vite": "^5.4.8",
|
"vite": "^5.4.9",
|
||||||
"vite-plugin-vue-devtools": "^7.4.6",
|
"vite-plugin-vue-devtools": "^7.5.2",
|
||||||
"vitest": "^2.0.3",
|
"vitest": "^2.0.3",
|
||||||
"vue-tsc": "^1.6.5"
|
"vue-tsc": "^1.6.5"
|
||||||
}
|
}
|
||||||
|
20
public/assets/icons/chat-icon.svg
Normal file
After Width: | Height: | Size: 597 KiB |
Before Width: | Height: | Size: 1.4 KiB |
@ -1 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" id="screenshot-7904a66d-0c2c-80b0-8004-7aa8e4dbc6e0" viewBox="5853.755 6642.086 23.788 23.788" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-7904a66d-0c2c-80b0-8004-7aa8e4dbc6e0"><g class="fills" id="fills-7904a66d-0c2c-80b0-8004-7aa8e4dbc6e0"><path d="M5867.837,6654.000L5877.045,6644.792C5877.199,6644.649,5877.322,6644.476,5877.408,6644.284C5877.493,6644.093,5877.539,6643.886,5877.543,6643.676C5877.546,6643.466,5877.508,6643.258,5877.429,6643.063C5877.350,6642.869,5877.234,6642.692,5877.085,6642.544C5876.937,6642.395,5876.760,6642.278,5876.565,6642.200C5876.371,6642.121,5876.163,6642.082,5875.953,6642.086C5875.743,6642.090,5875.536,6642.136,5875.344,6642.221C5875.153,6642.307,5874.980,6642.430,5874.837,6642.583L5865.629,6651.792L5856.420,6642.583C5856.124,6642.307,5855.732,6642.157,5855.328,6642.164C5854.923,6642.171,5854.537,6642.335,5854.250,6642.622C5853.964,6642.908,5853.800,6643.294,5853.793,6643.699C5853.786,6644.104,5853.936,6644.495,5854.212,6644.792L5863.420,6654.000L5854.212,6663.208C5853.919,6663.501,5853.755,6663.898,5853.755,6664.312C5853.755,6664.726,5853.919,6665.124,5854.212,6665.417C5854.505,6665.709,5854.902,6665.874,5855.316,6665.874C5855.730,6665.874,5856.127,6665.709,5856.420,6665.417L5865.629,6656.208L5874.837,6665.417C5875.130,6665.709,5875.527,6665.874,5875.941,6665.874C5876.355,6665.874,5876.753,6665.709,5877.045,6665.417C5877.338,6665.124,5877.502,6664.726,5877.502,6664.312C5877.502,6663.898,5877.338,6663.501,5877.045,6663.208L5867.837,6654.000ZZ" style="fill: rgb(255, 255, 255);"/></g></g></svg>
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.91481 9.08504C7.71955 9.2803 7.40297 9.2803 7.20771 9.08504L5.55709 7.43442C5.36183 7.23916 5.04524 7.23916 4.84998 7.43442L2.99578 9.28862C2.80052 9.48389 2.48393 9.48389 2.28867 9.28863L1.00416 8.00412C0.808899 7.80885 0.808899 7.49227 1.00416 7.29701L2.85837 5.4428C3.05363 5.24754 3.05363 4.93096 2.85837 4.7357L0.914865 2.7922C0.719603 2.59693 0.719603 2.28035 0.914865 2.08509L2.07053 0.929423C2.26579 0.734161 2.58238 0.734162 2.77764 0.929424L4.72114 2.87293C4.9164 3.06819 5.23298 3.06819 5.42825 2.87293L7.297 1.00417C7.49226 0.808906 7.80885 0.808906 8.00411 1.00417L9.28862 2.28868C9.48388 2.48394 9.48388 2.80052 9.28862 2.99578L7.41986 4.86454C7.2246 5.0598 7.2246 5.37639 7.41986 5.57165L9.07048 7.22227C9.26574 7.41753 9.26574 7.73411 9.07048 7.92937L7.91481 9.08504Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 915 B |
7
public/assets/icons/eye-closed.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg width="20" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g>
|
||||||
|
<path fill="none" d="M0 0h24v24H0z"/>
|
||||||
|
<path d="M17.882 19.297A10.949 10.949 0 0 1 12 21c-5.392 0-9.878-3.88-10.819-9a10.982 10.982 0 0 1 3.34-6.066L1.392 2.808l1.415-1.415 19.799 19.8-1.415 1.414-3.31-3.31zM5.935 7.35A8.965 8.965 0 0 0 3.223 12a9.005 9.005 0 0 0 13.201 5.838l-2.028-2.028A4.5 4.5 0 0 1 8.19 9.604L5.935 7.35zm6.979 6.978l-3.242-3.242a2.5 2.5 0 0 0 3.241 3.241zm7.893 2.264l-1.431-1.43A8.935 8.935 0 0 0 20.777 12 9.005 9.005 0 0 0 9.552 5.338L7.974 3.76C9.221 3.27 10.58 3 12 3c5.392 0 9.878 3.88 10.819 9a10.947 10.947 0 0 1-2.012 4.592zm-9.084-9.084a4.5 4.5 0 0 1 4.769 4.769l-4.77-4.769z" fill="#808080"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 788 B |
BIN
public/assets/icons/f1-icon.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
public/assets/icons/f2-icon.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
public/assets/icons/f3-icon.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
public/assets/icons/f4-icon.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
public/assets/icons/f5-icon.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
public/assets/icons/f6-icon.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
public/assets/icons/f7-icon.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
public/assets/icons/f8-icon.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
25
public/assets/icons/map-icon.svg
Normal file
After Width: | Height: | Size: 600 KiB |
10
public/assets/icons/menu-icon.svg
Normal file
After Width: | Height: | Size: 600 KiB |
10
public/assets/icons/minus-icon.svg
Normal file
After Width: | Height: | Size: 597 KiB |
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 597 KiB |
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M21 15L15 21M21 8L8 21" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M21 15L15 21M21 8L8 21" stroke="#4d4d4d" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 346 B |
25
public/assets/icons/socials-icon.svg
Normal file
After Width: | Height: | Size: 599 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.3 KiB |
15
public/assets/login/sq-logo-v1.svg
Normal file
After Width: | Height: | Size: 12 KiB |
@ -1 +0,0 @@
|
|||||||
<svg width="290" xmlns="http://www.w3.org/2000/svg" height="87" id="screenshot-e9942e24-155b-8096-8004-7eb5ea5d2669" viewBox="0 0 290 87" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-e9942e24-155b-8096-8004-7eb5ea5d2669"><g class="fills" id="fills-e9942e24-155b-8096-8004-7eb5ea5d2669"><path d="M286.515,78.278C282.911,83.543,276.856,87.000,270.000,87.000L20.000,87.000C8.962,87.000,0.000,78.038,0.000,67.000L0.000,20.000C0.000,8.962,8.962,0.000,20.000,0.000L270.000,0.000C276.834,0.000,282.872,3.435,286.480,8.671C268.843,10.411,255.000,25.352,255.000,43.500C255.000,61.610,268.784,76.525,286.515,78.278ZM290.000,20.000L290.000,67.000" style="fill: #fff; fill-opacity: 1;"/></g></g></svg>
|
|
Before Width: | Height: | Size: 726 B |
@ -1 +0,0 @@
|
|||||||
<svg width="290" xmlns="http://www.w3.org/2000/svg" height="87" id="screenshot-e38d8c7f-bba0-801b-8004-7d6eeffceb00" viewBox="4058.354 6110 290 87" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-e38d8c7f-bba0-801b-8004-7d6eeffceb00"><g class="fills" id="fills-e38d8c7f-bba0-801b-8004-7d6eeffceb00"><path d="M4061.840,6118.722C4065.444,6113.457,4071.499,6110.000,4078.354,6110.000L4328.354,6110.000C4339.393,6110.000,4348.354,6118.962,4348.354,6130.000L4348.354,6177.000C4348.354,6188.038,4339.393,6197.000,4328.354,6197.000L4078.354,6197.000C4071.521,6197.000,4065.483,6193.565,4061.875,6188.329C4079.512,6186.589,4093.354,6171.648,4093.354,6153.500C4093.354,6135.390,4079.571,6120.475,4061.840,6118.722ZM4058.354,6177.000L4058.354,6130.000" style="fill: #fff; fill-opacity: 1;"/></g></g></svg>
|
|
Before Width: | Height: | Size: 829 B |
22
public/assets/ui-border-4-corners-light.svg
Normal file
After Width: | Height: | Size: 598 KiB |
26
public/assets/ui-border-4-corners.svg
Normal file
After Width: | Height: | Size: 471 KiB |
Before Width: | Height: | Size: 301 KiB After Width: | Height: | Size: 301 KiB |
Before Width: | Height: | Size: 400 KiB After Width: | Height: | Size: 400 KiB |
23
public/assets/ui-rect-border-4-corners.svg
Normal file
After Width: | Height: | Size: 470 KiB |
BIN
public/assets/ui-texture.png
Normal file
After Width: | Height: | Size: 135 KiB |
27
src/App.vue
@ -1,34 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="overflow-hidden">
|
|
||||||
<Notifications />
|
<Notifications />
|
||||||
<Login v-if="screen === 'login'" />
|
<component :is="currentScreen" />
|
||||||
<!-- <Register v-if="screen === 'register'" />-->
|
|
||||||
<Characters v-if="screen === 'characters'" />
|
|
||||||
<Game v-if="screen === 'game'" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||||
import Notifications from '@/components/utilities/Notifications.vue'
|
import Notifications from '@/components/utilities/Notifications.vue'
|
||||||
import Login from '@/screens/Login.vue'
|
import Login from '@/screens/Login.vue'
|
||||||
// import Register from '@/screens/Register.vue'
|
|
||||||
import Characters from '@/screens/Characters.vue'
|
import Characters from '@/screens/Characters.vue'
|
||||||
import Game from '@/screens/Game.vue'
|
import Game from '@/screens/Game.vue'
|
||||||
|
import ZoneEditor from '@/screens/ZoneEditor.vue'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
|
|
||||||
const screen = computed(() => {
|
const currentScreen = computed(() => {
|
||||||
if (!gameStore.connection) {
|
if (!gameStore.connection) return Login
|
||||||
return 'login'
|
if (!gameStore.token) return Login
|
||||||
} else if (gameStore.token && gameStore.connection) {
|
if (!gameStore.character) return Characters
|
||||||
if (gameStore.character) {
|
if (zoneEditorStore.active) return ZoneEditor
|
||||||
return 'game'
|
return Game
|
||||||
}
|
|
||||||
return 'characters'
|
|
||||||
}
|
|
||||||
return 'login' // default fallback
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Disable right click
|
// Disable right click
|
||||||
|
BIN
src/assets/fonts/upheavtt.ttf
Normal file
@ -4,11 +4,15 @@
|
|||||||
|
|
||||||
// Fonts
|
// Fonts
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Upheaval';
|
||||||
|
src: url('../fonts/upheavtt.ttf');
|
||||||
|
}
|
||||||
|
|
||||||
//Globals
|
//Globals
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-black m-0 select-none overscroll-none overflow-hidden;
|
@apply bg-black m-0 select-none;
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
|
||||||
@ -57,7 +61,7 @@ input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-field {
|
.input-field {
|
||||||
@apply px-4 py-3 text-base focus-visible:outline-none bg-gray border border-solid border-gray-500 rounded text-gray-300;
|
@apply px-4 py-2.5 text-base leading-5 focus-visible:outline-none bg-gray border border-solid border-gray-500 rounded text-gray-300;
|
||||||
&.inactive {
|
&.inactive {
|
||||||
@apply bg-gray-600/50 hover:cursor-not-allowed;
|
@apply bg-gray-600/50 hover:cursor-not-allowed;
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
@ -83,7 +87,7 @@ button {
|
|||||||
@apply text-center;
|
@apply text-center;
|
||||||
|
|
||||||
&.btn-cyan {
|
&.btn-cyan {
|
||||||
@apply bg-cyan text-gray-50 text-base rounded py-3;
|
@apply bg-cyan text-gray-50 text-base leading-5 rounded py-2.5;
|
||||||
|
|
||||||
&.active,
|
&.active,
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -92,7 +96,7 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.btn-red {
|
&.btn-red {
|
||||||
@apply bg-red text-gray-50;
|
@apply bg-red text-gray-50 text-base leading-5 rounded py-2.5;
|
||||||
|
|
||||||
&.active,
|
&.active,
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -100,9 +104,26 @@ button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.btn-empty {
|
||||||
|
@apply text-gray-50 border-2 border-solid border-gray-500 text-base leading-5 rounded py-2.5;
|
||||||
|
|
||||||
|
&.active,
|
||||||
|
&:hover {
|
||||||
|
@apply bg-gray-700 border-gray-700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply cursor-pointer;
|
@apply cursor-pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.eye-open {
|
||||||
|
@apply bg-[url('/assets/icons/eye-closed.svg')] w-5 h-4 right-2.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-pixel {
|
||||||
|
@apply text-white font-ui drop-shadow-pixel-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Scene name="effects" @preload="preloadScene" @create="createScene" @update="updateScene">
|
<Scene name="effects" @preload="preloadScene" @create="createScene" @update="updateScene"> </Scene>
|
||||||
</Scene>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -12,6 +11,8 @@ import { onBeforeMount, onBeforeUnmount, ref } from 'vue'
|
|||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const zoneEditorStore = useZoneEditorStore()
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
|
|
||||||
|
// See if there's a dat
|
||||||
|
|
||||||
const sceneRef = ref<Phaser.Scene | null>(null)
|
const sceneRef = ref<Phaser.Scene | null>(null)
|
||||||
|
|
||||||
// Effect-related refs
|
// Effect-related refs
|
||||||
@ -48,7 +49,7 @@ const createDayNightCycle = (scene: Phaser.Scene) => {
|
|||||||
const updateDayNightCycle = (time: number) => {
|
const updateDayNightCycle = (time: number) => {
|
||||||
if (!dayNightCycle.value) return
|
if (!dayNightCycle.value) return
|
||||||
|
|
||||||
const darkness = Math.sin((time % dayNightDuration) / dayNightDuration * Math.PI) * maxDarkness
|
const darkness = Math.sin(((time % dayNightDuration) / dayNightDuration) * Math.PI) * maxDarkness
|
||||||
dayNightCycle.value.clear()
|
dayNightCycle.value.clear()
|
||||||
dayNightCycle.value.fillStyle(0x000000, darkness)
|
dayNightCycle.value.fillStyle(0x000000, darkness)
|
||||||
dayNightCycle.value.fillRect(0, 0, window.innerWidth, window.innerHeight)
|
dayNightCycle.value.fillRect(0, 0, window.innerWidth, window.innerHeight)
|
||||||
@ -78,14 +79,14 @@ const toggleRain = (isRaining: boolean) => {
|
|||||||
const createFogEffect = (scene: Phaser.Scene) => {
|
const createFogEffect = (scene: Phaser.Scene) => {
|
||||||
fogSprite.value = scene.add.sprite(window.innerWidth / 2, window.innerHeight / 2, 'fog')
|
fogSprite.value = scene.add.sprite(window.innerWidth / 2, window.innerHeight / 2, 'fog')
|
||||||
fogSprite.value.setScale(2)
|
fogSprite.value.setScale(2)
|
||||||
fogSprite.value.setAlpha(0) // yeetdasasdasd
|
fogSprite.value.setAlpha(0)
|
||||||
fogSprite.value.setDepth(950) // yeetdasasdasd
|
fogSprite.value.setDepth(950)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateFogEffect = () => {
|
const updateFogEffect = () => {
|
||||||
if (fogSprite.value) {
|
if (fogSprite.value) {
|
||||||
// Example: Oscillate fog opacity
|
// Example: Oscillate fog opacity
|
||||||
const fogOpacity = (Math.sin(Date.now() / 5000) + 1) / 2 * 0.3
|
const fogOpacity = ((Math.sin(Date.now() / 5000) + 1) / 2) * 0.3
|
||||||
fogSprite.value.setAlpha(fogOpacity)
|
fogSprite.value.setAlpha(fogOpacity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal :isModalOpen="gameStore.uiSettings.isGmPanelOpen" @modal:close="() => gameStore.toggleGmPanel()" :modal-width="1000" :modal-height="650" :can-full-screen="true">
|
<Modal :isModalOpen="gameStore.uiSettings.isGmPanelOpen" @modal:close="() => gameStore.toggleGmPanel()" :modal-width="1000" :modal-height="650" :can-full-screen="true">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium shrink-0 text-gray-300">GM Panel</h3>
|
<h3 class="m-0 font-medium shrink-0 text-white">GM Panel</h3>
|
||||||
<div class="flex gap-1.5 flex-wrap">
|
<div class="flex gap-1.5 flex-wrap">
|
||||||
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">General</button>
|
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">General</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">Users</button>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal :isModalOpen="true" :closable="false" :is-resizable="false" :modal-width="modalWidth" :modal-height="modalHeight" :modal-position-x="posXY.x" :modal-position-y="posXY.y">
|
<Modal :isModalOpen="true" :closable="false" :is-resizable="false" :modal-width="modalWidth" :modal-height="modalHeight" :modal-position-x="posXY.x" :modal-position-y="posXY.y">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium shrink-0 text-gray-300">GM tools</h3>
|
<h3 class="m-0 font-medium shrink-0 text-white">GM tools</h3>
|
||||||
</template>
|
</template>
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
<div class="content flex flex-col gap-2.5 m-4 h-20">
|
<div class="content flex flex-col gap-2.5 m-4 h-20">
|
||||||
@ -20,7 +20,7 @@ import { onMounted, ref } from 'vue'
|
|||||||
const zoneEditorStore = useZoneEditorStore()
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const modalWidth = ref(200)
|
const modalWidth = ref(200)
|
||||||
const modalHeight = ref(180)
|
const modalHeight = ref(170)
|
||||||
|
|
||||||
let posXY = ref({ x: 0, y: 0 })
|
let posXY = ref({ x: 0, y: 0 })
|
||||||
|
|
||||||
|
@ -4,38 +4,38 @@
|
|||||||
<!-- Asset Categories -->
|
<!-- Asset Categories -->
|
||||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'tiles' }" @click="() => (selectedCategory = 'tiles')">
|
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'tiles' }" @click="() => (selectedCategory = 'tiles')">
|
||||||
<span>Tiles</span>
|
<span>Tiles</span>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'objects' }" @click="() => (selectedCategory = 'objects')">
|
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'objects' }" @click="() => (selectedCategory = 'objects')">
|
||||||
<span>Objects</span>
|
<span>Objects</span>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'sprites' }" @click="() => (selectedCategory = 'sprites')">
|
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'sprites' }" @click="() => (selectedCategory = 'sprites')">
|
||||||
<span>Sprites</span>
|
<span>Sprites</span>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
<a class="relative p-2.5 hover:cursor-pointer">
|
<a class="relative p-2.5 hover:cursor-pointer">
|
||||||
<span>Items</span>
|
<span>Items</span>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
<a class="relative p-2.5 hover:cursor-pointer">
|
<a class="relative p-2.5 hover:cursor-pointer">
|
||||||
<span>NPC's</span>
|
<span>NPC's</span>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
<a class="relative p-2.5 hover:cursor-pointer">
|
<a class="relative p-2.5 hover:cursor-pointer">
|
||||||
<span>Characters</span>
|
<span>Characters</span>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
<a class="relative p-2.5 hover:cursor-pointer">
|
<a class="relative p-2.5 hover:cursor-pointer">
|
||||||
<span>Mounts</span>
|
<span>Mounts</span>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
<a class="relative p-2.5 hover:cursor-pointer">
|
<a class="relative p-2.5 hover:cursor-pointer">
|
||||||
<span>Pets</span>
|
<span>Pets</span>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute w-px bg-cyan-200 h-full top-0 left-1/6"></div>
|
<div class="absolute w-px bg-gray-500 h-full top-0 left-1/6"></div>
|
||||||
|
|
||||||
<!-- Assets list -->
|
<!-- Assets list -->
|
||||||
<div class="overflow-auto h-full w-4/12 flex flex-col relative">
|
<div class="overflow-auto h-full w-4/12 flex flex-col relative">
|
||||||
@ -44,7 +44,7 @@
|
|||||||
<SpriteList v-if="selectedCategory === 'sprites'" />
|
<SpriteList v-if="selectedCategory === 'sprites'" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="absolute w-px bg-cyan-200 h-full top-0 left-1/2"></div>
|
<div class="absolute w-px bg-gray-500 h-full top-0 left-1/2"></div>
|
||||||
|
|
||||||
<!-- Asset details -->
|
<!-- Asset details -->
|
||||||
<div class="flex w-1/2 after:hidden flex-col relative overflow-auto">
|
<div class="flex w-1/2 after:hidden flex-col relative overflow-auto">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="filler"></div>
|
<div class="filler"></div>
|
||||||
<img class="max-h-56" :src="`${config.server_endpoint}/assets/objects/${selectedObject?.id}.png`" :alt="'Object ' + selectedObject?.id" />
|
<img class="max-h-56" :src="`${config.server_endpoint}/assets/objects/${selectedObject?.id}.png`" :alt="'Object ' + selectedObject?.id" />
|
||||||
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="removeObject">Remove</button>
|
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="removeObject">Remove</button>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-2.5 p-2.5 block">
|
<div class="m-2.5 p-2.5 block">
|
||||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveObject">
|
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveObject">
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
||||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||||
@ -18,10 +18,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<span>{{ object.name }}</span>
|
<span>{{ object.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-lg bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
|
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
|
||||||
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<div class="w-full flex gap-2 mt-2 pb-4 relative">
|
<div class="w-full flex gap-2 mt-2 pb-4 relative">
|
||||||
<button class="btn-cyan px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="saveSprite">Save</button>
|
<button class="btn-cyan px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="saveSprite">Save</button>
|
||||||
<button class="btn-red px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="deleteSprite">Delete</button>
|
<button class="btn-red px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="deleteSprite">Delete</button>
|
||||||
<div class="w-[calc(100%_+_32px)] absolute left-[-15px] bottom-0 h-px bg-cyan-200"></div>
|
<div class="w-[calc(100%_+_32px)] absolute left-[-15px] bottom-0 h-px bg-gray-500"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
||||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||||
@ -14,10 +14,10 @@
|
|||||||
<div class="flex items-center gap-2.5">
|
<div class="flex items-center gap-2.5">
|
||||||
<span>{{ sprite.name }}</span>
|
<span>{{ sprite.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-lg bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
|
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
|
||||||
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="filler"></div>
|
<div class="filler"></div>
|
||||||
<img class="max-h-72" :src="`${config.server_endpoint}/assets/tiles/${selectedTile?.id}.png`" :alt="'Tile ' + selectedTile?.id" />
|
<img class="max-h-72" :src="`${config.server_endpoint}/assets/tiles/${selectedTile?.id}.png`" :alt="'Tile ' + selectedTile?.id" />
|
||||||
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="deleteTile">Delete</button>
|
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="deleteTile">Delete</button>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-2.5 p-2.5 block">
|
<div class="m-2.5 p-2.5 block">
|
||||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveTile">
|
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveTile">
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
||||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||||
@ -18,10 +18,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<span>{{ tile.name }}</span>
|
<span>{{ tile.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-lg bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
|
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
|
||||||
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
14
src/components/gameMaster/zoneEditor/EventTiles.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<template></template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { ZoneEventTile } from '@/types'
|
||||||
|
import { tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
||||||
|
|
||||||
|
// function getEventTileImageProps(tile: ZoneEventTile) {
|
||||||
|
// return {
|
||||||
|
// x: tileToWorldX(zoneTilemap as any, tile.positionX, tile.positionY),
|
||||||
|
// y: tileToWorldY(zoneTilemap as any, tile.positionX, tile.positionY),
|
||||||
|
// texture: tile.type
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
</script>
|
126
src/components/gameMaster/zoneEditor/Objects.vue
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
<template>
|
||||||
|
<SelectedZoneObject v-if="selectedZoneObject" :zoneObject="selectedZoneObject" />
|
||||||
|
<Image
|
||||||
|
v-for="object in zoneEditorStore.zone?.zoneObjects"
|
||||||
|
v-bind="getObjectImageProps(object)"
|
||||||
|
@pointerup="() => selectedZoneObject = object"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { uuidv4 } from '@/utilities'
|
||||||
|
import { calculateIsometricDepth, getTile, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
||||||
|
import { Image, useScene } from 'phavuer'
|
||||||
|
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||||
|
import type { ZoneObject } from '@/types'
|
||||||
|
import SelectedZoneObject from '@/components/gameMaster/zoneEditor/partials/SelectedZoneObject.vue'
|
||||||
|
import { onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const scene = useScene()
|
||||||
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
|
const selectedZoneObject = ref<ZoneObject | null>(null)
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
tilemap: Phaser.Tilemaps.Tilemap
|
||||||
|
}>()
|
||||||
|
|
||||||
|
function getObjectImageProps(object: ZoneObject) {
|
||||||
|
return {
|
||||||
|
// alpha: object.id === movingZoneObject.value?.id ? .5 : 1,
|
||||||
|
depth: calculateIsometricDepth(object.positionX, object.positionY, object.object.frameWidth, object.object.frameHeight),
|
||||||
|
tint: selectedZoneObject.value?.id === object.id ? 0x00ff00 : 0xffffff,
|
||||||
|
x: tileToWorldX(props.tilemap as any, object.positionX, object.positionY),
|
||||||
|
y: tileToWorldY(props.tilemap as any, object.positionX, object.positionY),
|
||||||
|
flipX: object.isRotated,
|
||||||
|
texture: object.object.id,
|
||||||
|
originY: Number(object.object.originX),
|
||||||
|
originX: Number(object.object.originY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addZoneObject(pointer: Phaser.Input.Pointer) {
|
||||||
|
if (!zoneEditorStore.zone) return
|
||||||
|
|
||||||
|
// Check if tool is pencil
|
||||||
|
if (zoneEditorStore.tool !== 'pencil') return
|
||||||
|
|
||||||
|
// Check if draw mode is object
|
||||||
|
if (zoneEditorStore.drawMode !== 'object') return
|
||||||
|
|
||||||
|
// Check if left mouse button is pressed
|
||||||
|
if (!pointer.isDown) return
|
||||||
|
|
||||||
|
// Check if there is a tile @TODO chekc if props.tilemap words
|
||||||
|
const tile = getTile(props.tilemap, pointer.worldX, pointer.worldY)
|
||||||
|
if (!tile) return
|
||||||
|
|
||||||
|
// Check if there is a selected object
|
||||||
|
if (!zoneEditorStore.selectedObject) return
|
||||||
|
|
||||||
|
// Check if object already exists on position
|
||||||
|
const existingObject = zoneEditorStore.zone?.zoneObjects.find((object) => object.positionX === tile.x && object.positionY === tile.y)
|
||||||
|
if (existingObject) return
|
||||||
|
|
||||||
|
const newObject = {
|
||||||
|
id: uuidv4(),
|
||||||
|
zoneId: zoneEditorStore.zone.id,
|
||||||
|
zone: zoneEditorStore.zone,
|
||||||
|
object: zoneEditorStore.selectedObject,
|
||||||
|
depth: 0,
|
||||||
|
isRotated: false,
|
||||||
|
positionX: tile.x,
|
||||||
|
positionY: tile.y
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new object to zoneObjects
|
||||||
|
zoneEditorStore.zone.zoneObjects = zoneEditorStore.zone.zoneObjects.concat(newObject as ZoneObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
scene.input.on(Phaser.Input.Events.POINTER_DOWN, addZoneObject)
|
||||||
|
scene.input.on(Phaser.Input.Events.POINTER_MOVE, addZoneObject)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
scene.input.off(Phaser.Input.Events.POINTER_DOWN, addZoneObject)
|
||||||
|
scene.input.off(Phaser.Input.Events.POINTER_MOVE, addZoneObject)
|
||||||
|
})
|
||||||
|
|
||||||
|
// watch zoneEditorStore.objectList and update originX and originY of objects in zoneObjects
|
||||||
|
watch(
|
||||||
|
zoneEditorStore.objectList,
|
||||||
|
(newObjects) => {
|
||||||
|
// Check if zoneEditorStore.zone is set
|
||||||
|
if (!zoneEditorStore.zone) return
|
||||||
|
|
||||||
|
// Update zoneObjects
|
||||||
|
zoneEditorStore.zone.zoneObjects = zoneEditorStore.zone.zoneObjects.map((zoneObject) => {
|
||||||
|
const updatedObject = newObjects.find((obj) => obj.id === zoneObject.objectId)
|
||||||
|
if (updatedObject) {
|
||||||
|
return {
|
||||||
|
...zoneObject,
|
||||||
|
object: {
|
||||||
|
...zoneObject.object,
|
||||||
|
originX: updatedObject.originX,
|
||||||
|
originY: updatedObject.originY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return zoneObject
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update selectedObject if it's set
|
||||||
|
if (zoneEditorStore.selectedObject) {
|
||||||
|
const updatedObject = newObjects.find((obj) => obj.id === zoneEditorStore.selectedObject?.id)
|
||||||
|
if (updatedObject) {
|
||||||
|
zoneEditorStore.setSelectedObject({
|
||||||
|
...zoneEditorStore.selectedObject,
|
||||||
|
originX: updatedObject.originX,
|
||||||
|
originY: updatedObject.originY
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
</script>
|
91
src/components/gameMaster/zoneEditor/Tiles.vue
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<Controls :layer="tiles" :depth="0" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import config from '@/config'
|
||||||
|
import { useScene } from 'phavuer'
|
||||||
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||||
|
import { onBeforeMount, onBeforeUnmount } from 'vue'
|
||||||
|
import { getTile, placeTile, setAllTiles } from '@/composables/zoneComposable'
|
||||||
|
import Controls from '@/components/utilities/Controls.vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['tilemap:create'])
|
||||||
|
|
||||||
|
const scene = useScene()
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
|
|
||||||
|
const zoneTilemap = createTilemap()
|
||||||
|
const tiles = createTileLayer()
|
||||||
|
let tileArray = createTileArray()
|
||||||
|
|
||||||
|
function createTilemap() {
|
||||||
|
const zoneData = new Phaser.Tilemaps.MapData({
|
||||||
|
width: zoneEditorStore.zone?.width,
|
||||||
|
height: zoneEditorStore.zone?.height,
|
||||||
|
tileWidth: config.tile_size.x,
|
||||||
|
tileHeight: config.tile_size.y,
|
||||||
|
orientation: Phaser.Tilemaps.Orientation.ISOMETRIC,
|
||||||
|
format: Phaser.Tilemaps.Formats.ARRAY_2D
|
||||||
|
})
|
||||||
|
const tilemap = new Phaser.Tilemaps.Tilemap(scene, zoneData)
|
||||||
|
emit('tilemap:create', tilemap)
|
||||||
|
return tilemap
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTileLayer() {
|
||||||
|
const tilesetImages = gameStore.assets.filter((asset) => asset.group === 'tiles').map((asset, index) => zoneTilemap.addTilesetImage(asset.key, asset.key, config.tile_size.x, config.tile_size.y, 1, 2, index + 1, { x: 0, y: -config.tile_size.y }))
|
||||||
|
tilesetImages.push(zoneTilemap.addTilesetImage('blank_tile', 'blank_tile', config.tile_size.x, config.tile_size.y, 1, 2, 0, { x: 0, y: -config.tile_size.y }))
|
||||||
|
|
||||||
|
const layer = zoneTilemap.createBlankLayer('tiles', tilesetImages as any, 0, config.tile_size.y) as Phaser.Tilemaps.TilemapLayer
|
||||||
|
|
||||||
|
layer.setDepth(0)
|
||||||
|
layer.setCullPadding(2, 2)
|
||||||
|
|
||||||
|
return layer
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTileArray() {
|
||||||
|
return Array.from({ length: zoneTilemap.height || 0 }, () => Array.from({ length: zoneTilemap.width || 0 }, () => 'blank_tile'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTileClick(pointer: Phaser.Input.Pointer) {
|
||||||
|
// Check if tool is pencil
|
||||||
|
if (zoneEditorStore.tool !== 'pencil') return
|
||||||
|
|
||||||
|
// Check if draw mode is tile
|
||||||
|
if (zoneEditorStore.drawMode !== 'tile') return
|
||||||
|
|
||||||
|
// Check if left mouse button is pressed
|
||||||
|
if (!pointer.isDown) return
|
||||||
|
|
||||||
|
// Check if there is a tile
|
||||||
|
const tile = getTile(tiles, pointer.worldX, pointer.worldY)
|
||||||
|
if (!tile) return
|
||||||
|
|
||||||
|
// Check if there is a selected tile
|
||||||
|
if (!zoneEditorStore.selectedTile) return
|
||||||
|
|
||||||
|
placeTile(zoneTilemap, tiles, tile.x, tile.y, zoneEditorStore.selectedTile.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
if (!zoneEditorStore.zone?.tiles) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setAllTiles(zoneTilemap, tiles, zoneEditorStore.zone.tiles)
|
||||||
|
tileArray = zoneEditorStore.zone.tiles.map((row) => row.map((tileId) => tileId || 'blank_tile'))
|
||||||
|
|
||||||
|
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handleTileClick)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handleTileClick)
|
||||||
|
|
||||||
|
zoneTilemap.destroyLayer('tiles')
|
||||||
|
zoneTilemap.removeAllLayers()
|
||||||
|
zoneTilemap.destroy()
|
||||||
|
})
|
||||||
|
</script>
|
@ -1,213 +1,56 @@
|
|||||||
<template>
|
<template>
|
||||||
<Toolbar :layer="tiles" @eraser="eraser" @pencil="pencil" @paint="paint" @clear="clear" @save="save" />
|
<Tiles @tilemap:create="tileMap = $event" />
|
||||||
<ZoneList v-if="zoneEditorStore.isZoneListModalShown" />
|
<Objects v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />
|
||||||
|
<EventTiles v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />
|
||||||
|
|
||||||
<template v-if="zoneEditorStore.zone">
|
<Toolbar @save="save" />
|
||||||
<Controls :layer="tiles as TilemapLayer" />
|
|
||||||
|
|
||||||
<Tiles />
|
<ZoneList />
|
||||||
<Objects />
|
<TileList />
|
||||||
|
<ObjectList />
|
||||||
|
|
||||||
<ZoneSettings />
|
<ZoneSettings />
|
||||||
<TeleportModal v-if="shouldShowTeleportModal" />
|
<TeleportModal />
|
||||||
|
|
||||||
<Container :depth="2">
|
|
||||||
<Image v-for="object in zoneObjects" :depth="calculateIsometricDepth(object.positionX, object.positionY, 0)" :key="object.id" v-bind="getObjectImageProps(object)" @pointerup="() => setSelectedZoneObject(object)" :flipX="object.isRotated" />
|
|
||||||
</Container>
|
|
||||||
|
|
||||||
<Container :depth="3">
|
|
||||||
<Image v-for="tile in zoneEventTiles" :key="tile.id" v-bind="getEventTileImageProps(tile)" />
|
|
||||||
</Container>
|
|
||||||
|
|
||||||
<SelectedZoneObject v-if="zoneEditorStore.selectedZoneObject" @update_depth="updateZoneObjectDepth" @delete="deleteZoneObject" @move="handleMove" @rotate="handleRotate" />
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeMount, onMounted, onUnmounted, ref, watch } from 'vue'
|
import { onBeforeMount, onUnmounted, ref } from 'vue'
|
||||||
import { Container, Image, useScene } from 'phavuer'
|
import { useScene } from 'phavuer'
|
||||||
import { storeToRefs } from 'pinia'
|
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||||
import { calculateIsometricDepth, loadAssets, placeTile, setAllTiles, sortByIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
import { loadAssets } from '@/composables/zoneComposable'
|
||||||
import { ZoneEventTileType, type ZoneObject, type ZoneEventTile, type Zone } from '@/types'
|
import { type ZoneObject, type ZoneEventTile, type Zone } from '@/types'
|
||||||
import { uuidv4 } from '@/utilities'
|
|
||||||
import config from '@/config'
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Controls from '@/components/utilities/Controls.vue'
|
|
||||||
import Toolbar from '@/components/gameMaster/zoneEditor/partials/Toolbar.vue'
|
import Toolbar from '@/components/gameMaster/zoneEditor/partials/Toolbar.vue'
|
||||||
import Tiles from '@/components/gameMaster/zoneEditor/partials/TileList.vue'
|
import TileList from '@/components/gameMaster/zoneEditor/partials/TileList.vue'
|
||||||
import SelectedZoneObject from '@/components/gameMaster/zoneEditor/partials/SelectedZoneObject.vue'
|
import ObjectList from '@/components/gameMaster/zoneEditor/partials/ObjectList.vue'
|
||||||
import ZoneSettings from '@/components/gameMaster/zoneEditor/partials/ZoneSettings.vue'
|
import ZoneSettings from '@/components/gameMaster/zoneEditor/partials/ZoneSettings.vue'
|
||||||
import Objects from '@/components/gameMaster/zoneEditor/partials/ObjectList.vue'
|
|
||||||
import ZoneList from '@/components/gameMaster/zoneEditor/partials/ZoneList.vue'
|
import ZoneList from '@/components/gameMaster/zoneEditor/partials/ZoneList.vue'
|
||||||
import TeleportModal from '@/components/gameMaster/zoneEditor/partials/TeleportModal.vue'
|
import TeleportModal from '@/components/gameMaster/zoneEditor/partials/TeleportModal.vue'
|
||||||
import Tilemap = Phaser.Tilemaps.Tilemap
|
import Tiles from '@/components/gameMaster/zoneEditor/Tiles.vue'
|
||||||
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
|
import Objects from '@/components/gameMaster/zoneEditor/Objects.vue'
|
||||||
|
import EventTiles from '@/components/gameMaster/zoneEditor/EventTiles.vue'
|
||||||
/**
|
|
||||||
* @TODO:
|
|
||||||
* Clean all the code in this file
|
|
||||||
*/
|
|
||||||
|
|
||||||
const scene = useScene()
|
const scene = useScene()
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const zoneEditorStore = useZoneEditorStore()
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
|
|
||||||
const { objectList, zone, selectedTile, selectedObject, selectedZoneObject, eraserMode, drawMode } = storeToRefs(zoneEditorStore)
|
const tileMap = ref(null as Phaser.Tilemaps.Tilemap | null)
|
||||||
|
const tileArray = ref<string[][]>([])
|
||||||
const zoneTilemap = createTilemap()
|
|
||||||
const tiles = createTileLayer()
|
|
||||||
const zoneObjects = ref<ZoneObject[]>([])
|
const zoneObjects = ref<ZoneObject[]>([])
|
||||||
const zoneEventTiles = ref<ZoneEventTile[]>([])
|
const zoneEventTiles = ref<ZoneEventTile[]>([])
|
||||||
let tileArray = createTileArray()
|
|
||||||
|
|
||||||
const shouldShowTeleportModal = computed(() => zoneEditorStore.tool === 'pencil' && drawMode.value === 'teleport')
|
|
||||||
|
|
||||||
function createTilemap() {
|
|
||||||
const zoneData = new Phaser.Tilemaps.MapData({
|
|
||||||
width: zone.value?.width ?? 10,
|
|
||||||
height: zone.value?.height ?? 10,
|
|
||||||
tileWidth: config.tile_size.x,
|
|
||||||
tileHeight: config.tile_size.y,
|
|
||||||
orientation: Phaser.Tilemaps.Orientation.ISOMETRIC,
|
|
||||||
format: Phaser.Tilemaps.Formats.ARRAY_2D
|
|
||||||
})
|
|
||||||
const tilemap = new Phaser.Tilemaps.Tilemap(scene, zoneData)
|
|
||||||
return tilemap
|
|
||||||
}
|
|
||||||
|
|
||||||
function createTileLayer() {
|
|
||||||
const tilesetImages = gameStore.assets.filter((asset) => asset.group === 'tiles').map((asset, index) => zoneTilemap.addTilesetImage(asset.key, asset.key, config.tile_size.x, config.tile_size.y, 1, 2, index + 1, { x: 0, y: -config.tile_size.y }))
|
|
||||||
tilesetImages.push(zoneTilemap.addTilesetImage('blank_tile', 'blank_tile', config.tile_size.x, config.tile_size.y, 1, 2, 0, { x: 0, y: -config.tile_size.y }))
|
|
||||||
|
|
||||||
const layer = zoneTilemap.createBlankLayer('tiles', tilesetImages as any, 0, config.tile_size.y) as Phaser.Tilemaps.TilemapLayer
|
|
||||||
|
|
||||||
layer.setDepth(0)
|
|
||||||
|
|
||||||
return layer
|
|
||||||
}
|
|
||||||
|
|
||||||
function createTileArray() {
|
|
||||||
return Array.from({ length: zoneTilemap.height || 0 }, () => Array.from({ length: zoneTilemap.width || 0 }, () => 'blank_tile'))
|
|
||||||
}
|
|
||||||
|
|
||||||
function getObjectImageProps(object: ZoneObject) {
|
|
||||||
return {
|
|
||||||
tint: selectedZoneObject.value?.id === object.id ? 0x00ff00 : 0xffffff,
|
|
||||||
x: tileToWorldX(zoneTilemap as any, object.positionX, object.positionY),
|
|
||||||
y: tileToWorldY(zoneTilemap as any, object.positionX, object.positionY),
|
|
||||||
texture: object.object.id,
|
|
||||||
originY: Number(object.object.originX),
|
|
||||||
originX: Number(object.object.originY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEventTileImageProps(tile: ZoneEventTile) {
|
|
||||||
return {
|
|
||||||
x: tileToWorldX(zoneTilemap as any, tile.positionX, tile.positionY),
|
|
||||||
y: tileToWorldY(zoneTilemap as any, tile.positionX, tile.positionY),
|
|
||||||
texture: tile.type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function eraser(tile: Phaser.Tilemaps.Tile) {
|
|
||||||
if (eraserMode.value === 'tile') {
|
|
||||||
placeTile(zoneTilemap as Tilemap, tiles as TilemapLayer, tile.x, tile.y, 'blank_tile')
|
|
||||||
tileArray[tile.y][tile.x] = 'blank_tile'
|
|
||||||
} else if (eraserMode.value === 'object') {
|
|
||||||
zoneObjects.value = zoneObjects.value.filter((object) => object.positionX !== tile.x || object.positionY !== tile.y)
|
|
||||||
} else if (eraserMode.value === 'blocking tile' || eraserMode.value === 'teleport') {
|
|
||||||
zoneEventTiles.value = zoneEventTiles.value.filter((eventTile) => eventTile.positionX !== tile.x || eventTile.positionY !== tile.y || (eraserMode.value === 'teleport' && eventTile.type !== ZoneEventTileType.TELEPORT))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function pencil(tile: Phaser.Tilemaps.Tile) {
|
|
||||||
if (drawMode.value === 'tile' && selectedTile.value) {
|
|
||||||
placeTile(zoneTilemap as Tilemap, tiles as TilemapLayer, tile.x, tile.y, selectedTile.value.id)
|
|
||||||
tileArray[tile.y][tile.x] = selectedTile.value.id
|
|
||||||
} else if (drawMode.value === 'object' && selectedObject.value) {
|
|
||||||
addZoneObject(tile)
|
|
||||||
} else if (drawMode.value === 'blocking tile' || drawMode.value === 'teleport') {
|
|
||||||
addZoneEventTile(tile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addZoneObject(tile: Phaser.Tilemaps.Tile) {
|
|
||||||
// Check if object already exists on position
|
|
||||||
const existingObject = zoneObjects.value.find((object) => object.positionX === tile.x && object.positionY === tile.y)
|
|
||||||
if (existingObject) return
|
|
||||||
|
|
||||||
const newObject = {
|
|
||||||
id: uuidv4(),
|
|
||||||
zoneId: zone.value!.id,
|
|
||||||
zone: zone.value!,
|
|
||||||
objectId: selectedObject.value!.id,
|
|
||||||
object: selectedObject.value!,
|
|
||||||
depth: 0,
|
|
||||||
isRotated: false,
|
|
||||||
positionX: tile.x,
|
|
||||||
positionY: tile.y
|
|
||||||
}
|
|
||||||
// Add new object to zoneObjects
|
|
||||||
zoneObjects.value = zoneObjects.value.concat(newObject)
|
|
||||||
}
|
|
||||||
|
|
||||||
function addZoneEventTile(tile: Phaser.Tilemaps.Tile) {
|
|
||||||
// Check if event tile already exists on position
|
|
||||||
const existingEventTile = zoneEventTiles.value.find((eventTile) => eventTile.positionX === tile.x && eventTile.positionY === tile.y)
|
|
||||||
if (existingEventTile) return
|
|
||||||
|
|
||||||
const newEventTile = {
|
|
||||||
id: uuidv4(),
|
|
||||||
zoneId: zone.value!.id,
|
|
||||||
zone: zone.value!,
|
|
||||||
type: drawMode.value === 'blocking tile' ? ZoneEventTileType.BLOCK : ZoneEventTileType.TELEPORT,
|
|
||||||
positionX: tile.x,
|
|
||||||
positionY: tile.y,
|
|
||||||
teleport:
|
|
||||||
drawMode.value === 'teleport'
|
|
||||||
? {
|
|
||||||
toZoneId: zoneEditorStore.teleportSettings.toZoneId,
|
|
||||||
toPositionX: zoneEditorStore.teleportSettings.toPositionX,
|
|
||||||
toPositionY: zoneEditorStore.teleportSettings.toPositionY,
|
|
||||||
toRotation: zoneEditorStore.teleportSettings.toRotation
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
zoneEventTiles.value = zoneEventTiles.value.concat(newEventTile as any)
|
|
||||||
}
|
|
||||||
|
|
||||||
function paint() {
|
|
||||||
if (!selectedTile.value) return
|
|
||||||
|
|
||||||
// Ensure tileArray is initialized with correct dimensions
|
|
||||||
if (!tileArray || tileArray.length !== zoneTilemap.height) {
|
|
||||||
tileArray = Array.from({ length: zoneTilemap.height }, () => Array.from({ length: zoneTilemap.width }, () => 'blank_tile'))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set all tiles in the tilemap to the selected tile's id
|
|
||||||
for (let y = 0; y < zoneTilemap.height; y++) {
|
|
||||||
if (!tileArray[y]) {
|
|
||||||
tileArray[y] = Array(zoneTilemap.width).fill('blank_tile')
|
|
||||||
}
|
|
||||||
for (let x = 0; x < zoneTilemap.width; x++) {
|
|
||||||
placeTile(zoneTilemap as Tilemap, tiles as TilemapLayer, x, y, selectedTile.value.id)
|
|
||||||
tileArray[y][x] = selectedTile.value.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
if (!zone.value) return
|
if (!zoneEditorStore.zone) return
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
zoneId: zone.value.id,
|
zoneId: zoneEditorStore.zone.id,
|
||||||
name: zoneEditorStore.zoneSettings.name,
|
name: zoneEditorStore.zoneSettings.name,
|
||||||
width: zoneEditorStore.zoneSettings.width,
|
width: zoneEditorStore.zoneSettings.width,
|
||||||
height: zoneEditorStore.zoneSettings.height,
|
height: zoneEditorStore.zoneSettings.height,
|
||||||
tiles: tileArray,
|
tiles: tileArray,
|
||||||
pvp: zone.value.pvp,
|
pvp: zoneEditorStore.zone.pvp,
|
||||||
zoneEventTiles: zoneEventTiles.value.map(({ id, zoneId, type, positionX, positionY, teleport }) => ({ id, zoneId, type, positionX, positionY, teleport })),
|
zoneEventTiles: zoneEventTiles.value.map(({ id, zoneId, type, positionX, positionY, teleport }) => ({ id, zoneId, type, positionX, positionY, teleport })),
|
||||||
zoneObjects: zoneObjects.value.map(({ id, zoneId, objectId, depth, isRotated, positionX, positionY }) => ({ id, zoneId, objectId, depth, isRotated, positionX, positionY }))
|
zoneObjects: zoneObjects.value.map(({ id, zoneId, objectId, depth, isRotated, positionX, positionY }) => ({ id, zoneId, objectId, depth, isRotated, positionX, positionY }))
|
||||||
}
|
}
|
||||||
@ -217,111 +60,16 @@ function save() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:zone_editor:zone:update', data, (response: Zone) => {
|
gameStore.connection?.emit('gm:zone_editor:zone:update', data, (response: Zone) => {
|
||||||
console.log('zone updated')
|
|
||||||
zoneEditorStore.setZone(response)
|
zoneEditorStore.setZone(response)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear() {
|
|
||||||
for (let y = 0; y < zoneTilemap.height; y++) {
|
|
||||||
if (!tileArray[y]) {
|
|
||||||
tileArray[y] = Array(zoneTilemap.width).fill('blank_tile')
|
|
||||||
}
|
|
||||||
for (let x = 0; x < zoneTilemap.width; x++) {
|
|
||||||
placeTile(zoneTilemap as Tilemap, tiles as TilemapLayer, x, y, 'blank_tile')
|
|
||||||
tileArray[y][x] = 'blank_tile'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zoneEventTiles.value = []
|
|
||||||
zoneObjects.value = []
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateZoneObjectDepth(depth: number) {
|
|
||||||
zoneObjects.value = zoneObjects.value.map((object) => (object.id === selectedZoneObject.value?.id ? { ...object, depth } : object))
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteZoneObject(objectId: string) {
|
|
||||||
zoneObjects.value = zoneObjects.value.filter((object) => object.id !== objectId)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMove() {
|
|
||||||
console.log('move btn clicked')
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleRotate(objectId: string) {
|
|
||||||
const object = zoneObjects.value.find((obj) => obj.id === objectId)
|
|
||||||
if (object) {
|
|
||||||
object.isRotated = !object.isRotated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// watch zoneEditorStore.objectList and update originX and originY of objects in zoneObjects
|
|
||||||
watch(
|
|
||||||
objectList,
|
|
||||||
(newObjects) => {
|
|
||||||
zoneObjects.value = zoneObjects.value.map((zoneObject) => {
|
|
||||||
const updatedObject = newObjects.find((obj) => obj.id === zoneObject.objectId)
|
|
||||||
if (updatedObject) {
|
|
||||||
return {
|
|
||||||
...zoneObject,
|
|
||||||
object: {
|
|
||||||
...zoneObject.object,
|
|
||||||
originX: updatedObject.originX,
|
|
||||||
originY: updatedObject.originY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return zoneObject
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update selectedObject if it exists
|
|
||||||
if (zoneEditorStore.selectedObject) {
|
|
||||||
const updatedObject = newObjects.find((obj) => obj.id === zoneEditorStore.selectedObject?.id)
|
|
||||||
if (updatedObject) {
|
|
||||||
zoneEditorStore.setSelectedObject({
|
|
||||||
...zoneEditorStore.selectedObject,
|
|
||||||
originX: updatedObject.originX,
|
|
||||||
originY: updatedObject.originY
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
const setSelectedZoneObject = (zoneObject: ZoneObject | null) => {
|
|
||||||
if (!zoneObject) return
|
|
||||||
if (zoneEditorStore.tool !== 'move') return
|
|
||||||
zoneEditorStore.setSelectedZoneObject(zoneObject)
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
await gameStore.fetchAllZoneAssets()
|
await gameStore.fetchAllZoneAssets()
|
||||||
await loadAssets(scene)
|
await loadAssets(scene)
|
||||||
|
|
||||||
tileArray.forEach((row, y) => row.forEach((_, x) => placeTile(zoneTilemap, tiles, x, y, 'blank_tile')))
|
|
||||||
|
|
||||||
if (zone.value?.tiles) {
|
|
||||||
setAllTiles(zoneTilemap, tiles, zone.value.tiles)
|
|
||||||
tileArray = zone.value.tiles.map((row) => row.map((tileId) => tileId || 'blank_tile'))
|
|
||||||
}
|
|
||||||
|
|
||||||
zoneEventTiles.value = zone.value?.zoneEventTiles ?? []
|
|
||||||
zoneObjects.value = sortByIsometricDepth(zone.value?.zoneObjects ?? [])
|
|
||||||
|
|
||||||
// Center camera
|
|
||||||
const centerY = (zoneTilemap.height * zoneTilemap.tileHeight) / 2
|
|
||||||
const centerX = (zoneTilemap.width * zoneTilemap.tileWidth) / 2
|
|
||||||
scene.cameras.main.centerOn(centerX, centerY)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
zoneEventTiles.value = []
|
|
||||||
zoneObjects.value = []
|
|
||||||
tiles?.destroy()
|
|
||||||
zoneTilemap?.removeAllLayers()
|
|
||||||
zoneTilemap?.destroy()
|
|
||||||
zoneEditorStore.reset()
|
zoneEditorStore.reset()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal :isModalOpen="true" @modal:close="() => zoneEditorStore.toggleCreateZoneModal()" :modal-width="300" :modal-height="400" :is-resizable="false">
|
<Modal :isModalOpen="true" @modal:close="() => zoneEditorStore.toggleCreateZoneModal()" :modal-width="300" :modal-height="400" :is-resizable="false">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium shrink-0 text-gray-300">Create new zone</h3>
|
<h3 class="m-0 font-medium shrink-0 text-white">Create new zone</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<Modal :isModalOpen="zoneEditorStore.isObjectListModalShown" :modal-width="645" :modal-height="260" @modal:close="() => (zoneEditorStore.isObjectListModalShown = false)">
|
<Modal :isModalOpen="zoneEditorStore.isObjectListModalShown" :modal-width="645" :modal-height="260" @modal:close="() => (zoneEditorStore.isObjectListModalShown = false)">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="text-lg text-gray-300">Objects</h3>
|
<h3 class="text-lg text-white">Objects</h3>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="w-full flex gap-1.5 flex-row">
|
<div class="w-full flex gap-1.5 flex-row">
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col items-center py-5 px-3 fixed bottom-14 right-0" v-if="zoneEditorStore.selectedZoneObject">
|
<div class="flex flex-col items-center py-5 px-3 fixed bottom-14 right-0">
|
||||||
<div class="self-end mt-2 flex gap-2">
|
<div class="self-end mt-2 flex gap-2">
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1.5 font-titles block text-sm text-gray-700 hidden" for="depth">Depth</label>
|
<label class="mb-1.5 font-titles block text-sm text-gray-700 hidden" for="depth">Depth</label>
|
||||||
@ -42,7 +42,7 @@ const handleRotate = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleMove = () => {
|
const handleMove = () => {
|
||||||
emit('move')
|
emit('move', zoneEditorStore.selectedZoneObject?.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal :is-modal-open="true" @modal:close="() => zoneEditorStore.setTool('move')" :modal-width="300" :modal-height="350" :is-resizable="false">
|
<Modal :is-modal-open="showTeleportModal" @modal:close="() => zoneEditorStore.setTool('move')" :modal-width="300" :modal-height="350" :is-resizable="false">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium shrink-0 text-gray-300">Teleport settings</h3>
|
<h3 class="m-0 font-medium shrink-0 text-white">Teleport settings</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<form method="post" @submit.prevent="" class="inline">
|
<form method="post" @submit.prevent="" class="inline">
|
||||||
@ -40,12 +39,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import Modal from '@/components/utilities/Modal.vue'
|
import Modal from '@/components/utilities/Modal.vue'
|
||||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import type { Zone } from '@/types'
|
import type { Zone } from '@/types'
|
||||||
|
|
||||||
|
const showTeleportModal = computed(() => zoneEditorStore.tool === 'pencil' && zoneEditorStore.drawMode === 'teleport')
|
||||||
const zoneEditorStore = useZoneEditorStore()
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<Modal :isModalOpen="zoneEditorStore.isTileListModalShown" :modal-width="645" :modal-height="600" @modal:close="() => (zoneEditorStore.isTileListModalShown = false)">
|
<Modal :isModalOpen="zoneEditorStore.isTileListModalShown" :modal-width="645" :modal-height="600" @modal:close="() => (zoneEditorStore.isTileListModalShown = false)">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="text-lg text-gray-300">Tiles</h3>
|
<h3 class="text-lg text-white">Tiles</h3>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="w-full flex gap-1.5 flex-row">
|
<div class="w-full flex gap-1.5 flex-row">
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<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="clickOutsideElement" class="tools flex gap-2.5" v-if="zoneEditorStore.zone">
|
<div ref="toolbar" class="tools flex gap-2.5" v-if="zoneEditorStore.zone">
|
||||||
<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': zoneEditorStore.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': zoneEditorStore.tool === 'move' }" @click="handleClick('move')">
|
||||||
<img class="invert w-5 h-5" src="/assets/icons/zoneEditor/move.svg" alt="Move camera" /> <span :class="{ 'ml-2.5': zoneEditorStore.tool !== 'move' }">(M)</span>
|
<img class="invert w-5 h-5" src="/assets/icons/zoneEditor/move.svg" alt="Move camera" /> <span :class="{ 'ml-2.5': zoneEditorStore.tool !== 'move' }">(M)</span>
|
||||||
</button>
|
</button>
|
||||||
@ -83,22 +83,15 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import { useScene } from 'phavuer'
|
|
||||||
import { getTile } from '@/composables/zoneComposable'
|
|
||||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||||
import { onClickOutside } from '@vueuse/core'
|
import { onClickOutside } from '@vueuse/core'
|
||||||
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
|
|
||||||
|
|
||||||
const zoneEditorStore = useZoneEditorStore()
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
|
|
||||||
const props = defineProps({
|
const emit = defineEmits(['save', 'clear'])
|
||||||
layer: Phaser.Tilemaps.TilemapLayer
|
|
||||||
})
|
|
||||||
const scene = useScene()
|
|
||||||
const emit = defineEmits(['move', 'eraser', 'pencil', 'paint', 'save', 'clear'])
|
|
||||||
|
|
||||||
// track when clicked outside of toolbar items
|
// track when clicked outside of toolbar items
|
||||||
const clickOutsideElement = ref(null)
|
const toolbar = ref(null)
|
||||||
|
|
||||||
// track select state
|
// track select state
|
||||||
let selectPencilOpen = ref(false)
|
let selectPencilOpen = ref(false)
|
||||||
@ -119,47 +112,6 @@ function setEraserMode(value: string) {
|
|||||||
selectEraserOpen.value = false
|
selectEraserOpen.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function clickTile(pointer: Phaser.Input.Pointer) {
|
|
||||||
if (zoneEditorStore.tool !== 'eraser' && zoneEditorStore.tool !== 'pencil' && zoneEditorStore.tool !== 'paint') return
|
|
||||||
if (pointer.event.shiftKey) return
|
|
||||||
|
|
||||||
const px = scene.cameras.main.worldView.x + pointer.x
|
|
||||||
const py = scene.cameras.main.worldView.y + pointer.y
|
|
||||||
|
|
||||||
const pointer_tile = getTile(px, py, props.layer as TilemapLayer) as Phaser.Tilemaps.Tile
|
|
||||||
if (!pointer_tile) return
|
|
||||||
|
|
||||||
if (zoneEditorStore.tool === 'eraser') {
|
|
||||||
emit('eraser', pointer_tile)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zoneEditorStore.tool === 'pencil') {
|
|
||||||
emit('pencil', pointer_tile)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zoneEditorStore.tool === 'paint') {
|
|
||||||
emit('paint', pointer_tile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawTiles(pointer: Phaser.Input.Pointer) {
|
|
||||||
if (!pointer.isDown) return
|
|
||||||
clickTile(pointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_UP, clickTile)
|
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_MOVE, drawTiles)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
addEventListener('keydown', initKeyShortcuts)
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
scene.input.off(Phaser.Input.Events.POINTER_UP, clickTile)
|
|
||||||
scene.input.off(Phaser.Input.Events.POINTER_MOVE, drawTiles)
|
|
||||||
removeEventListener('keydown', initKeyShortcuts)
|
|
||||||
})
|
|
||||||
|
|
||||||
function handleClick(tool: string) {
|
function handleClick(tool: string) {
|
||||||
if (tool === 'settings') {
|
if (tool === 'settings') {
|
||||||
zoneEditorStore.toggleSettingsModal()
|
zoneEditorStore.toggleSettingsModal()
|
||||||
@ -172,16 +124,16 @@ function handleClick(tool: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cycleToolMode(tool: 'pencil' | 'eraser') {
|
function cycleToolMode(tool: 'pencil' | 'eraser') {
|
||||||
const modes = ['tile', 'object', 'teleport', 'blocking tile'];
|
const modes = ['tile', 'object', 'teleport', 'blocking tile']
|
||||||
const currentMode = tool === 'pencil' ? zoneEditorStore.drawMode : zoneEditorStore.eraserMode;
|
const currentMode = tool === 'pencil' ? zoneEditorStore.drawMode : zoneEditorStore.eraserMode
|
||||||
const currentIndex = modes.indexOf(currentMode);
|
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') {
|
if (tool === 'pencil') {
|
||||||
setDrawMode(nextMode);
|
setDrawMode(nextMode)
|
||||||
} else {
|
} else {
|
||||||
setEraserMode(nextMode);
|
setEraserMode(nextMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,19 +151,26 @@ 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') && zoneEditorStore.tool === tool) {
|
if ((tool === 'pencil' || tool === 'eraser') && zoneEditorStore.tool === tool) {
|
||||||
cycleToolMode(tool);
|
cycleToolMode(tool)
|
||||||
} else {
|
} else {
|
||||||
handleClick(tool);
|
handleClick(tool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickOutside(clickOutsideElement, handleClickOutside)
|
|
||||||
|
|
||||||
function handleClickOutside() {
|
function handleClickOutside() {
|
||||||
selectPencilOpen.value = false
|
selectPencilOpen.value = false
|
||||||
selectEraserOpen.value = false
|
selectEraserOpen.value = false
|
||||||
}
|
}
|
||||||
|
onClickOutside(toolbar, handleClickOutside)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
addEventListener('keydown', initKeyShortcuts)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
removeEventListener('keydown', initKeyShortcuts)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<CreateZone v-if="zoneEditorStore.isCreateZoneModalShown" />
|
<CreateZone v-if="zoneEditorStore.isCreateZoneModalShown" />
|
||||||
|
<Modal :is-modal-open="zoneEditorStore.isZoneListModalShown" @modal:close="() => zoneEditorStore.toggleZoneListModal()" :is-resizable="false" :modal-width="300" :modal-height="360">
|
||||||
<Teleport to="body">
|
|
||||||
<Modal @modal:close="() => zoneEditorStore.toggleZoneListModal()" :is-resizable="false" :is-modal-open="true" :modal-width="300" :modal-height="360">
|
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="text-lg text-gray-300">Zones</h3>
|
<h3 class="text-lg text-white">Zones</h3>
|
||||||
</template>
|
</template>
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
<div class="my-4 mx-auto">
|
<div class="my-4 mx-auto">
|
||||||
@ -13,19 +11,18 @@
|
|||||||
<button class="btn-cyan py-1.5 min-w-[calc(50%_-_5px)]" @click="() => zoneEditorStore.toggleCreateZoneModal()">New</button>
|
<button class="btn-cyan py-1.5 min-w-[calc(50%_-_5px)]" @click="() => zoneEditorStore.toggleCreateZoneModal()">New</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap" v-for="(zone, index) in zoneEditorStore.zoneList" :key="zone.id">
|
<div class="relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap" v-for="(zone, index) in zoneEditorStore.zoneList" :key="zone.id">
|
||||||
<div class="absolute left-0 top-0 w-full h-px bg-cyan-200" v-if="index === 0"></div>
|
<div class="absolute left-0 top-0 w-full h-px bg-gray-500" v-if="index === 0"></div>
|
||||||
<div class="flex gap-3 items-center w-full" @click="() => loadZone(zone.id)">
|
<div class="flex gap-3 items-center w-full" @click="() => loadZone(zone.id)">
|
||||||
<span>{{ zone.name }}</span>
|
<span>{{ zone.name }}</span>
|
||||||
<span class="ml-auto gap-1 flex">
|
<span class="ml-auto gap-1 flex">
|
||||||
<button class="btn-red py-0.5 px-2.5 z-50" @click.stop="() => deleteZone(zone.id)">X</button>
|
<button class="btn-red w-11 h-11 z-50" @click.stop="() => deleteZone(zone.id)">X</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</Teleport>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal :is-modal-open="zoneEditorStore.isSettingsModalShown" @modal:close="() => zoneEditorStore.toggleSettingsModal()" :modal-width="600" :modal-height="350">
|
<Modal :is-modal-open="zoneEditorStore.isSettingsModalShown" @modal:close="() => zoneEditorStore.toggleSettingsModal()" :modal-width="600" :modal-height="350">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium shrink-0 text-gray-300">Zone settings</h3>
|
<h3 class="m-0 font-medium shrink-0 text-white">Zone settings</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
@ -41,15 +41,15 @@ import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
|||||||
|
|
||||||
const zoneEditorStore = useZoneEditorStore()
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
|
|
||||||
zoneEditorStore.setZoneName(zoneEditorStore.zone.name)
|
zoneEditorStore.setZoneName(zoneEditorStore.zone?.name)
|
||||||
zoneEditorStore.setZoneWidth(zoneEditorStore.zone.width)
|
zoneEditorStore.setZoneWidth(zoneEditorStore.zone?.width)
|
||||||
zoneEditorStore.setZoneHeight(zoneEditorStore.zone.height)
|
zoneEditorStore.setZoneHeight(zoneEditorStore.zone?.height)
|
||||||
zoneEditorStore.setZonePvp(zoneEditorStore.zone.pvp)
|
zoneEditorStore.setZonePvp(zoneEditorStore.zone?.pvp)
|
||||||
|
|
||||||
const name = ref(zoneEditorStore.zoneSettings.name)
|
const name = ref(zoneEditorStore.zoneSettings?.name)
|
||||||
const width = ref(zoneEditorStore.zoneSettings.width)
|
const width = ref(zoneEditorStore.zoneSettings?.width)
|
||||||
const height = ref(zoneEditorStore.zoneSettings.height)
|
const height = ref(zoneEditorStore.zoneSettings?.height)
|
||||||
const pvp = ref(zoneEditorStore.zoneSettings.pvp)
|
const pvp = ref(zoneEditorStore.zoneSettings?.pvp)
|
||||||
|
|
||||||
watch(name, (value) => {
|
watch(name, (value) => {
|
||||||
zoneEditorStore.setZoneName(value)
|
zoneEditorStore.setZoneName(value)
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full md:min-w-[350px] max-w-[750px] flex flex-col">
|
<div class="w-full md:min-w-[350px] max-w-[750px] flex flex-col absolute left-1/2 -translate-x-1/2 bottom-5">
|
||||||
<div ref="chatWindow" class="w-full overflow-auto h-32 mb-5 bg-gray-300/80 rounded-lg border-2 border-solid border-cyan-200" v-show="gameStore.uiSettings.isChatOpen">
|
<div ref="chatWindow" class="w-full overflow-auto h-32 mb-5 bg-gray rounded-md border-2 border-solid border-gray-500 text-gray-300" v-show="gameStore.uiSettings.isChatOpen">
|
||||||
<div v-for="message in chats" class="flex-col py-2 items-center p-3">
|
<div v-for="message in chats" class="flex-col py-2 items-center p-3">
|
||||||
<span class="text-ellipsis overflow-hidden whitespace-nowrap text-sm">{{ message.character.name }}</span>
|
<span class="text-ellipsis overflow-hidden whitespace-nowrap text-sm" :class="{ 'text-cyan-300': gameStore.character?.role == 'gm' }">{{ message.character.name }}</span>
|
||||||
<p class="text-gray-50 m-0">{{ message.message }}</p>
|
<p class="text-gray-50 m-0">{{ message.message }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex">
|
<div class="w-96 mx-auto relative">
|
||||||
|
<img src="/assets/icons/chat-icon.svg" class="absolute top-1/2 -translate-y-1/2 left-2.5 h-4 w-4 opacity-50" />
|
||||||
<input
|
<input
|
||||||
class="w-full h-12 rounded-lg text-lg px-4 py-0 bg-gray border-2 border-solid border-gray-500 text-gray-300 bg-[url('/assets/icons/submit-icon.svg')] bg-no-repeat bg-[right_25px_center] bg-[length:30px] focus:outline-none focus:ring-0 focus:border-cyan-800"
|
class="w-[332px] h-8 rounded-sm text-xs font-default pl-8 pr-4 py-0 bg-gray-600 border-2 border-solid border-gray-500 text-gray-300 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover focus:outline-none focus:ring-0 focus:border-cyan-800"
|
||||||
placeholder="Type something..."
|
placeholder="Type something..."
|
||||||
v-model="message"
|
v-model="message"
|
||||||
@keypress="handleKeyPress"
|
@keypress="handleKeyPress"
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
<template>
|
<template></template>
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
@ -1,5 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div class="absolute left-[66px] top-4 bg-[url('/assets/ui-rect-border-4-corners.svg')] bg-no-repeat px-4 py-2 w-[181px] h-[26px] flex flex-col justify-between">
|
||||||
|
<div class="w-full flex items-center gap-2">
|
||||||
|
<label class="text-xs leading-3 text-pixel" for="hp">HP</label>
|
||||||
|
<progress class="h-2 rounded-sm w-full max-w-44 appearance-none accent-green" id="hp" :value="gameStore.character?.hitpoints" max="100">{{ gameStore.character?.hitpoints }}%</progress>
|
||||||
|
<span class="text-xs leading-3 text-pixel">{{ gameStore.character?.hitpoints }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex items-center gap-2">
|
||||||
|
<label class="text-xs leading-3 text-pixel" for="sp">SP</label>
|
||||||
|
<progress class="h-2 rounded-sm w-full max-w-44 appearance-none accent-blue" id="sp" :value="gameStore.character?.mana" max="100">{{ gameStore.character?.mana }}%</progress>
|
||||||
|
<span class="text-xs leading-3 text-pixel">{{ gameStore.character?.mana }}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -9,41 +20,30 @@ const gameStore = useGameStore()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.hud-wrapper {
|
#hp {
|
||||||
.hud-bg {
|
|
||||||
mask: url('/assets/shapes/hud-image-shape.svg') center/cover no-repeat;
|
|
||||||
}
|
|
||||||
#hp {
|
|
||||||
// Chrome, Safari, Edge, Opera
|
// Chrome, Safari, Edge, Opera
|
||||||
&::-webkit-progress-value {
|
&::-webkit-progress-value {
|
||||||
@apply bg-red rounded-lg;
|
@apply bg-gradient-to-r from-green from-75% to-green-200 rounded-sm;
|
||||||
}
|
}
|
||||||
&::-webkit-progress-bar {
|
&::-webkit-progress-bar {
|
||||||
@apply bg-white rounded-lg border-2 border-solid border-white;
|
@apply bg-white rounded-sm border border-solid border-black;
|
||||||
}
|
}
|
||||||
// Firefox
|
// Firefox
|
||||||
&::-moz-progress-bar {
|
&::-moz-progress-bar {
|
||||||
@apply bg-red rounded-lg border-2 border-solid border-white;
|
@apply bg-gradient-to-r from-green from-75% to-green-200 rounded-sm border border-solid border-black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#mp {
|
#sp {
|
||||||
// Chrome, Safari, Edge, Opera
|
// Chrome, Safari, Edge, Opera
|
||||||
&::-webkit-progress-value {
|
&::-webkit-progress-value {
|
||||||
@apply bg-blue rounded-lg;
|
@apply bg-gradient-to-r from-blue from-75% to-blue-200 rounded-sm;
|
||||||
}
|
}
|
||||||
&::-webkit-progress-bar {
|
&::-webkit-progress-bar {
|
||||||
@apply bg-white rounded-lg border-2 border-solid border-white;
|
@apply bg-white rounded-sm border border-solid border-black;
|
||||||
}
|
}
|
||||||
// Firefox
|
// Firefox
|
||||||
&::-moz-progress-bar {
|
&::-moz-progress-bar {
|
||||||
@apply bg-blue rounded-lg border-2 border-solid border-white;
|
@apply bg-gradient-to-r from-blue from-75% to-blue-200 rounded-sm border border-solid border-black;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.other-player {
|
|
||||||
.hud-bg {
|
|
||||||
mask: url('/assets/shapes/hud-image-shape-flipped.svg') center/cover no-repeat;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,44 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div class="absolute left-[300px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F1</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f1-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[346px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F2</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f2-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[392px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F3</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f3-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[438px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F4</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f4-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[484px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F5</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f5-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[530px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F6</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f6-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[576px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F7</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f7-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[622px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F8</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f8-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -1,39 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<ul class="list-none flex gap-2.5 items-center m-0 max-md:pl-0">
|
<ul class="list-none flex flex-col gap-2.5 items-center m-0 pl-0 absolute left-4 top-4">
|
||||||
<li class="menu-item group relative" @click="gameStore.toggleChat">
|
|
||||||
<div class="group-hover:block absolute bottom-16 left-1/2 -translate-x-1/2 w-20 h-6 text-center bg-gray-300 border-2 border-solid border-cyan rounded-3xl hidden">
|
|
||||||
<p class="absolute w-full bottom-0 m-0 text-xs leading-6">Chat</p>
|
|
||||||
<div class="group-hover:block absolute -bottom-2.5 bg-cyan h-2 w-3.5 [clip-path:polygon(100%_0,_0_0,_50%_100%)] left-1/2 -translate-x-1/2 hidden"></div>
|
|
||||||
</div>
|
|
||||||
<a class="group-hover:bg-gray/70 group-hover:cursor-pointer p-1.5 bg-gray-300/70 border-2 border-solid border-cyan-200 hover:border-cyan rounded-lg block w-11 h-9">
|
|
||||||
<img class="group-hover:drop-shadow-default w-11 h-9 object-contain" draggable="false" src="/assets/icons/chat.png" />
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="menu-item group relative">
|
<li class="menu-item group relative">
|
||||||
<div class="group-hover:block absolute bottom-16 left-1/2 -translate-x-1/2 w-20 h-6 text-center bg-gray-300 border-2 border-solid border-cyan rounded-3xl hidden">
|
<div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
|
||||||
<p class="absolute w-full bottom-0 m-0 text-xs leading-6">World</p>
|
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">Open menu</p>
|
||||||
<div class="group-hover:block absolute -bottom-2.5 bg-cyan h-2 w-3.5 [clip-path:polygon(100%_0,_0_0,_50%_100%)] left-1/2 -translate-x-1/2 hidden"></div>
|
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
<a class="group-hover:bg-gray/70 group-hover:cursor-pointer p-1.5 bg-gray-300/70 border-2 border-solid border-cyan-200 hover:border-cyan rounded-lg block w-11 h-9">
|
<a class="group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners.svg')] bg-no-repeat block w-[42px] h-[42px] relative">
|
||||||
<img class="group-hover:drop-shadow-default w-11 h-9 object-contain" draggable="false" src="/assets/icons/world.png" />
|
<img class="group-hover:drop-shadow-default w-6 h-5 mx-[9px] my-[11px] object-contain" draggable="false" src="/assets/icons/menu-icon.svg" />
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="menu-item group relative">
|
|
||||||
<div class="group-hover:block absolute bottom-16 left-1/2 -translate-x-1/2 w-20 h-6 text-center bg-gray-300 border-2 border-solid border-cyan rounded-3xl hidden">
|
|
||||||
<p class="absolute w-full bottom-0 m-0 text-xs leading-6">Users</p>
|
|
||||||
<div class="group-hover:block absolute -bottom-2.5 bg-cyan h-2 w-3.5 [clip-path:polygon(100%_0,_0_0,_50%_100%)] left-1/2 -translate-x-1/2 hidden"></div>
|
|
||||||
</div>
|
|
||||||
<a class="group-hover:bg-gray/70 group-hover:cursor-pointer p-1.5 bg-gray-300/70 border-2 border-solid border-cyan-200 hover:border-cyan rounded-lg block w-11 h-9">
|
|
||||||
<img class="group-hover:drop-shadow-default w-11 h-9 object-contain" draggable="false" src="/assets/icons/users.png" />
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="menu-item group relative" @click="gameStore.toggleUserPanel">
|
<li class="menu-item group relative" @click="gameStore.toggleUserPanel">
|
||||||
<div class="group-hover:block absolute bottom-16 left-1/2 -translate-x-1/2 w-20 h-6 text-center bg-gray-300 border-2 border-solid border-cyan rounded-3xl hidden">
|
<div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
|
||||||
<p class="absolute w-full bottom-0 m-0 text-xs leading-6">Inventory</p>
|
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">User Profile</p>
|
||||||
<div class="group-hover:block absolute -bottom-2.5 bg-cyan h-2 w-3.5 [clip-path:polygon(100%_0,_0_0,_50%_100%)] left-1/2 -translate-x-1/2 hidden"></div>
|
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
<a class="group-hover:bg-gray/70 group-hover:cursor-pointer p-1.5 bg-gray-300/70 border-2 border-solid border-cyan-200 hover:border-cyan rounded-lg block w-11 h-9">
|
<a class="group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners.svg')] bg-no-repeat block w-[42px] h-[42px] relative">
|
||||||
<img class="group-hover:drop-shadow-default w-11 h-9 object-contain" draggable="false" src="/assets/icons/treasure-chest.png" />
|
<img class="group-hover:drop-shadow-default w-8 h-8 m-[5px] object-contain" draggable="false" src="/assets/avatar/default/head.png" />
|
||||||
|
<p class="absolute bottom-0 -right-1.5 m-0 max-w-4 font-ui z-10 text-white text-[12px] leading-[6px] drop-shadow-pixel"><span class="font-ui text-white text-[8px] ml-0.5">LVL</span> {{ characterLevel }}</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="menu-item group relative" @click="gameStore.toggleChat">
|
||||||
|
<div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
|
||||||
|
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">Open Chat</p>
|
||||||
|
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
|
||||||
|
</div>
|
||||||
|
<a class="group-hover:bg-gray-800 group-hover:cursor-pointer border border-b-4 border-solid rounded border-gray-500 block w-[34px] h-[31px]">
|
||||||
|
<img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/chat-icon.svg" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="menu-item group relative">
|
||||||
|
<div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
|
||||||
|
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">World map</p>
|
||||||
|
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
|
||||||
|
</div>
|
||||||
|
<a class="group-hover:bg-gray-800 group-hover:cursor-pointer border border-b-4 border-solid rounded border-gray-500 block w-[34px] h-[31px]">
|
||||||
|
<img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/map-icon.svg" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="menu-item group relative">
|
||||||
|
<div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
|
||||||
|
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">Users</p>
|
||||||
|
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
|
||||||
|
</div>
|
||||||
|
<a class="group-hover:bg-gray-800 group-hover:cursor-pointer border border-b-4 border-solid rounded border-gray-500 block w-[34px] h-[31px]">
|
||||||
|
<img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/socials-icon.svg" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -43,4 +53,5 @@
|
|||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
|
let characterLevel = gameStore.character?.level.toString().padStart(2, '0')
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,5 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div class="absolute top-4 right-4">
|
||||||
|
<div class="w-40 h-40 rounded-full border border-solid border-gray-500 bg-[url('/assets/ui-texture.png')] bg-no-repeat">
|
||||||
|
<div class="w-40 h-40 rounded-full shadow-inner"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute -bottom-3 left-1/2 -translate-x-1/2 flex gap-1">
|
||||||
|
<button class="w-6 h-6 relative p-0">
|
||||||
|
<img class="absolute w-3 h-3 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" src="/assets/icons/plus-icon.svg" />
|
||||||
|
<img class="w-full h-full" src="/assets/ui-border-4-corners.svg" />
|
||||||
|
</button>
|
||||||
|
<button class="w-6 h-6 relative p-0">
|
||||||
|
<img class="absolute w-3 h-3 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" src="/assets/icons/minus-icon.svg" />
|
||||||
|
<img class="w-full h-full" src="/assets/ui-border-4-corners.svg" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="absolute z-50 w-full h-dvh top-0 left-0 bg-black/60" v-show="gameStore.uiSettings.isUserPanelOpen">
|
<div class="absolute z-50 w-full h-dvh top-0 left-0 bg-black/60" v-show="gameStore.uiSettings.isUserPanelOpen">
|
||||||
<div class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 max-w-[875px] max-h-[600px] h-full w-[80%] bg-gray-300/80 border-solid border-2 border-cyan-200 rounded-lg z-50 flex flex-col backdrop-blur-sm shadow-lg">
|
<div class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 max-w-[875px] max-h-[600px] h-full w-[80%] bg-gray-700 border-solid border-2 border-gray-500 rounded-md z-50 flex flex-col backdrop-blur-sm shadow-lg">
|
||||||
<div class="p-2.5 flex max-sm:flex-wrap justify-between items-center gap-5 border-solid border-0 border-b border-cyan-200">
|
<div class="p-2.5 flex max-sm:flex-wrap justify-between items-center gap-5 border-solid border-0 border-b border-gray-500">
|
||||||
<h3 class="m-0 font-medium shrink-0">Game menu</h3>
|
<h3 class="m-0 font-medium shrink-0">Game menu</h3>
|
||||||
<div class="hidden sm:flex gap-1.5 flex-wrap">
|
<div class="hidden sm:flex gap-1.5 flex-wrap">
|
||||||
<button @click.stop="userPanelScreen = 'inventory'" :class="{ active: userPanelScreen === 'inventory' }" class="btn-cyan py-1.5 px-4 min-w-24">Inventory</button>
|
<button @click.stop="userPanelScreen = 'inventory'" :class="{ active: userPanelScreen === 'inventory' }" class="btn-cyan py-1.5 px-4 min-w-24">Inventory</button>
|
||||||
@ -10,7 +10,7 @@
|
|||||||
<button @click.stop="userPanelScreen = 'settings'" :class="{ active: userPanelScreen === 'settings' }" class="btn-cyan py-1.5 px-4 min-w-24">Settings</button>
|
<button @click.stop="userPanelScreen = 'settings'" :class="{ active: userPanelScreen === 'settings' }" class="btn-cyan py-1.5 px-4 min-w-24">Settings</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2.5">
|
<div class="flex gap-2.5">
|
||||||
<button class="w-5 h-5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out" @click="gameStore.toggleUserPanel">
|
<button class="w-3.5 h-3.5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out" @click="gameStore.toggleUserPanel">
|
||||||
<img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" class="w-full h-full" />
|
<img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" class="w-full h-full" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute -left-5 -bottom-5 w-[calc(100%_+_40px)] my-px h-px bg-cyan-200"></div>
|
<div class="absolute -left-5 -bottom-5 w-[calc(100%_+_40px)] my-px h-px bg-gray-500"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<h4 class="font-medium text-lg max-w-[375px]">Character stats</h4>
|
<h4 class="font-medium text-lg max-w-[375px]">Character stats</h4>
|
||||||
|
@ -13,54 +13,54 @@
|
|||||||
<div class="flex flex-col gap-3 mx-5 mt-2">
|
<div class="flex flex-col gap-3 mx-5 mt-2">
|
||||||
<div class="flex gap-3 justify-center">
|
<div class="flex gap-3 justify-center">
|
||||||
<!-- Helmet -->
|
<!-- Helmet -->
|
||||||
<div class="bg-gray-300/80 border-solid border-2 border-cyan-200 rounded-md aspect-square h-11 w-11 relative hover:bg-gray-200">
|
<div class="bg-gray-300/80 border-solid border-2 border-gray-500 rounded-md aspect-square h-11 w-11 relative hover:bg-gray-200">
|
||||||
<img src="/assets/icons/inventory/helmet.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
<img src="/assets/icons/inventory/helmet.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Head charm -->
|
<!-- Head charm -->
|
||||||
<div class="bg-gray-300/80 border-solid border-2 border-cyan-200 rounded-md aspect-square h-11 w-11 relative hover:bg-gray-200">
|
<div class="bg-gray-300/80 border-solid border-2 border-gray-500 rounded-md aspect-square h-11 w-11 relative hover:bg-gray-200">
|
||||||
<img src="/assets/icons/inventory/head_charm.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
<img src="/assets/icons/inventory/head_charm.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-3 justify-center">
|
<div class="flex gap-3 justify-center">
|
||||||
<!-- Bracers -->
|
<!-- Bracers -->
|
||||||
<div class="bg-gray-300/80 border-solid border-2 border-cyan-200 rounded-md w-11 h-[104px] relative hover:bg-gray-200">
|
<div class="bg-gray-300/80 border-solid border-2 border-gray-500 rounded-md w-11 h-[104px] relative hover:bg-gray-200">
|
||||||
<img src="/assets/icons/inventory/bracers.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
<img src="/assets/icons/inventory/bracers.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Chestplate -->
|
<!-- Chestplate -->
|
||||||
<div class="bg-gray-300/80 border-solid border-2 border-cyan-200 rounded-md aspect-square w-[104px] h-[104px] relative hover:bg-gray-200">
|
<div class="bg-gray-300/80 border-solid border-2 border-gray-500 rounded-md aspect-square w-[104px] h-[104px] relative hover:bg-gray-200">
|
||||||
<img src="/assets/icons/inventory/chestplate.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-10/12 opacity-20" />
|
<img src="/assets/icons/inventory/chestplate.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-10/12 opacity-20" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Primary Weapon -->
|
<!-- Primary Weapon -->
|
||||||
<div class="bg-gray-300/80 border-solid border-2 border-cyan-200 rounded-md w-11 h-[104px] self-stretch justify-self-stretch relative hover:bg-gray-200">
|
<div class="bg-gray-300/80 border-solid border-2 border-gray-500 rounded-md w-11 h-[104px] self-stretch justify-self-stretch relative hover:bg-gray-200">
|
||||||
<img src="/assets/icons/inventory/primary_weapon.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
<img src="/assets/icons/inventory/primary_weapon.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-3 justify-center">
|
<div class="flex gap-3 justify-center">
|
||||||
<!-- Legs -->
|
<!-- Legs -->
|
||||||
<div class="bg-gray-300/80 border-solid border-2 border-cyan-200 rounded-md w-11 h-[104px] self-stretch justify-self-stretch relative hover:bg-gray-200">
|
<div class="bg-gray-300/80 border-solid border-2 border-gray-500 rounded-md w-11 h-[104px] self-stretch justify-self-stretch relative hover:bg-gray-200">
|
||||||
<img src="/assets/icons/inventory/legs.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
<img src="/assets/icons/inventory/legs.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<!-- Belt/pouch -->
|
<!-- Belt/pouch -->
|
||||||
<div class="bg-gray-300/80 border-solid border-2 border-cyan-200 rounded-md aspect-square h-11 w-11 self-stretch justify-self-stretch relative hover:bg-gray-200">
|
<div class="bg-gray-300/80 border-solid border-2 border-gray-500 rounded-md aspect-square h-11 w-11 self-stretch justify-self-stretch relative hover:bg-gray-200">
|
||||||
<img src="/assets/icons/inventory/pouch.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
<img src="/assets/icons/inventory/pouch.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Boots -->
|
<!-- Boots -->
|
||||||
<div class="bg-gray-300/80 border-solid border-2 border-cyan-200 rounded-md aspect-square h-11 w-11 self-stretch justify-self-stretch relative hover:bg-gray-200">
|
<div class="bg-gray-300/80 border-solid border-2 border-gray-500 rounded-md aspect-square h-11 w-11 self-stretch justify-self-stretch relative hover:bg-gray-200">
|
||||||
<img src="/assets/icons/inventory/boots.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
<img src="/assets/icons/inventory/boots.svg" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-11/12 opacity-20" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute -left-5 -bottom-5 w-[calc(100%_+_40px)] my-px h-px bg-cyan-200"></div>
|
<div class="absolute -left-5 -bottom-5 w-[calc(100%_+_40px)] my-px h-px bg-gray-500"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<h4 class="font-medium text-lg max-w-[375px]">Equipment Bonus</h4>
|
<h4 class="font-medium text-lg max-w-[375px]">Equipment Bonus</h4>
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
<div class="m-4 relative">
|
<div class="m-4 relative">
|
||||||
<h4 class="m-auto font-medium text-lg max-w-[375px]">Inventory</h4>
|
<h4 class="m-auto font-medium text-lg max-w-[375px]">Inventory</h4>
|
||||||
<div class="flex gap-3 mt-4 mx-auto flex-wrap max-w-[375px]">
|
<div class="flex gap-3 mt-4 mx-auto flex-wrap max-w-[375px]">
|
||||||
<div v-for="n in 24" class="bg-gray-300/80 border-solid border-2 border-cyan-200 w-12 h-12 rounded-md aspect-square shrink-0 justify-self-stretch hover:bg-gray-200"></div>
|
<div v-for="n in 24" class="bg-gray-300/80 border-solid border-2 border-gray-500 w-12 h-12 rounded-md aspect-square shrink-0 justify-self-stretch hover:bg-gray-200"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute -left-5 -bottom-5 w-[calc(100%_+_40px)] my-px h-px bg-cyan-200"></div>
|
<div class="absolute -left-5 -bottom-5 w-[calc(100%_+_40px)] my-px h-px bg-gray-500"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<h4 class="m-auto font-medium text-lg max-w-[375px]">Chest items</h4>
|
<h4 class="m-auto font-medium text-lg max-w-[375px]">Chest items</h4>
|
||||||
<div class="flex gap-3 mt-4 mx-auto flex-wrap max-w-[375px]">
|
<div class="flex gap-3 mt-4 mx-auto flex-wrap max-w-[375px]">
|
||||||
<div v-for="n in 12" class="bg-gray-300/80 border-solid border-2 border-cyan-200 w-12 h-12 rounded-md aspect-square shrink-0 justify-self-stretch hover:bg-gray-200"></div>
|
<div v-for="n in 12" class="bg-gray-300/80 border-solid border-2 border-gray-500 w-12 h-12 rounded-md aspect-square shrink-0 justify-self-stretch hover:bg-gray-200"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,26 +4,26 @@
|
|||||||
<!-- Settings Categories -->
|
<!-- Settings Categories -->
|
||||||
<div class="relative p-2.5">
|
<div class="relative p-2.5">
|
||||||
<h3>Settings</h3>
|
<h3>Settings</h3>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</div>
|
</div>
|
||||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': settingCategory === 'character' }" @click.stop="settingCategory = 'character'">
|
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': settingCategory === 'character' }" @click.stop="settingCategory = 'character'">
|
||||||
<span>Character</span>
|
<span>Character</span>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': settingCategory === 'account' }" @click.stop="settingCategory = 'account'">
|
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': settingCategory === 'account' }" @click.stop="settingCategory = 'account'">
|
||||||
<span>Account</span>
|
<span>Account</span>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': settingCategory === 'audio' }" @click.stop="settingCategory = 'audio'">
|
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': settingCategory === 'audio' }" @click.stop="settingCategory = 'audio'">
|
||||||
<span>Audio</span>
|
<span>Audio</span>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': settingCategory === 'video' }" @click.stop="settingCategory = 'video'">
|
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': settingCategory === 'video' }" @click.stop="settingCategory = 'video'">
|
||||||
<span>Video</span>
|
<span>Video</span>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute w-px bg-cyan-200 h-full top-0 left-1/6"></div>
|
<div class="absolute w-px bg-gray-500 h-full top-0 left-1/6"></div>
|
||||||
|
|
||||||
<!-- Assets list -->
|
<!-- Assets list -->
|
||||||
<div class="overflow-auto h-full w-10/12 flex flex-col relative">
|
<div class="overflow-auto h-full w-10/12 flex flex-col relative">
|
||||||
|
@ -38,24 +38,20 @@ const modalOpened = ref(props.modalOpened)
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal :closable="false" :is-resizable="false" :isModalOpen="true" @modal:close="() => (modalOpened = !modalOpened)" :modal-width="300" :modal-height="190">
|
<Modal :closable="false" :is-resizable="false" :isModalOpen="true" @modal:close="() => (modalOpened = !modalOpened)" :modal-width="350" :modal-height="230">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<div class="text-gray-300">
|
|
||||||
<slot name="modalHeader"></slot>
|
<slot name="modalHeader"></slot>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
<div class="text-gray-300 h-full">
|
<div class="h-[calc(100%_-_32px)] p-4">
|
||||||
<div class="flex h-full flex-col justify-between">
|
<div class="h-full flex flex-col justify-between">
|
||||||
<span class="p-2">
|
|
||||||
<slot name="modalBody"></slot>
|
<slot name="modalBody"></slot>
|
||||||
</span>
|
<div class="grid grid-flow-col justify-stretch gap-4">
|
||||||
<div class="flex justify-between p-2">
|
<button class="btn-empty py-1.5 px-4 min-w-24 inline-block" @click="props.cancelFunction()">
|
||||||
<button class="btn-cyan py-1.5 px-4 min-w-24 inline-block" @click="props.cancelFunction()">
|
|
||||||
{{ props.cancelButtonText }}
|
{{ props.cancelButtonText }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="btn-cyan py-1.5 px-4 min-w-24 inline-block" type="submit" @click="props.confirmFunction()">
|
<button class="btn-red py-1.5 px-4 min-w-24 inline-block" type="submit" @click="props.confirmFunction()">
|
||||||
{{ props.confirmButtonText }}
|
{{ props.confirmButtonText }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,23 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div v-if="isModalOpenRef" class="fixed bg-gray border-solid border-2 border-gray-500 z-50 flex flex-col backdrop-blur-sm shadow-lg" :style="modalStyle">
|
<div v-if="isModalOpenRef" class="fixed border-solid border-2 border-gray-500 z-50 flex flex-col backdrop-blur-sm shadow-lg" :style="modalStyle">
|
||||||
<div @mousedown="startDrag" class="cursor-move p-2.5 flex justify-between items-center border-solid border-0 border-b border-gray-500">
|
<div @mousedown="startDrag" class="cursor-move p-2.5 flex justify-between items-center border-solid border-0 border-b border-gray-500 relative">
|
||||||
|
<div class="rounded-t-md absolute w-full h-full top-0 left-0 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-center bg-cover opacity-90"></div>
|
||||||
|
<div class="relative z-10">
|
||||||
<slot name="modalHeader" />
|
<slot name="modalHeader" />
|
||||||
|
</div>
|
||||||
<div class="flex gap-2.5">
|
<div class="flex gap-2.5">
|
||||||
<button @click="toggleFullScreen" class="w-5 h-5 m-0 p-0 relative hover:scale-110 transition-transform duration-300 ease-in-out" v-if="canFullScreen">
|
<button @click="toggleFullScreen" class="w-5 h-5 m-0 p-0 relative hover:scale-110 transition-transform duration-300 ease-in-out" v-if="canFullScreen">
|
||||||
<img :alt="isFullScreen ? 'exit full-screen' : 'full-screen'" draggable="false" :src="isFullScreen ? '/assets/icons/minimize.svg' : '/assets/icons/full-screen.svg'" class="w-full h-full invert" />
|
<img :alt="isFullScreen ? 'exit full-screen' : 'full-screen'" draggable="false" :src="isFullScreen ? '/assets/icons/minimize.svg' : '/assets/icons/full-screen.svg'" class="w-3.5 h-3.5 invert" />
|
||||||
</button>
|
</button>
|
||||||
<button @click="close" v-if="closable" class="w-5 h-5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out">
|
<button @click="close" v-if="closable" 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" draggable="false" src="/assets/icons/close-button-white.svg" class="w-full h-full" />
|
<img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" class="w-full h-full" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-hidden grow">
|
<div class="overflow-hidden grow relative">
|
||||||
|
<div class="rounded-b-md absolute w-full h-full top-0 left-0 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover bg-center opacity-90"></div>
|
||||||
|
<div class="relative z-10 h-full">
|
||||||
<slot name="modalBody" />
|
<slot name="modalBody" />
|
||||||
<img v-if="isResizable && !isFullScreen" src="/assets/icons/resize-icon.svg" alt="resize" class="absolute bottom-0 right-0 w-5 h-5 cursor-nwse-resize invert-[60%]" @mousedown="startResize" />
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="$slots.modalFooter" class="px-5 min-h-12 flex justify-end gap-7.5 items-center border-solid border-t border-gray-500">
|
<img v-if="isResizable && !isFullScreen" src="/assets/icons/resize-icon.svg" alt="resize" class="absolute z-10 bottom-0 right-0 w-5 h-5 cursor-nwse-resize" @mousedown="startResize" />
|
||||||
<slot name="modalFooter" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@ -84,7 +87,7 @@ let startHeight = 0
|
|||||||
let preFullScreenState = { x: 0, y: 0, width: 0, height: 0 }
|
let preFullScreenState = { x: 0, y: 0, width: 0, height: 0 }
|
||||||
|
|
||||||
const modalStyle = computed(() => ({
|
const modalStyle = computed(() => ({
|
||||||
borderRadius: isFullScreen.value ? '0' : '10px',
|
borderRadius: isFullScreen.value ? '0' : '6px',
|
||||||
top: isFullScreen.value ? '0' : `${y.value}px`,
|
top: isFullScreen.value ? '0' : `${y.value}px`,
|
||||||
left: isFullScreen.value ? '0' : `${x.value}px`,
|
left: isFullScreen.value ? '0' : `${x.value}px`,
|
||||||
width: isFullScreen.value ? '100vw' : `${width.value}px`,
|
width: isFullScreen.value ? '100vw' : `${width.value}px`,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal v-for="notification in gameStore.getNotifications" :key="notification.id" :isModalOpen="true" @modal:close="closeNotification(notification.id)">
|
<Modal v-for="notification in gameStore.getNotifications" :key="notification.id" :isModalOpen="true" @modal:close="closeNotification(notification.id)">
|
||||||
<template #modalHeader v-if="notification.title">
|
<template #modalHeader v-if="notification.title">
|
||||||
<h3 class="m-0 font-medium shrink-0 text-gray-300">{{ notification.title }}</h3>
|
<h3 class="m-0 font-medium shrink-0 text-white">{{ notification.title }}</h3>
|
||||||
</template>
|
</template>
|
||||||
<template #modalBody v-if="notification.message">
|
<template #modalBody v-if="notification.message">
|
||||||
<p class="m-4">{{ notification.message }}</p>
|
<p class="m-4">{{ notification.message }}</p>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Image v-for="object in zoneStore.zone?.zoneObjects" :depth="calculateIsometricDepth(object.positionX, object.positionY, object.object.frameWidth, object.object.frameHeight)" :key="object.id" v-bind="getObjectImageProps(object)" :flipX="object.isRotated" />
|
<Image v-for="object in zoneStore.zone?.zoneObjects" v-bind="getObjectImageProps(object)" />
|
||||||
<!-- <Text v-for="object in zoneStore.zone?.zoneObjects" :key="object.id" :depth="99999" :text="Math.ceil(calculateIsometricDepth(object.positionX, object.positionY, object.object.frameWidth, object.object.frameHeight))" v-bind="getObjectProps(object)" />-->
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -15,19 +14,12 @@ const props = defineProps<{
|
|||||||
tilemap: Phaser.Tilemaps.Tilemap
|
tilemap: Phaser.Tilemaps.Tilemap
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const getObjectProps = (object: ZoneObject) => {
|
|
||||||
return {
|
|
||||||
x: tileToWorldX(props.tilemap as any, object.positionX, object.positionY),
|
|
||||||
y: tileToWorldY(props.tilemap as any, object.positionX, object.positionY),
|
|
||||||
originY: Number(object.object.originX),
|
|
||||||
originX: Number(object.object.originY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getObjectImageProps = (object: ZoneObject) => {
|
const getObjectImageProps = (object: ZoneObject) => {
|
||||||
return {
|
return {
|
||||||
|
depth: calculateIsometricDepth(object.positionX, object.positionY, object.object.frameWidth, object.object.frameHeight),
|
||||||
x: tileToWorldX(props.tilemap as any, object.positionX, object.positionY),
|
x: tileToWorldX(props.tilemap as any, object.positionX, object.positionY),
|
||||||
y: tileToWorldY(props.tilemap as any, object.positionX, object.positionY),
|
y: tileToWorldY(props.tilemap as any, object.positionX, object.positionY),
|
||||||
|
flipX: object.isRotated,
|
||||||
texture: object.object.id,
|
texture: object.object.id,
|
||||||
originY: Number(object.object.originX),
|
originY: Number(object.object.originX),
|
||||||
originX: Number(object.object.originY)
|
originX: Number(object.object.originY)
|
||||||
|
@ -52,16 +52,15 @@ function createTileLayer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createTileArray() {
|
function createTileArray() {
|
||||||
return Array.from({ length: zoneStore.zone?.width ?? 0 }, () => Array.from({ length: zoneStore.zone?.height ?? 0 }, () => 'blank_tile'))
|
return Array.from({ length: zoneTilemap.height || 0 }, () => Array.from({ length: zoneTilemap.width || 0 }, () => 'blank_tile'))
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
if (zoneStore.zone?.tiles) {
|
if (!zoneStore.zone?.tiles) {
|
||||||
|
return
|
||||||
|
}
|
||||||
setAllTiles(zoneTilemap, tiles, zoneStore.zone.tiles)
|
setAllTiles(zoneTilemap, tiles, zoneStore.zone.tiles)
|
||||||
tileArray = zoneStore.zone.tiles.map((row) => row.map((tileId) => tileId || 'blank_tile'))
|
tileArray = zoneStore.zone.tiles.map((row) => row.map((tileId) => tileId || 'blank_tile'))
|
||||||
} else {
|
|
||||||
tileArray.forEach((row, y) => row.forEach((_, x) => placeTile(zoneTilemap, tiles, x, y, 'blank_tile')))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
@ -9,7 +9,7 @@ export function useGamePointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilema
|
|||||||
const dragThreshold = 5 // pixels
|
const dragThreshold = 5 // pixels
|
||||||
|
|
||||||
function updateWaypoint(worldX: number, worldY: number) {
|
function updateWaypoint(worldX: number, worldY: number) {
|
||||||
const pointerTile = getTile(worldX, worldY, layer)
|
const pointerTile = getTile(layer, worldX, worldY)
|
||||||
if (pointerTile) {
|
if (pointerTile) {
|
||||||
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
|
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
|
||||||
waypoint.value = {
|
waypoint.value = {
|
||||||
@ -46,7 +46,7 @@ export function useGamePointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilema
|
|||||||
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 (distance <= dragThreshold) {
|
if (distance <= dragThreshold) {
|
||||||
const pointerTile = getTile(pointer.worldX, pointer.worldY, layer)
|
const pointerTile = getTile(layer, pointer.worldX, pointer.worldY)
|
||||||
if (pointerTile) {
|
if (pointerTile) {
|
||||||
gameStore.connection?.emit('character:initMove', {
|
gameStore.connection?.emit('character:initMove', {
|
||||||
positionX: pointerTile.x,
|
positionX: pointerTile.x,
|
||||||
|
@ -11,7 +11,7 @@ export function useZoneEditorPointerHandlers(scene: Phaser.Scene, layer: Phaser.
|
|||||||
|
|
||||||
function updateWaypoint(pointer: Phaser.Input.Pointer) {
|
function updateWaypoint(pointer: Phaser.Input.Pointer) {
|
||||||
const { x: px, y: py } = camera.getWorldPoint(pointer.x, pointer.y)
|
const { x: px, y: py } = camera.getWorldPoint(pointer.x, pointer.y)
|
||||||
const pointerTile = getTile(px, py, layer)
|
const pointerTile = getTile(layer, px, py)
|
||||||
|
|
||||||
if (pointerTile) {
|
if (pointerTile) {
|
||||||
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
|
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
|
||||||
|
@ -2,15 +2,16 @@ import config from '@/config'
|
|||||||
import Tilemap = Phaser.Tilemaps.Tilemap
|
import Tilemap = Phaser.Tilemaps.Tilemap
|
||||||
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
|
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
|
||||||
import Tileset = Phaser.Tilemaps.Tileset
|
import Tileset = Phaser.Tilemaps.Tileset
|
||||||
|
import Tile = Phaser.Tilemaps.Tile
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
|
||||||
export function getTile(x: number, y: number, layer: Phaser.Tilemaps.TilemapLayer): Phaser.Tilemaps.Tile | undefined {
|
export function getTile(layer: TilemapLayer | Tilemap, x: number, y: number): Tile | undefined {
|
||||||
const tile: Phaser.Tilemaps.Tile = layer.getTileAtWorldXY(x, y)
|
const tile = layer.getTileAtWorldXY(x, y)
|
||||||
if (!tile) return undefined
|
if (!tile) return undefined
|
||||||
return tile
|
return tile
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tileToWorldXY(layer: Phaser.Tilemaps.TilemapLayer, pos_x: number, pos_y: number) {
|
export function tileToWorldXY(layer: TilemapLayer, pos_x: number, pos_y: number) {
|
||||||
const worldPoint = layer.tileToWorldXY(pos_x, pos_y)
|
const worldPoint = layer.tileToWorldXY(pos_x, pos_y)
|
||||||
const positionX = worldPoint.x + config.tile_size.y
|
const positionX = worldPoint.x + config.tile_size.y
|
||||||
const positionY = worldPoint.y
|
const positionY = worldPoint.y
|
||||||
@ -18,7 +19,7 @@ export function tileToWorldXY(layer: Phaser.Tilemaps.TilemapLayer, pos_x: number
|
|||||||
return { positionX, positionY }
|
return { positionX, positionY }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tileToWorldX(layer: Phaser.Tilemaps.TilemapLayer, pos_x: number, pos_y: number): number {
|
export function tileToWorldX(layer: TilemapLayer, pos_x: number, pos_y: number): number {
|
||||||
const worldPoint = layer.tileToWorldXY(pos_x, pos_y)
|
const worldPoint = layer.tileToWorldXY(pos_x, pos_y)
|
||||||
return worldPoint.x + config.tile_size.x / 2
|
return worldPoint.x + config.tile_size.x / 2
|
||||||
}
|
}
|
||||||
@ -35,8 +36,8 @@ export function placeTile(zone: Tilemap, layer: TilemapLayer, x: number, y: numb
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setAllTiles(zone: Tilemap, layer: TilemapLayer, tiles: string[][]) {
|
export function setAllTiles(zone: Tilemap, layer: TilemapLayer, tiles: string[][]) {
|
||||||
tiles.forEach((row, y) => {
|
tiles.forEach((row: string[], y: number) => {
|
||||||
row.forEach((tile, x) => {
|
row.forEach((tile: string, x: number) => {
|
||||||
placeTile(zone, layer, x, y, tile)
|
placeTile(zone, layer, x, y, tile)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -58,6 +59,8 @@ export const sortByIsometricDepth = <T extends { positionX: number; positionY: n
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const clearAssets = (scene: Phaser.Scene) => {}
|
||||||
|
|
||||||
export const loadAssets = (scene: Phaser.Scene): Promise<void> => {
|
export const loadAssets = (scene: Phaser.Scene): Promise<void> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
|
@ -5,14 +5,9 @@
|
|||||||
<div class="filler"></div>
|
<div class="filler"></div>
|
||||||
<div class="flex gap-14 w-full max-h-[650px] overflow-x-auto" v-if="!isLoading">
|
<div class="flex gap-14 w-full max-h-[650px] overflow-x-auto" v-if="!isLoading">
|
||||||
<!-- CHARACTER LIST -->
|
<!-- CHARACTER LIST -->
|
||||||
<div
|
<div v-for="character in characters" :key="character.id" class="group first:ml-auto last:mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 relative shadow-character" :class="{ active: selected_character == character.id }">
|
||||||
v-for="character in characters"
|
<img src="/assets/ui-box-outer.svg" class="absolute w-full h-full" />
|
||||||
:key="character.id"
|
<img src="/assets/ui-box-inner.svg" class="absolute left-2 bottom-2 w-[calc(100%_-_16px)] h-[calc(100%_-_40px)]" />
|
||||||
class="group first:ml-auto last:mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 relative shadow-character"
|
|
||||||
:class="{ active: selected_character == character.id }"
|
|
||||||
>
|
|
||||||
<img src="/assets/login/login-box-outer.svg" class="absolute w-full h-full max-lg:hidden" />
|
|
||||||
<img src="/assets/login/login-box-inner.svg" class="absolute left-2 bottom-2 w-[calc(100%_-_16px)] h-[calc(100%_-_40px)] max-lg:hidden" />
|
|
||||||
<input class="opacity-0 h-full w-full absolute m-0 z-10" type="radio" :id="character.id" name="character" :value="character.id" v-model="selected_character" />
|
<input class="opacity-0 h-full w-full absolute m-0 z-10" type="radio" :id="character.id" name="character" :value="character.id" v-model="selected_character" />
|
||||||
<label class="font-bold absolute left-1/2 top-4 max-w-32 -translate-x-1/2 -translate-y-1/2 text-center text-ellipsis overflow-hidden whitespace-nowrap drop-shadow-text" :for="character.id">{{ character.name }}</label>
|
<label class="font-bold absolute left-1/2 top-4 max-w-32 -translate-x-1/2 -translate-y-1/2 text-center text-ellipsis overflow-hidden whitespace-nowrap drop-shadow-text" :for="character.id">{{ character.name }}</label>
|
||||||
|
|
||||||
@ -66,21 +61,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CREATE CHARACTER MODAL -->
|
<!-- CREATE CHARACTER MODAL -->
|
||||||
<Modal :isModalOpen="isModalOpen" @modal:close="isModalOpen = false">
|
<Modal :isModalOpen="isModalOpen" @modal:close="isModalOpen = false" :modal-width="430" :modal-height="275">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium text-gray-300">Create your character</h3>
|
<h3 class="m-0 font-medium text-white">Create your character</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
<div class="m-4 character-form">
|
<div class="p-4 h-[calc(100%_-_32px)]">
|
||||||
<form method="post" @submit.prevent="create" class="inline">
|
<form method="post" @submit.prevent="create" class="h-full flex flex-col justify-between">
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="name">Name</label>
|
<label for="name" class="text-white">Nickname</label>
|
||||||
<input class="input-field max-w-64" v-model="name" name="name" id="name" />
|
<input class="input-field" v-model="name" name="name" id="name" placeholder="Enter a nickname.." />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-flow-col justify-stretch gap-4">
|
||||||
|
<button type="button" class="btn-empty py-1.5 px-4 inline-block" @click.prevent="isModalOpen = false">Cancel</button>
|
||||||
|
<button class="btn-cyan py-1.5 px-4 inline-block" type="submit">Create</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-cyan py-1.5 px-4 mr-5 min-w-24 inline-block" type="submit">CREATE</button>
|
|
||||||
</form>
|
</form>
|
||||||
<button class="btn-cyan py-1.5 px-4 min-w-24 inline-block" @click="isModalOpen = false">CANCEL</button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -88,11 +85,13 @@
|
|||||||
<!-- DELETE CHARACTER MODAL -->
|
<!-- DELETE CHARACTER MODAL -->
|
||||||
<ConfirmationModal v-if="deletingCharacter != null" :confirm-function="delete_character.bind(this, deletingCharacter.id)" :cancel-function="(() => (deletingCharacter = null)).bind(this)" confirm-button-text="Delete">
|
<ConfirmationModal v-if="deletingCharacter != null" :confirm-function="delete_character.bind(this, deletingCharacter.id)" :cancel-function="(() => (deletingCharacter = null)).bind(this)" confirm-button-text="Delete">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium text-gray-300">Deleting character</h3>
|
<h3 class="m-0 font-medium text-white">Delete character?</h3>
|
||||||
</template>
|
</template>
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
You are about to delete <span class="font-extrabold">{{ deletingCharacter.name }}</span
|
<p class="mt-0 mb-5 text-white text-lg">
|
||||||
>, are you sure about that?
|
Do you want to permanently delete <span class="font-extrabold text-white">{{ deletingCharacter.name }}</span
|
||||||
|
>?
|
||||||
|
</p>
|
||||||
</template>
|
</template>
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
</template>
|
</template>
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
<GmTools v-if="gameStore.character?.role === 'gm'" />
|
<GmTools v-if="gameStore.character?.role === 'gm'" />
|
||||||
<GmPanel v-if="gameStore.character?.role === 'gm'" />
|
<GmPanel v-if="gameStore.character?.role === 'gm'" />
|
||||||
|
|
||||||
<div v-if="!zoneEditorStore.active">
|
|
||||||
<Game :config="gameConfig" @create="createGame">
|
<Game :config="gameConfig" @create="createGame">
|
||||||
<Scene name="main" @preload="preloadScene" @create="createScene">
|
<Scene name="main" @preload="preloadScene" @create="createScene">
|
||||||
<div v-if="isLoaded">
|
<div v-if="isLoaded">
|
||||||
@ -13,30 +12,22 @@
|
|||||||
<Minimap />
|
<Minimap />
|
||||||
<Zone />
|
<Zone />
|
||||||
<Chat />
|
<Chat />
|
||||||
<Inventory />
|
|
||||||
<ExpBar />
|
<ExpBar />
|
||||||
|
|
||||||
|
<Inventory />
|
||||||
<Effects />
|
<Effects />
|
||||||
</div>
|
</div>
|
||||||
</Scene>
|
</Scene>
|
||||||
</Game>
|
</Game>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="zoneEditorStore.active">
|
|
||||||
<Game :config="gameConfig" @create="createGame">
|
|
||||||
<Scene name="main" @preload="preloadScene" @create="createScene">
|
|
||||||
<ZoneEditor v-if="isLoaded" :key="JSON.stringify(`${zoneEditorStore.zone?.id}_${zoneEditorStore.zone?.createdAt}_${zoneEditorStore.zone?.updatedAt}`)" />
|
|
||||||
</Scene>
|
|
||||||
</Game>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import config from '@/config'
|
||||||
import 'phaser'
|
import 'phaser'
|
||||||
import { ref, onBeforeUnmount } from 'vue'
|
import { ref, onBeforeUnmount } from 'vue'
|
||||||
import { Game, Scene } from 'phavuer'
|
import { Game, Scene } from 'phavuer'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
|
||||||
import Menu from '@/components/gui/Menu.vue'
|
import Menu from '@/components/gui/Menu.vue'
|
||||||
import ExpBar from '@/components/gui/ExpBar.vue'
|
import ExpBar from '@/components/gui/ExpBar.vue'
|
||||||
import Hud from '@/components/gui/Hud.vue'
|
import Hud from '@/components/gui/Hud.vue'
|
||||||
@ -44,20 +35,17 @@ import Zone from '@/components/zone/Zone.vue'
|
|||||||
import Keybindings from '@/components/gui/Keybindings.vue'
|
import Keybindings from '@/components/gui/Keybindings.vue'
|
||||||
import Chat from '@/components/gui/Chat.vue'
|
import Chat from '@/components/gui/Chat.vue'
|
||||||
import GmTools from '@/components/gameMaster/GmTools.vue'
|
import GmTools from '@/components/gameMaster/GmTools.vue'
|
||||||
import ZoneEditor from '@/components/gameMaster/zoneEditor/ZoneEditor.vue'
|
|
||||||
import GmPanel from '@/components/gameMaster/GmPanel.vue'
|
import GmPanel from '@/components/gameMaster/GmPanel.vue'
|
||||||
import Inventory from '@/components/gui/UserPanel.vue'
|
import Inventory from '@/components/gui/UserPanel.vue'
|
||||||
import Effects from '@/components/Effects.vue'
|
import Effects from '@/components/Effects.vue'
|
||||||
import { loadAssets } from '@/composables/zoneComposable'
|
import { loadAssets } from '@/composables/zoneComposable'
|
||||||
import Minimap from '@/components/gui/Minimap.vue'
|
import Minimap from '@/components/gui/Minimap.vue'
|
||||||
|
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const zoneEditorStore = useZoneEditorStore()
|
|
||||||
const isLoaded = ref(false)
|
const isLoaded = ref(false)
|
||||||
|
|
||||||
const gameConfig = {
|
const gameConfig = {
|
||||||
name: 'New Quest',
|
name: config.name,
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
type: Phaser.AUTO, // AUTO, CANVAS, WEBGL, HEADLESS
|
type: Phaser.AUTO, // AUTO, CANVAS, WEBGL, HEADLESS
|
||||||
@ -154,7 +142,6 @@ const createScene = async (scene: Phaser.Scene) => {
|
|||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
isLoaded.value = false
|
isLoaded.value = false
|
||||||
gameStore.disconnectSocket()
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -4,23 +4,23 @@
|
|||||||
<div class="bg-[url('/assets/login/login-bg.png')] w-full lg:w-1/2 h-[35dvh] lg:h-dvh absolute right-0 max-lg:bottom-0 lg:top-0 bg-no-repeat bg-cover bg-center"></div>
|
<div class="bg-[url('/assets/login/login-bg.png')] w-full lg:w-1/2 h-[35dvh] lg:h-dvh absolute right-0 max-lg:bottom-0 lg:top-0 bg-no-repeat bg-cover bg-center"></div>
|
||||||
<div class="bg-gray-900 z-20 w-full lg:w-1/2 h-[65dvh] lg:h-dvh relative">
|
<div class="bg-gray-900 z-20 w-full lg:w-1/2 h-[65dvh] lg:h-dvh relative">
|
||||||
<div class="h-dvh flex items-center lg:justify-center flex-col px-8 max-lg:pt-20">
|
<div class="h-dvh flex items-center lg:justify-center flex-col px-8 max-lg:pt-20">
|
||||||
<img src="/assets/login/nq-logo-v1.png" class="mb-10" />
|
<img src="/assets/login/sq-logo-v1.svg" class="mb-10" />
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<img src="/assets/login/login-box-outer.svg" class="absolute w-full h-full max-lg:hidden" />
|
<img src="/assets/ui-box-outer.svg" class="absolute w-full h-full" />
|
||||||
<img src="/assets/login/login-box-inner.svg" class="absolute left-2 top-2 w-[calc(100%_-_16px)] h-[calc(100%_-_16px)] max-lg:hidden" />
|
<img src="/assets/ui-box-inner.svg" class="absolute left-2 top-2 w-[calc(100%_-_16px)] h-[calc(100%_-_16px)] max-lg:hidden" />
|
||||||
|
|
||||||
<!-- Login Form -->
|
<!-- Login Form -->
|
||||||
<form v-show="switchForm === 'login'" @submit.prevent="loginFunc" class="relative px-6 py-11">
|
<form v-show="switchForm === 'login'" @submit.prevent="loginFunc" class="relative px-6 py-11">
|
||||||
<div class="flex flex-col gap-5 p-2 mb-8 relative">
|
<div class="flex flex-col gap-5 p-2 mb-8 relative">
|
||||||
<div class="w-full grid gap-3 relative">
|
<div class="w-full grid gap-3 relative">
|
||||||
<input class="input-field xs:min-w-[350px] min-w-64" id="username" v-model="username" type="text" name="username" placeholder="Username" required autofocus />
|
<input class="input-field xs:min-w-[350px] min-w-64" id="username-login" v-model="username" type="text" name="username" placeholder="Username" required autofocus />
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input class="input-field xs:min-w-[350px] min-w-64" id="password" v-model="password" type="password" name="password" placeholder="Password" required />
|
<input class="input-field xs:min-w-[350px] min-w-64" id="password-login" v-model="password" :type="showPassword ? 'text' : 'password'" name="password" placeholder="Password" required />
|
||||||
<button class="p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-[url('/assets/icons/eye.svg')] bg-no-repeat"></button>
|
<button type="button" @click.prevent="showPassword = !showPassword" :class="{ 'eye-open': showPassword }" class="bg-[url('/assets/icons/eye.svg')] p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-no-repeat"></button>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span>
|
<span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="text-right text-cyan-300 text-base">Forgot password?</button>
|
<button class="inline-flex self-end p-0 text-cyan-300 text-base">Forgot password?</button>
|
||||||
<button class="btn-cyan px-0 xs:w-full" type="submit">Play now</button>
|
<button class="btn-cyan px-0 xs:w-full" type="submit">Play now</button>
|
||||||
|
|
||||||
<!-- Divider shape -->
|
<!-- Divider shape -->
|
||||||
@ -39,10 +39,10 @@
|
|||||||
<form v-show="switchForm === 'register'" @submit.prevent="registerFunc" class="relative px-6 py-11">
|
<form v-show="switchForm === 'register'" @submit.prevent="registerFunc" class="relative px-6 py-11">
|
||||||
<div class="flex flex-col gap-5 p-2 mb-8 relative">
|
<div class="flex flex-col gap-5 p-2 mb-8 relative">
|
||||||
<div class="w-full grid gap-3 relative">
|
<div class="w-full grid gap-3 relative">
|
||||||
<input class="input-field xs:min-w-[350px] min-w-64" id="username" v-model="username" type="text" name="username" placeholder="Username" required autofocus />
|
<input class="input-field xs:min-w-[350px] min-w-64" id="username-register" v-model="username" type="text" name="username" placeholder="Username" required autofocus />
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input class="input-field xs:min-w-[350px] min-w-64" id="password" v-model="password" type="password" name="password" placeholder="Password" required />
|
<input class="input-field xs:min-w-[350px] min-w-64" id="password-register" v-model="password" :type="showPassword ? 'text' : 'password'" name="password" placeholder="Password" required />
|
||||||
<button class="p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-[url('/assets/icons/eye.svg')] bg-no-repeat"></button>
|
<button type="button" @click.prevent="showPassword = !showPassword" :class="{ 'eye-open': showPassword }" class="bg-[url('/assets/icons/eye.svg')] p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-no-repeat"></button>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span>
|
<span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -76,6 +76,8 @@ const username = ref('')
|
|||||||
const password = ref('')
|
const password = ref('')
|
||||||
const switchForm = ref('login')
|
const switchForm = ref('login')
|
||||||
const loginError = ref('')
|
const loginError = ref('')
|
||||||
|
const showPassword = ref(false)
|
||||||
|
|
||||||
// automatic login because of development
|
// automatic login because of development
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const token = useCookies().get('token')
|
const token = useCookies().get('token')
|
||||||
|
@ -1 +0,0 @@
|
|||||||
<template></template>
|
|
137
src/screens/ZoneEditor.vue
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex justify-center items-center h-dvh relative">
|
||||||
|
<GmTools v-if="gameStore.character?.role === 'gm'" />
|
||||||
|
<GmPanel v-if="gameStore.character?.role === 'gm'" />
|
||||||
|
|
||||||
|
<Game :config="gameConfig" @create="createGame">
|
||||||
|
<Scene name="main" @preload="preloadScene" @create="createScene">
|
||||||
|
<ZoneEditor v-if="isLoaded" :key="JSON.stringify(`${zoneEditorStore.zone?.id}_${zoneEditorStore.zone?.createdAt}_${zoneEditorStore.zone?.updatedAt}`)" />
|
||||||
|
</Scene>
|
||||||
|
</Game>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import config from '@/config'
|
||||||
|
import 'phaser'
|
||||||
|
import { ref, onBeforeUnmount } from 'vue'
|
||||||
|
import { Game, Scene } from 'phavuer'
|
||||||
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||||
|
import GmTools from '@/components/gameMaster/GmTools.vue'
|
||||||
|
import ZoneEditor from '@/components/gameMaster/zoneEditor/ZoneEditor.vue'
|
||||||
|
import GmPanel from '@/components/gameMaster/GmPanel.vue'
|
||||||
|
import { loadAssets } from '@/composables/zoneComposable'
|
||||||
|
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
|
const isLoaded = ref(false)
|
||||||
|
|
||||||
|
const gameConfig = {
|
||||||
|
name: config.name,
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
type: Phaser.AUTO, // AUTO, CANVAS, WEBGL, HEADLESS
|
||||||
|
resolution: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
const createGame = (game: Phaser.Game) => {
|
||||||
|
/**
|
||||||
|
* Resize the game when the window is resized
|
||||||
|
*/
|
||||||
|
addEventListener('resize', () => {
|
||||||
|
game.scale.resize(window.innerWidth, window.innerHeight)
|
||||||
|
})
|
||||||
|
|
||||||
|
// We don't support canvas mode, only WebGL
|
||||||
|
if (game.renderer.type === Phaser.CANVAS) {
|
||||||
|
gameStore.addNotification({
|
||||||
|
title: 'Warning',
|
||||||
|
message: 'Your browser does not support WebGL. Please use a modern browser like Chrome, Firefox, or Edge.'
|
||||||
|
})
|
||||||
|
gameStore.disconnectSocket()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const preloadScene = async (scene: Phaser.Scene) => {
|
||||||
|
isLoaded.value = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create loading bar
|
||||||
|
*/
|
||||||
|
const width = scene.cameras.main.width
|
||||||
|
const height = scene.cameras.main.height
|
||||||
|
|
||||||
|
const progressBox = scene.add.graphics()
|
||||||
|
const progressBar = scene.add.graphics()
|
||||||
|
progressBox.fillStyle(0x222222, 0.8)
|
||||||
|
progressBox.fillRect(width / 2 - 180, height / 2, 320, 50)
|
||||||
|
|
||||||
|
const loadingText = scene.make.text({
|
||||||
|
x: width / 2,
|
||||||
|
y: height / 2 - 50,
|
||||||
|
text: 'Loading...',
|
||||||
|
style: {
|
||||||
|
font: '20px monospace',
|
||||||
|
fill: '#ffffff'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
loadingText.setOrigin(0.5, 0.5)
|
||||||
|
|
||||||
|
scene.load.on(Phaser.Loader.Events.PROGRESS, function (value: any) {
|
||||||
|
progressBar.clear()
|
||||||
|
progressBar.fillStyle(0x368f8b, 1)
|
||||||
|
progressBar.fillRect(width / 2 - 180 + 10, height / 2 + 10, 300 * value, 30)
|
||||||
|
})
|
||||||
|
|
||||||
|
scene.load.on(Phaser.Loader.Events.COMPLETE, function () {
|
||||||
|
progressBar.destroy()
|
||||||
|
progressBox.destroy()
|
||||||
|
loadingText.destroy()
|
||||||
|
isLoaded.value = true
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the base assets into the Phaser scene
|
||||||
|
*/
|
||||||
|
scene.load.image('BLOCK', '/assets/zone/bt_tile.png')
|
||||||
|
scene.load.image('TELEPORT', '/assets/zone/tp_tile.png')
|
||||||
|
scene.load.image('blank_tile', '/assets/zone/blank_tile.png')
|
||||||
|
scene.load.image('blank_object', '/assets/zone/blank_tile.png')
|
||||||
|
scene.load.image('waypoint', '/assets/waypoint.png')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the assets into the Phaser scene
|
||||||
|
*/
|
||||||
|
await loadAssets(scene)
|
||||||
|
}
|
||||||
|
|
||||||
|
const createScene = async (scene: Phaser.Scene) => {
|
||||||
|
/**
|
||||||
|
* Create sprite animations
|
||||||
|
* This is done here because phaser forces us to
|
||||||
|
*/
|
||||||
|
gameStore.assets.forEach((asset) => {
|
||||||
|
if (asset.group !== 'sprite_animations') return
|
||||||
|
|
||||||
|
scene.anims.create({
|
||||||
|
key: asset.key,
|
||||||
|
frameRate: 7,
|
||||||
|
frames: scene.anims.generateFrameNumbers(asset.key, { start: 0, end: asset.frameCount! - 1 }),
|
||||||
|
repeat: -1
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
isLoaded.value = false
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
canvas {
|
||||||
|
image-rendering: -moz-crisp-edges;
|
||||||
|
image-rendering: -webkit-crisp-edges;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
</style>
|
@ -18,6 +18,7 @@ export async function login(username: string, password: string, gameStore = useG
|
|||||||
const response = await axios.post(`${config.server_endpoint}/login`, { username, password })
|
const response = await axios.post(`${config.server_endpoint}/login`, { username, password })
|
||||||
useCookies().set('token', response.data.token as string, {
|
useCookies().set('token', response.data.token as string, {
|
||||||
// for whole domain
|
// for whole domain
|
||||||
|
// @TODO : #190
|
||||||
domain: window.location.hostname.split('.').slice(-2).join('.')
|
domain: window.location.hostname.split('.').slice(-2).join('.')
|
||||||
})
|
})
|
||||||
return { success: true, token: response.data.token }
|
return { success: true, token: response.data.token }
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { io, Socket } from 'socket.io-client'
|
import { io, Socket } from 'socket.io-client'
|
||||||
import type { Asset, Character, Notification, User } from '@/types'
|
import type { Asset, Character, Notification, User, WorldSettings } from '@/types'
|
||||||
import config from '@/config'
|
import config from '@/config'
|
||||||
import { useCookies } from '@vueuse/integrations/useCookies'
|
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||||
|
|
||||||
@ -15,6 +15,12 @@ export const useGameStore = defineStore('game', {
|
|||||||
user: null as User | null,
|
user: null as User | null,
|
||||||
character: null as Character | null,
|
character: null as Character | null,
|
||||||
isPlayerDraggingCamera: false,
|
isPlayerDraggingCamera: false,
|
||||||
|
world: {
|
||||||
|
date: new Date(),
|
||||||
|
isRainEnabled: false,
|
||||||
|
isFogEnabled: false,
|
||||||
|
fogDensity: 0.5
|
||||||
|
} as WorldSettings,
|
||||||
gameSettings: {
|
gameSettings: {
|
||||||
isCameraFollowingCharacter: false
|
isCameraFollowingCharacter: false
|
||||||
},
|
},
|
||||||
@ -150,6 +156,7 @@ export const useGameStore = defineStore('game', {
|
|||||||
|
|
||||||
useCookies().remove('token', {
|
useCookies().remove('token', {
|
||||||
// for whole domain
|
// for whole domain
|
||||||
|
// @TODO : #190
|
||||||
domain: window.location.hostname.split('.').slice(-2).join('.')
|
domain: window.location.hostname.split('.').slice(-2).join('.')
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -163,6 +170,11 @@ export const useGameStore = defineStore('game', {
|
|||||||
this.gameSettings.isCameraFollowingCharacter = false
|
this.gameSettings.isCameraFollowingCharacter = false
|
||||||
this.uiSettings.isChatOpen = false
|
this.uiSettings.isChatOpen = false
|
||||||
this.uiSettings.isUserPanelOpen = false
|
this.uiSettings.isUserPanelOpen = false
|
||||||
|
|
||||||
|
this.world.date = new Date()
|
||||||
|
this.world.isRainEnabled = false
|
||||||
|
this.world.isFogEnabled = false
|
||||||
|
this.world.fogDensity = 0.5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -12,7 +12,7 @@ export type TeleportSettings = {
|
|||||||
export const useZoneEditorStore = defineStore('zoneEditor', {
|
export const useZoneEditorStore = defineStore('zoneEditor', {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
active: false,
|
active: true,
|
||||||
zone: null as Zone | null,
|
zone: null as Zone | null,
|
||||||
tool: 'move',
|
tool: 'move',
|
||||||
drawMode: 'tile',
|
drawMode: 'tile',
|
||||||
@ -22,7 +22,6 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
|
|||||||
objectList: [] as Object[],
|
objectList: [] as Object[],
|
||||||
selectedTile: null as Tile | null,
|
selectedTile: null as Tile | null,
|
||||||
selectedObject: null as Object | null,
|
selectedObject: null as Object | null,
|
||||||
selectedZoneObject: null as ZoneObject | null,
|
|
||||||
objectDepth: 0,
|
objectDepth: 0,
|
||||||
isTileListModalShown: false,
|
isTileListModalShown: false,
|
||||||
isObjectListModalShown: false,
|
isObjectListModalShown: false,
|
||||||
@ -95,9 +94,6 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
|
|||||||
setSelectedObject(object: any) {
|
setSelectedObject(object: any) {
|
||||||
this.selectedObject = object
|
this.selectedObject = object
|
||||||
},
|
},
|
||||||
setSelectedZoneObject(zoneObject: ZoneObject | null) {
|
|
||||||
this.selectedZoneObject = zoneObject
|
|
||||||
},
|
|
||||||
setObjectDepth(depth: number) {
|
setObjectDepth(depth: number) {
|
||||||
this.objectDepth = depth
|
this.objectDepth = depth
|
||||||
},
|
},
|
||||||
@ -114,8 +110,8 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
|
|||||||
setTeleportSettings(teleportSettings: TeleportSettings) {
|
setTeleportSettings(teleportSettings: TeleportSettings) {
|
||||||
this.teleportSettings = teleportSettings
|
this.teleportSettings = teleportSettings
|
||||||
},
|
},
|
||||||
reset() {
|
reset(resetZone = false) {
|
||||||
// this.zone = null
|
if (resetZone) this.zone = null
|
||||||
this.zoneList = []
|
this.zoneList = []
|
||||||
this.tileList = []
|
this.tileList = []
|
||||||
this.objectList = []
|
this.objectList = []
|
||||||
@ -123,7 +119,6 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
|
|||||||
this.drawMode = 'tile'
|
this.drawMode = 'tile'
|
||||||
this.selectedTile = null
|
this.selectedTile = null
|
||||||
this.selectedObject = null
|
this.selectedObject = null
|
||||||
this.selectedZoneObject = null
|
|
||||||
this.objectDepth = 0
|
this.objectDepth = 0
|
||||||
this.isSettingsModalShown = false
|
this.isSettingsModalShown = false
|
||||||
this.isZoneListModalShown = false
|
this.isZoneListModalShown = false
|
||||||
|
@ -215,3 +215,10 @@ export type ChatMessage = {
|
|||||||
character: Character
|
character: Character
|
||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WorldSettings = {
|
||||||
|
date: Date
|
||||||
|
isRainEnabled: boolean
|
||||||
|
isFogEnabled: boolean
|
||||||
|
fogDensity: number
|
||||||
|
}
|
||||||
|
@ -4,7 +4,8 @@ export default {
|
|||||||
theme: {
|
theme: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
'titles': ['Poppins', 'serif'],
|
'titles': ['Poppins', 'serif'],
|
||||||
'default': ['Quicksand', 'serif']
|
'default': ['Quicksand', 'serif'],
|
||||||
|
'ui': ['Upheaval', 'serif'],
|
||||||
},
|
},
|
||||||
backgroundSize: {
|
backgroundSize: {
|
||||||
'cover': 'cover',
|
'cover': 'cover',
|
||||||
@ -43,10 +44,13 @@ export default {
|
|||||||
dropShadow: {
|
dropShadow: {
|
||||||
'default': '0 3px 6px rgb(0, 0, 0)',
|
'default': '0 3px 6px rgb(0, 0, 0)',
|
||||||
'text': '1px 1px 5px rgba(0, 0, 0, 0.25)',
|
'text': '1px 1px 5px rgba(0, 0, 0, 0.25)',
|
||||||
|
'pixel': '2px 2px 0px rgb(77, 77, 77)',
|
||||||
|
'pixel-black': '2px 2px 0px rgb(0, 0, 0)',
|
||||||
'20': '0 3px 6px rgba(0, 0, 0, 0.2)'
|
'20': '0 3px 6px rgba(0, 0, 0, 0.2)'
|
||||||
},
|
},
|
||||||
boxShadow: {
|
boxShadow: {
|
||||||
'character': '0 4px 30px rgba(0, 0, 0, 0.1)',
|
'character': '0 4px 30px rgba(0, 0, 0, 0.1)',
|
||||||
|
'inner': 'inset 0 0 12px 8px rgb(0, 0, 0)',
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
cyan: {
|
cyan: {
|
||||||
@ -68,10 +72,14 @@ export default {
|
|||||||
400: '#332426'
|
400: '#332426'
|
||||||
},
|
},
|
||||||
blue: {
|
blue: {
|
||||||
DEFAULT: '#00c2ff'
|
DEFAULT: '#4F5FF0',
|
||||||
|
100: '#4F5FF0',
|
||||||
|
200: '#2E378A'
|
||||||
},
|
},
|
||||||
green: {
|
green: {
|
||||||
DEFAULT: '#09ad19'
|
DEFAULT: '#05E300',
|
||||||
|
100: '#05E300',
|
||||||
|
200: '#027D00'
|
||||||
},
|
},
|
||||||
purple: {
|
purple: {
|
||||||
DEFAULT: '#9841e6'
|
DEFAULT: '#9841e6'
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url';
|
import { fileURLToPath, URL } from 'node:url';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import VueDevTools from 'vite-plugin-vue-devtools';
|
import VueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue';
|
||||||
import VueDevTools from 'vite-plugin-vue-devtools'
|
import VueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
|