Compare commits
20 Commits
feature/#1
...
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 |
@ -3,7 +3,7 @@
|
|||||||
<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>Sylvan Quest - Play</title>
|
<title>Sylvan Quest - Play</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
106
package-lock.json
generated
106
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": {
|
||||||
@ -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"
|
||||||
},
|
},
|
||||||
@ -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",
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
10
public/assets/icons/minus-icon.svg
Normal file
10
public/assets/icons/minus-icon.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 597 KiB |
File diff suppressed because one or more lines are too long
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 |
15
public/assets/login/sq-logo-v1.svg
Normal file
15
public/assets/login/sq-logo-v1.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 12 KiB |
29
src/App.vue
29
src/App.vue
@ -1,34 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="overflow-hidden">
|
<Notifications />
|
||||||
<Notifications />
|
<component :is="currentScreen" />
|
||||||
<Login v-if="screen === 'login'" />
|
|
||||||
<!-- <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
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
//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;
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-field {
|
.input-field {
|
||||||
@apply px-4 py-2.5 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 {
|
||||||
@ -87,7 +87,7 @@ button {
|
|||||||
@apply text-center;
|
@apply text-center;
|
||||||
|
|
||||||
&.btn-cyan {
|
&.btn-cyan {
|
||||||
@apply bg-cyan text-gray-50 text-base rounded py-2.5;
|
@apply bg-cyan text-gray-50 text-base leading-5 rounded py-2.5;
|
||||||
|
|
||||||
&.active,
|
&.active,
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -96,7 +96,7 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.btn-red {
|
&.btn-red {
|
||||||
@apply bg-red text-gray-50 text-base rounded py-2.5;
|
@apply bg-red text-gray-50 text-base leading-5 rounded py-2.5;
|
||||||
|
|
||||||
&.active,
|
&.active,
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -104,6 +104,15 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
@ -85,7 +86,7 @@ const createFogEffect = (scene: Phaser.Scene) => {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,4 +107,4 @@ defineExpose(controlEffects)
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (sceneRef.value) sceneRef.value.scene.remove('effects')
|
if (sceneRef.value) sceneRef.value.scene.remove('effects')
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -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,7 +18,7 @@
|
|||||||
</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-md 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">
|
||||||
|
@ -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,7 +14,7 @@
|
|||||||
<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-md 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">
|
||||||
|
@ -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,7 +18,7 @@
|
|||||||
</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-md 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">
|
||||||
|
14
src/components/gameMaster/zoneEditor/EventTiles.vue
Normal file
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
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
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,31 +1,28 @@
|
|||||||
<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">
|
<template #modalHeader>
|
||||||
<Modal @modal:close="() => zoneEditorStore.toggleZoneListModal()" :is-resizable="false" :is-modal-open="true" :modal-width="300" :modal-height="360">
|
<h3 class="text-lg text-white">Zones</h3>
|
||||||
<template #modalHeader>
|
</template>
|
||||||
<h3 class="text-lg text-gray-300">Zones</h3>
|
<template #modalBody>
|
||||||
</template>
|
<div class="my-4 mx-auto">
|
||||||
<template #modalBody>
|
<div class="text-center mb-4 px-2 flex gap-2.5">
|
||||||
<div class="my-4 mx-auto">
|
<button class="btn-cyan py-1.5 min-w-[calc(50%_-_5px)]" @click="fetchZones">Refresh</button>
|
||||||
<div class="text-center mb-4 px-2 flex gap-2.5">
|
<button class="btn-cyan py-1.5 min-w-[calc(50%_-_5px)]" @click="() => zoneEditorStore.toggleCreateZoneModal()">New</button>
|
||||||
<button class="btn-cyan py-1.5 min-w-[calc(50%_-_5px)]" @click="fetchZones">Refresh</button>
|
|
||||||
<button class="btn-cyan py-1.5 min-w-[calc(50%_-_5px)]" @click="() => zoneEditorStore.toggleCreateZoneModal()">New</button>
|
|
||||||
</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="absolute left-0 top-0 w-full h-px bg-cyan-200" v-if="index === 0"></div>
|
|
||||||
<div class="flex gap-3 items-center w-full" @click="() => loadZone(zone.id)">
|
|
||||||
<span>{{ zone.name }}</span>
|
|
||||||
<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>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<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">
|
||||||
</Modal>
|
<div class="absolute left-0 top-0 w-full h-px bg-gray-500" v-if="index === 0"></div>
|
||||||
</Teleport>
|
<div class="flex gap-3 items-center w-full" @click="() => loadZone(zone.id)">
|
||||||
|
<span>{{ zone.name }}</span>
|
||||||
|
<span class="ml-auto gap-1 flex">
|
||||||
|
<button class="btn-red w-11 h-11 z-50" @click.stop="() => deleteZone(zone.id)">X</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
</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)
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
<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 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 rounded-md border-2 border-solid border-gray-500 text-gray-300" 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" :class="{'text-cyan-300': gameStore.character?.role == 'gm'}">{{ 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-64 mx-auto relative">
|
<div class="w-96 mx-auto relative">
|
||||||
<img src="/assets/icons/chat-icon.svg" class="absolute top-1/2 -translate-y-1/2 left-1.5 h-4 w-4 opacity-50" />
|
<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-[204px] h-6 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"
|
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,9 +1,7 @@
|
|||||||
<template>
|
<template></template>
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,48 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="absolute left-[300px] top-4">
|
<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>
|
<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>
|
<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 class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f1-icon.png')] bg-no-repeat"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-[346px] top-4">
|
<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>
|
<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>
|
<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 class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f2-icon.png')] bg-no-repeat"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-[392px] top-4">
|
<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>
|
<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>
|
<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 class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f3-icon.png')] bg-no-repeat"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-[438px] top-4">
|
<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>
|
<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>
|
<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 class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f4-icon.png')] bg-no-repeat"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-[484px] top-4">
|
<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>
|
<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>
|
<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 class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f5-icon.png')] bg-no-repeat"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-[530px] top-4">
|
<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>
|
<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>
|
<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 class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f6-icon.png')] bg-no-repeat"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-[576px] top-4">
|
<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>
|
<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>
|
<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 class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f7-icon.png')] bg-no-repeat"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-[622px] top-4">
|
<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>
|
<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>
|
<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 class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f8-icon.png')] bg-no-repeat"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
</script>
|
</script>
|
||||||
|
@ -53,5 +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');
|
let characterLevel = gameStore.character?.level.toString().padStart(2, '0')
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,9 +1,23 @@
|
|||||||
<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">
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
</script>
|
</script>
|
||||||
|
@ -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-md 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>
|
||||||
|
@ -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>
|
<div class="grid grid-flow-col justify-stretch gap-4">
|
||||||
</span>
|
<button class="btn-empty py-1.5 px-4 min-w-24 inline-block" @click="props.cancelFunction()">
|
||||||
<div class="flex justify-between p-2">
|
|
||||||
<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">
|
||||||
<slot name="modalHeader" />
|
<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" />
|
||||||
|
</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-3.5 h-3.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">
|
||||||
<slot name="modalBody" />
|
<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>
|
||||||
<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 class="relative z-10 h-full">
|
||||||
</div>
|
<slot name="modalBody" />
|
||||||
<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">
|
</div>
|
||||||
<slot name="modalFooter" />
|
<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" />
|
||||||
</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) {
|
||||||
setAllTiles(zoneTilemap, tiles, zoneStore.zone.tiles)
|
return
|
||||||
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')))
|
|
||||||
}
|
}
|
||||||
|
setAllTiles(zoneTilemap, tiles, zoneStore.zone.tiles)
|
||||||
|
tileArray = zoneStore.zone.tiles.map((row) => row.map((tileId) => tileId || '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/ui-box-outer.svg" class="absolute w-full h-full max-lg:hidden" />
|
|
||||||
<img src="/assets/ui-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,40 +3,31 @@
|
|||||||
<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">
|
<Menu />
|
||||||
<Menu />
|
<Hud />
|
||||||
<Hud />
|
<Keybindings />
|
||||||
<Keybindings />
|
<Minimap />
|
||||||
<Minimap />
|
<Zone />
|
||||||
<Zone />
|
<Chat />
|
||||||
<Chat />
|
<ExpBar />
|
||||||
<ExpBar />
|
|
||||||
|
|
||||||
<Inventory />
|
<Inventory />
|
||||||
<Effects />
|
<Effects />
|
||||||
</div>
|
</div>
|
||||||
</Scene>
|
</Scene>
|
||||||
</Game>
|
</Game>
|
||||||
</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>
|
</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: 'Sylvan 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,7 +4,7 @@
|
|||||||
<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/ui-box-outer.svg" class="absolute w-full h-full" />
|
<img src="/assets/ui-box-outer.svg" class="absolute w-full h-full" />
|
||||||
<img src="/assets/ui-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" />
|
||||||
@ -16,12 +16,12 @@
|
|||||||
<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 />
|
<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-login" v-model="password" :type="showPassword ? 'text' : '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 @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>
|
<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 -->
|
||||||
<div class="absolute w-40 h-0.5 -bottom-8 left-1/2 -translate-x-1/2 flex justify-between">
|
<div class="absolute w-40 h-0.5 -bottom-8 left-1/2 -translate-x-1/2 flex justify-between">
|
||||||
@ -42,11 +42,11 @@
|
|||||||
<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 />
|
<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-register" v-model="password" :type="showPassword ? 'text' : '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 @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>
|
<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="btn-cyan xs:w-full" type="submit">Register now</button>
|
<button class="btn-cyan xs:w-full" type="submit">Register now</button>
|
||||||
|
|
||||||
<!-- Divider shape -->
|
<!-- Divider shape -->
|
||||||
<div class="absolute w-40 h-0.5 -bottom-8 left-1/2 -translate-x-1/2 flex justify-between">
|
<div class="absolute w-40 h-0.5 -bottom-8 left-1/2 -translate-x-1/2 flex justify-between">
|
||||||
|
137
src/screens/ZoneEditor.vue
Normal file
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>
|
@ -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
|
||||||
|
@ -221,4 +221,4 @@ export type WorldSettings = {
|
|||||||
isRainEnabled: boolean
|
isRainEnabled: boolean
|
||||||
isFogEnabled: boolean
|
isFogEnabled: boolean
|
||||||
fogDensity: number
|
fogDensity: number
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ export default {
|
|||||||
},
|
},
|
||||||
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: {
|
||||||
|
@ -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/
|
||||||
@ -15,4 +14,4 @@ export default defineConfig({
|
|||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
Reference in New Issue
Block a user