Compare commits

...

30 Commits

Author SHA1 Message Date
aff32c33c7 npm update, mobile scroll fix, zone editor object refactor work, removed redundant div 2024-10-18 00:20:30 +02:00
774871510e Reordered func. params getTile(), npm run format, refactor zone object part in zone editor, other improvements 2024-10-17 19:26:45 +02:00
e61b705031 Smaller GmTools modal height 2024-10-17 18:41:14 +02:00
3902c611fa Fixed placing tiles 2024-10-17 18:39:18 +02:00
3765cfe5e9 Started refactoring zone editor 2024-10-17 02:32:39 +02:00
2fad54fd26 More styling tweaks, updated confirmation modal 2024-10-16 22:23:00 +02:00
be3cbf77bf Added global empty btn styling, adjusted and WIP modal styling 2024-10-16 20:47:29 +02:00
f24a498246 Re-added Vue Dev Tools. Problem fixed
https://github.com/vuejs/devtools-next/issues/635
2024-10-16 18:50:38 +02:00
9686381745 Removed unused impots, use config.name instead of hardcoded name 2024-10-16 16:07:35 +02:00
e42c530685 Bug fix for toggling zoneEditor 2024-10-16 16:06:18 +02:00
e56e078042 Separated Game and ZoneEditor screens 2024-10-16 16:04:43 +02:00
13be1a38fa Refactor App.vue 2024-10-16 16:04:29 +02:00
27e857b9a6 npm update, removed vue dev tools bc bug 2024-10-16 15:45:41 +02:00
2b2c290db0 Removed unused const. 2024-10-16 14:15:30 +02:00
245b50c1fd Moved component attributes to properties function for more streamlined experience 2024-10-16 14:15:01 +02:00
32dc7a2963 Removed register, it's in the same screen now 2024-10-16 14:11:43 +02:00
a9c2b209d9 Works partially 💩 2024-10-15 23:48:50 +02:00
a6c22df528 Fixed some bugs in login form, updated logo name and icons, wip of minimap 2024-10-15 20:27:54 +02:00
7dd2d70eca npm update 2024-10-15 15:49:12 +02:00
5fc3547d9c Replaced logo 2024-10-14 23:25:41 +02:00
b5c222cc05 Made eye icon on login/register functional 2024-10-14 22:05:12 +02:00
8b1efca7b8 Added world settings type and added it into gameStore 2024-10-14 20:11:30 +02:00
1bdd2bc75a npm update 2024-10-14 19:14:53 +02:00
24dff8d920 New Quest > Sylvan Quest 2024-10-14 15:46:02 +02:00
8b98fc5c4e More WIP ingame UI
Temporary skill icons, adjusted borders globally
2024-10-13 20:27:44 +02:00
934ae50d8e Updated global btn + input styling 2024-10-13 17:25:46 +02:00
7504e3719e WIP ingame ui updated design
Cleaned up some old icons and styling
2024-10-13 17:21:45 +02:00
34bd103ec2 Reorderered components 2024-10-13 12:27:16 +02:00
295ce98e33 Merge remote-tracking branch 'origin/feature/137-zone-effect' into feature/new-design-FE 2024-10-13 12:25:28 +02:00
1cfdf1857e Updated close icon, more styling changes 2024-10-12 22:20:25 +02:00
88 changed files with 1121 additions and 779 deletions

View File

@ -3,8 +3,8 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/> <meta name="viewport" content="width=device-width">
<title>New Quest - Play</title> <title>Sylvan Quest - Play</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

206
package-lock.json generated
View File

@ -11,11 +11,11 @@
"@vueuse/core": "^10.5.0", "@vueuse/core": "^10.5.0",
"@vueuse/integrations": "^10.5.0", "@vueuse/integrations": "^10.5.0",
"axios": "^1.7.7", "axios": "^1.7.7",
"phaser": "^3.85.2", "phaser": "^3.86.0",
"pinia": "^2.1.6", "pinia": "^2.1.6",
"socket.io-client": "^4.8.0", "socket.io-client": "^4.8.0",
"universal-cookie": "^6.1.3", "universal-cookie": "^6.1.3",
"vue": "^3.5.10", "vue": "^3.5.12",
"zod": "^3.22.2" "zod": "^3.22.2"
}, },
"devDependencies": { "devDependencies": {
@ -41,8 +41,8 @@
"sass": "^1.79.4", "sass": "^1.79.4",
"tailwindcss": "^3.4.13", "tailwindcss": "^3.4.13",
"typescript": "~5.6.2", "typescript": "~5.6.2",
"vite": "^5.4.8", "vite": "^5.4.9",
"vite-plugin-vue-devtools": "^7.4.6", "vite-plugin-vue-devtools": "^7.5.2",
"vitest": "^2.0.3", "vitest": "^2.0.3",
"vue-tsc": "^1.6.5" "vue-tsc": "^1.6.5"
} }
@ -1991,9 +1991,9 @@
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.16.11", "version": "20.16.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.12.tgz",
"integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==", "integrity": "sha512-LfPFB0zOeCeCNQV3i+67rcoVvoN5n0NVuR2vLG0O5ySQMgchuZlC4lgz546ZOJyDtj5KIgOxy+lacOimfqZAIA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -2228,14 +2228,14 @@
} }
}, },
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.2.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.3.tgz",
"integrity": "sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==", "integrity": "sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "2.1.2", "@vitest/spy": "2.1.3",
"@vitest/utils": "2.1.2", "@vitest/utils": "2.1.3",
"chai": "^5.1.1", "chai": "^5.1.1",
"tinyrainbow": "^1.2.0" "tinyrainbow": "^1.2.0"
}, },
@ -2244,13 +2244,13 @@
} }
}, },
"node_modules/@vitest/mocker": { "node_modules/@vitest/mocker": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.2.tgz", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.3.tgz",
"integrity": "sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==", "integrity": "sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "^2.1.0-beta.1", "@vitest/spy": "2.1.3",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"magic-string": "^0.30.11" "magic-string": "^0.30.11"
}, },
@ -2258,7 +2258,7 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
}, },
"peerDependencies": { "peerDependencies": {
"@vitest/spy": "2.1.2", "@vitest/spy": "2.1.3",
"msw": "^2.3.5", "msw": "^2.3.5",
"vite": "^5.0.0" "vite": "^5.0.0"
}, },
@ -2282,9 +2282,9 @@
} }
}, },
"node_modules/@vitest/pretty-format": { "node_modules/@vitest/pretty-format": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.2.tgz", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.3.tgz",
"integrity": "sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==", "integrity": "sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -2295,13 +2295,13 @@
} }
}, },
"node_modules/@vitest/runner": { "node_modules/@vitest/runner": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.2.tgz", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.3.tgz",
"integrity": "sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==", "integrity": "sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/utils": "2.1.2", "@vitest/utils": "2.1.3",
"pathe": "^1.1.2" "pathe": "^1.1.2"
}, },
"funding": { "funding": {
@ -2309,13 +2309,13 @@
} }
}, },
"node_modules/@vitest/snapshot": { "node_modules/@vitest/snapshot": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.2.tgz", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.3.tgz",
"integrity": "sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==", "integrity": "sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "2.1.2", "@vitest/pretty-format": "2.1.3",
"magic-string": "^0.30.11", "magic-string": "^0.30.11",
"pathe": "^1.1.2" "pathe": "^1.1.2"
}, },
@ -2324,9 +2324,9 @@
} }
}, },
"node_modules/@vitest/spy": { "node_modules/@vitest/spy": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.2.tgz", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.3.tgz",
"integrity": "sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==", "integrity": "sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -2337,13 +2337,13 @@
} }
}, },
"node_modules/@vitest/utils": { "node_modules/@vitest/utils": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.2.tgz", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.3.tgz",
"integrity": "sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==", "integrity": "sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "2.1.2", "@vitest/pretty-format": "2.1.3",
"loupe": "^3.1.1", "loupe": "^3.1.1",
"tinyrainbow": "^1.2.0" "tinyrainbow": "^1.2.0"
}, },
@ -2490,14 +2490,14 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vue/devtools-core": { "node_modules/@vue/devtools-core": {
"version": "7.4.6", "version": "7.5.2",
"resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.4.6.tgz", "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.5.2.tgz",
"integrity": "sha512-7ATNPEbVqThOOAp2bg/YUIm9MqqgimbSk24D05hdXUp89JlXX12aTzdrWd9xZRwS78hDR+wCToHl1C/8sopBrg==", "integrity": "sha512-J7vcCb2P7bH3TvikqSe3BquCZsgWC7PL0t9yO88c3LUK3cyhQdJoWcn0Tkgop55UztHWs40+7uQNDmTkcdNZAQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/devtools-kit": "^7.4.6", "@vue/devtools-kit": "^7.5.2",
"@vue/devtools-shared": "^7.4.6", "@vue/devtools-shared": "^7.5.2",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"nanoid": "^3.3.4", "nanoid": "^3.3.4",
"pathe": "^1.1.2", "pathe": "^1.1.2",
@ -2508,14 +2508,14 @@
} }
}, },
"node_modules/@vue/devtools-kit": { "node_modules/@vue/devtools-kit": {
"version": "7.4.6", "version": "7.5.2",
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.4.6.tgz", "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.5.2.tgz",
"integrity": "sha512-NbYBwPWgEic1AOd9bWExz9weBzFdjiIfov0yRn4DrRfR+EQJCI9dn4I0XS7IxYGdkmUJi8mFW42LLk18WsGqew==", "integrity": "sha512-0leUOE2HBfl8sHf9ePKzxqnCFskkU22tWWqd9OfeSlslAKE30/TViYvWcF4vgQmPlJnAAdHU0WfW5dYlCeOiuw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/devtools-shared": "^7.4.6", "@vue/devtools-shared": "^7.5.2",
"birpc": "^0.2.17", "birpc": "^0.2.19",
"hookable": "^5.5.3", "hookable": "^5.5.3",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"perfect-debounce": "^1.0.0", "perfect-debounce": "^1.0.0",
@ -2524,9 +2524,9 @@
} }
}, },
"node_modules/@vue/devtools-shared": { "node_modules/@vue/devtools-shared": {
"version": "7.4.6", "version": "7.5.2",
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.4.6.tgz", "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.5.2.tgz",
"integrity": "sha512-rPeSBzElnHYMB05Cc056BQiJpgocQjY8XVulgni+O9a9Gr9tNXgPteSzFFD+fT/iWMxNuUgGKs9CuW5DZewfIg==", "integrity": "sha512-+zmcixnD6TAo+zwm30YuwZckhL9iIi4u+gFwbq9C8zpm3SMndTlEYZtNhAHUhOXB+bCkzyunxw80KQ/T0trF4w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -2858,9 +2858,9 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.12.1", "version": "8.13.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@ -3186,9 +3186,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001668", "version": "1.0.30001669",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz",
"integrity": "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==", "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -3642,9 +3642,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.36", "version": "1.5.41",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.36.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz",
"integrity": "sha512-HYTX8tKge/VNp6FGO+f/uVDmUkq+cEfcxYhKf15Akc4M5yxt5YmorwlAitKWjWhWQnKcDRBAQKXkhqqXMqcrjw==", "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@ -5764,9 +5764,9 @@
} }
}, },
"node_modules/phaser3-rex-plugins": { "node_modules/phaser3-rex-plugins": {
"version": "1.80.8", "version": "1.80.9",
"resolved": "https://registry.npmjs.org/phaser3-rex-plugins/-/phaser3-rex-plugins-1.80.8.tgz", "resolved": "https://registry.npmjs.org/phaser3-rex-plugins/-/phaser3-rex-plugins-1.80.9.tgz",
"integrity": "sha512-/uZMauVrOC/uiywQF37yH5Q6eUMOL2knMCnfEDWnc5ek40jvQOKjsenjB6q6hYuMvV54C8m0q2p1oL6B/rz12g==", "integrity": "sha512-yx+WSAf4MOF2AimVL/Dv7eN65/YuO4LVNlYihDP0tAgFfCoTBunAd0YDbv82eoSkOAd0gy6w3Qh71p3kq1eRTA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -5798,9 +5798,9 @@
} }
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
@ -6409,9 +6409,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.79.5", "version": "1.80.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.79.5.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.1.tgz",
"integrity": "sha512-W1h5kp6bdhqFh2tk3DsI771MoEJjvrSY/2ihJRJS4pjIyfJCw0nTsxqhnrUzaLMOJjFchj8rOvraI/YUVjtx5g==", "integrity": "sha512-9lBwDZ7j3y/1DKj5Ec249EVGo5CVpwnzIyIj+cqlCjKkApLnzsJ/l9SnV4YnORvW9dQwQN+gQvh/mFZ8CnDs7Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -6818,9 +6818,9 @@
} }
}, },
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.4.13", "version": "3.4.14",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz",
"integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -6944,9 +6944,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/tinyexec": { "node_modules/tinyexec": {
"version": "0.3.0", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz",
"integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -7062,9 +7062,9 @@
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.7.0", "version": "2.8.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==",
"dev": true, "dev": true,
"license": "0BSD" "license": "0BSD"
}, },
@ -7195,9 +7195,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.4.8", "version": "5.4.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz",
"integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -7268,9 +7268,9 @@
} }
}, },
"node_modules/vite-node": { "node_modules/vite-node": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.2.tgz", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.3.tgz",
"integrity": "sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==", "integrity": "sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -7322,15 +7322,15 @@
} }
}, },
"node_modules/vite-plugin-vue-devtools": { "node_modules/vite-plugin-vue-devtools": {
"version": "7.4.6", "version": "7.5.2",
"resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.4.6.tgz", "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.5.2.tgz",
"integrity": "sha512-lOKur3qovCB3BQStL0qfHEoIusqya1ngfxfWuqn9DTa6h9rlw6+S3PV4geOP5YBGYQ4NW1hRX70OD8I+sYr1dA==", "integrity": "sha512-+lQOKW0kZAvLxy9KcsmtOk5Hsu0ibVAot9odFwCCASE4jukb0zaWGIyZwFLk4IsWNDT3iISvajIr704UYcZL6g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/devtools-core": "^7.4.6", "@vue/devtools-core": "^7.5.2",
"@vue/devtools-kit": "^7.4.6", "@vue/devtools-kit": "^7.5.2",
"@vue/devtools-shared": "^7.4.6", "@vue/devtools-shared": "^7.5.2",
"execa": "^8.0.1", "execa": "^8.0.1",
"sirv": "^2.0.4", "sirv": "^2.0.4",
"vite-plugin-inspect": "^0.8.7", "vite-plugin-inspect": "^0.8.7",
@ -7365,19 +7365,19 @@
} }
}, },
"node_modules/vitest": { "node_modules/vitest": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.2.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.3.tgz",
"integrity": "sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==", "integrity": "sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/expect": "2.1.2", "@vitest/expect": "2.1.3",
"@vitest/mocker": "2.1.2", "@vitest/mocker": "2.1.3",
"@vitest/pretty-format": "^2.1.2", "@vitest/pretty-format": "^2.1.3",
"@vitest/runner": "2.1.2", "@vitest/runner": "2.1.3",
"@vitest/snapshot": "2.1.2", "@vitest/snapshot": "2.1.3",
"@vitest/spy": "2.1.2", "@vitest/spy": "2.1.3",
"@vitest/utils": "2.1.2", "@vitest/utils": "2.1.3",
"chai": "^5.1.1", "chai": "^5.1.1",
"debug": "^4.3.6", "debug": "^4.3.6",
"magic-string": "^0.30.11", "magic-string": "^0.30.11",
@ -7388,7 +7388,7 @@
"tinypool": "^1.0.0", "tinypool": "^1.0.0",
"tinyrainbow": "^1.2.0", "tinyrainbow": "^1.2.0",
"vite": "^5.0.0", "vite": "^5.0.0",
"vite-node": "2.1.2", "vite-node": "2.1.3",
"why-is-node-running": "^2.3.0" "why-is-node-running": "^2.3.0"
}, },
"bin": { "bin": {
@ -7403,8 +7403,8 @@
"peerDependencies": { "peerDependencies": {
"@edge-runtime/vm": "*", "@edge-runtime/vm": "*",
"@types/node": "^18.0.0 || >=20.0.0", "@types/node": "^18.0.0 || >=20.0.0",
"@vitest/browser": "2.1.2", "@vitest/browser": "2.1.3",
"@vitest/ui": "2.1.2", "@vitest/ui": "2.1.3",
"happy-dom": "*", "happy-dom": "*",
"jsdom": "*" "jsdom": "*"
}, },
@ -7794,9 +7794,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.5.1", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {

View File

@ -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"
} }

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 597 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" id="screenshot-7904a66d-0c2c-80b0-8004-7aa8e4dbc6e0" viewBox="5853.755 6642.086 23.788 23.788" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-7904a66d-0c2c-80b0-8004-7aa8e4dbc6e0"><g class="fills" id="fills-7904a66d-0c2c-80b0-8004-7aa8e4dbc6e0"><path d="M5867.837,6654.000L5877.045,6644.792C5877.199,6644.649,5877.322,6644.476,5877.408,6644.284C5877.493,6644.093,5877.539,6643.886,5877.543,6643.676C5877.546,6643.466,5877.508,6643.258,5877.429,6643.063C5877.350,6642.869,5877.234,6642.692,5877.085,6642.544C5876.937,6642.395,5876.760,6642.278,5876.565,6642.200C5876.371,6642.121,5876.163,6642.082,5875.953,6642.086C5875.743,6642.090,5875.536,6642.136,5875.344,6642.221C5875.153,6642.307,5874.980,6642.430,5874.837,6642.583L5865.629,6651.792L5856.420,6642.583C5856.124,6642.307,5855.732,6642.157,5855.328,6642.164C5854.923,6642.171,5854.537,6642.335,5854.250,6642.622C5853.964,6642.908,5853.800,6643.294,5853.793,6643.699C5853.786,6644.104,5853.936,6644.495,5854.212,6644.792L5863.420,6654.000L5854.212,6663.208C5853.919,6663.501,5853.755,6663.898,5853.755,6664.312C5853.755,6664.726,5853.919,6665.124,5854.212,6665.417C5854.505,6665.709,5854.902,6665.874,5855.316,6665.874C5855.730,6665.874,5856.127,6665.709,5856.420,6665.417L5865.629,6656.208L5874.837,6665.417C5875.130,6665.709,5875.527,6665.874,5875.941,6665.874C5876.355,6665.874,5876.753,6665.709,5877.045,6665.417C5877.338,6665.124,5877.502,6664.726,5877.502,6664.312C5877.502,6663.898,5877.338,6663.501,5877.045,6663.208L5867.837,6654.000ZZ" style="fill: rgb(255, 255, 255);"/></g></g></svg> <svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.91481 9.08504C7.71955 9.2803 7.40297 9.2803 7.20771 9.08504L5.55709 7.43442C5.36183 7.23916 5.04524 7.23916 4.84998 7.43442L2.99578 9.28862C2.80052 9.48389 2.48393 9.48389 2.28867 9.28863L1.00416 8.00412C0.808899 7.80885 0.808899 7.49227 1.00416 7.29701L2.85837 5.4428C3.05363 5.24754 3.05363 4.93096 2.85837 4.7357L0.914865 2.7922C0.719603 2.59693 0.719603 2.28035 0.914865 2.08509L2.07053 0.929423C2.26579 0.734161 2.58238 0.734162 2.77764 0.929424L4.72114 2.87293C4.9164 3.06819 5.23298 3.06819 5.42825 2.87293L7.297 1.00417C7.49226 0.808906 7.80885 0.808906 8.00411 1.00417L9.28862 2.28868C9.48388 2.48394 9.48388 2.80052 9.28862 2.99578L7.41986 4.86454C7.2246 5.0598 7.2246 5.37639 7.41986 5.57165L9.07048 7.22227C9.26574 7.41753 9.26574 7.73411 9.07048 7.92937L7.91481 9.08504Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 915 B

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="20" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M17.882 19.297A10.949 10.949 0 0 1 12 21c-5.392 0-9.878-3.88-10.819-9a10.982 10.982 0 0 1 3.34-6.066L1.392 2.808l1.415-1.415 19.799 19.8-1.415 1.414-3.31-3.31zM5.935 7.35A8.965 8.965 0 0 0 3.223 12a9.005 9.005 0 0 0 13.201 5.838l-2.028-2.028A4.5 4.5 0 0 1 8.19 9.604L5.935 7.35zm6.979 6.978l-3.242-3.242a2.5 2.5 0 0 0 3.241 3.241zm7.893 2.264l-1.431-1.43A8.935 8.935 0 0 0 20.777 12 9.005 9.005 0 0 0 9.552 5.338L7.974 3.76C9.221 3.27 10.58 3 12 3c5.392 0 9.878 3.88 10.819 9a10.947 10.947 0 0 1-2.012 4.592zm-9.084-9.084a4.5 4.5 0 0 1 4.769 4.769l-4.77-4.769z" fill="#808080"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 788 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 600 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 600 KiB

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

View File

@ -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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 599 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1 +0,0 @@
<svg width="290" xmlns="http://www.w3.org/2000/svg" height="87" id="screenshot-e9942e24-155b-8096-8004-7eb5ea5d2669" viewBox="0 0 290 87" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-e9942e24-155b-8096-8004-7eb5ea5d2669"><g class="fills" id="fills-e9942e24-155b-8096-8004-7eb5ea5d2669"><path d="M286.515,78.278C282.911,83.543,276.856,87.000,270.000,87.000L20.000,87.000C8.962,87.000,0.000,78.038,0.000,67.000L0.000,20.000C0.000,8.962,8.962,0.000,20.000,0.000L270.000,0.000C276.834,0.000,282.872,3.435,286.480,8.671C268.843,10.411,255.000,25.352,255.000,43.500C255.000,61.610,268.784,76.525,286.515,78.278ZM290.000,20.000L290.000,67.000" style="fill: #fff; fill-opacity: 1;"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 726 B

View File

@ -1 +0,0 @@
<svg width="290" xmlns="http://www.w3.org/2000/svg" height="87" id="screenshot-e38d8c7f-bba0-801b-8004-7d6eeffceb00" viewBox="4058.354 6110 290 87" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-e38d8c7f-bba0-801b-8004-7d6eeffceb00"><g class="fills" id="fills-e38d8c7f-bba0-801b-8004-7d6eeffceb00"><path d="M4061.840,6118.722C4065.444,6113.457,4071.499,6110.000,4078.354,6110.000L4328.354,6110.000C4339.393,6110.000,4348.354,6118.962,4348.354,6130.000L4348.354,6177.000C4348.354,6188.038,4339.393,6197.000,4328.354,6197.000L4078.354,6197.000C4071.521,6197.000,4065.483,6193.565,4061.875,6188.329C4079.512,6186.589,4093.354,6171.648,4093.354,6153.500C4093.354,6135.390,4079.571,6120.475,4061.840,6118.722ZM4058.354,6177.000L4058.354,6130.000" style="fill: #fff; fill-opacity: 1;"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 829 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 598 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 471 KiB

View File

Before

Width:  |  Height:  |  Size: 301 KiB

After

Width:  |  Height:  |  Size: 301 KiB

View File

Before

Width:  |  Height:  |  Size: 400 KiB

After

Width:  |  Height:  |  Size: 400 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

View File

@ -1,34 +1,27 @@
<template> <template>
<div class="overflow-hidden">
<Notifications /> <Notifications />
<Login v-if="screen === 'login'" /> <component :is="currentScreen" />
<!-- <Register v-if="screen === 'register'" />-->
<Characters v-if="screen === 'characters'" />
<Game v-if="screen === 'game'" />
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
import Notifications from '@/components/utilities/Notifications.vue' import Notifications from '@/components/utilities/Notifications.vue'
import Login from '@/screens/Login.vue' import Login from '@/screens/Login.vue'
// import Register from '@/screens/Register.vue'
import Characters from '@/screens/Characters.vue' import Characters from '@/screens/Characters.vue'
import Game from '@/screens/Game.vue' import Game from '@/screens/Game.vue'
import ZoneEditor from '@/screens/ZoneEditor.vue'
import { computed } from 'vue' import { computed } from 'vue'
const gameStore = useGameStore() const gameStore = useGameStore()
const zoneEditorStore = useZoneEditorStore()
const screen = computed(() => { const currentScreen = computed(() => {
if (!gameStore.connection) { if (!gameStore.connection) return Login
return 'login' if (!gameStore.token) return Login
} else if (gameStore.token && gameStore.connection) { if (!gameStore.character) return Characters
if (gameStore.character) { if (zoneEditorStore.active) return ZoneEditor
return 'game' return Game
}
return 'characters'
}
return 'login' // default fallback
}) })
// Disable right click // Disable right click

Binary file not shown.

View File

@ -4,11 +4,15 @@
// Fonts // Fonts
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
@font-face {
font-family: 'Upheaval';
src: url('../fonts/upheavtt.ttf');
}
//Globals //Globals
body { body {
@apply bg-black m-0 select-none overscroll-none overflow-hidden; @apply bg-black m-0 select-none;
-ms-overflow-style: none; -ms-overflow-style: none;
scrollbar-width: none; scrollbar-width: none;
@ -57,7 +61,7 @@ input {
} }
.input-field { .input-field {
@apply px-4 py-3 text-base focus-visible:outline-none bg-gray border border-solid border-gray-500 rounded text-gray-300; @apply px-4 py-2.5 text-base leading-5 focus-visible:outline-none bg-gray border border-solid border-gray-500 rounded text-gray-300;
&.inactive { &.inactive {
@apply bg-gray-600/50 hover:cursor-not-allowed; @apply bg-gray-600/50 hover:cursor-not-allowed;
&::placeholder { &::placeholder {
@ -83,7 +87,7 @@ button {
@apply text-center; @apply text-center;
&.btn-cyan { &.btn-cyan {
@apply bg-cyan text-gray-50 text-base rounded py-3; @apply bg-cyan text-gray-50 text-base leading-5 rounded py-2.5;
&.active, &.active,
&:hover { &:hover {
@ -92,7 +96,7 @@ button {
} }
&.btn-red { &.btn-red {
@apply bg-red text-gray-50; @apply bg-red text-gray-50 text-base leading-5 rounded py-2.5;
&.active, &.active,
&:hover { &:hover {
@ -100,9 +104,26 @@ button {
} }
} }
&.btn-empty {
@apply text-gray-50 border-2 border-solid border-gray-500 text-base leading-5 rounded py-2.5;
&.active,
&:hover {
@apply bg-gray-700 border-gray-700;
}
}
&:hover { &:hover {
@apply cursor-pointer; @apply cursor-pointer;
} }
&.eye-open {
@apply bg-[url('/assets/icons/eye-closed.svg')] w-5 h-4 right-2.5;
}
}
.text-pixel {
@apply text-white font-ui drop-shadow-pixel-black;
} }
::-webkit-scrollbar { ::-webkit-scrollbar {

View File

@ -1,6 +1,5 @@
<template> <template>
<Scene name="effects" @preload="preloadScene" @create="createScene" @update="updateScene"> <Scene name="effects" @preload="preloadScene" @create="createScene" @update="updateScene"> </Scene>
</Scene>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -12,6 +11,8 @@ import { onBeforeMount, onBeforeUnmount, ref } from 'vue'
const gameStore = useGameStore() const gameStore = useGameStore()
const zoneEditorStore = useZoneEditorStore() const zoneEditorStore = useZoneEditorStore()
// See if there's a dat
const sceneRef = ref<Phaser.Scene | null>(null) const sceneRef = ref<Phaser.Scene | null>(null)
// Effect-related refs // Effect-related refs
@ -48,7 +49,7 @@ const createDayNightCycle = (scene: Phaser.Scene) => {
const updateDayNightCycle = (time: number) => { const updateDayNightCycle = (time: number) => {
if (!dayNightCycle.value) return if (!dayNightCycle.value) return
const darkness = Math.sin((time % dayNightDuration) / dayNightDuration * Math.PI) * maxDarkness const darkness = Math.sin(((time % dayNightDuration) / dayNightDuration) * Math.PI) * maxDarkness
dayNightCycle.value.clear() dayNightCycle.value.clear()
dayNightCycle.value.fillStyle(0x000000, darkness) dayNightCycle.value.fillStyle(0x000000, darkness)
dayNightCycle.value.fillRect(0, 0, window.innerWidth, window.innerHeight) dayNightCycle.value.fillRect(0, 0, window.innerWidth, window.innerHeight)
@ -78,14 +79,14 @@ const toggleRain = (isRaining: boolean) => {
const createFogEffect = (scene: Phaser.Scene) => { const createFogEffect = (scene: Phaser.Scene) => {
fogSprite.value = scene.add.sprite(window.innerWidth / 2, window.innerHeight / 2, 'fog') fogSprite.value = scene.add.sprite(window.innerWidth / 2, window.innerHeight / 2, 'fog')
fogSprite.value.setScale(2) fogSprite.value.setScale(2)
fogSprite.value.setAlpha(0) // yeetdasasdasd fogSprite.value.setAlpha(0)
fogSprite.value.setDepth(950) // yeetdasasdasd fogSprite.value.setDepth(950)
} }
const updateFogEffect = () => { const updateFogEffect = () => {
if (fogSprite.value) { if (fogSprite.value) {
// Example: Oscillate fog opacity // Example: Oscillate fog opacity
const fogOpacity = (Math.sin(Date.now() / 5000) + 1) / 2 * 0.3 const fogOpacity = ((Math.sin(Date.now() / 5000) + 1) / 2) * 0.3
fogSprite.value.setAlpha(fogOpacity) fogSprite.value.setAlpha(fogOpacity)
} }
} }

View File

@ -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>

View File

@ -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 })

View File

@ -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">

View File

@ -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">

View File

@ -7,7 +7,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg> </svg>
</label> </label>
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div> <div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</div> </div>
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll"> <div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
<div v-bind="wrapperProps" ref="elementToScroll"> <div v-bind="wrapperProps" ref="elementToScroll">
@ -18,10 +18,10 @@
</div> </div>
<span>{{ object.name }}</span> <span>{{ object.name }}</span>
</div> </div>
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div> <div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
</div> </div>
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-lg bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop"> <button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" /> <img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
</button> </button>
</div> </div>

View File

@ -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>

View File

@ -6,7 +6,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg> </svg>
</button> </button>
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div> <div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</div> </div>
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll"> <div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
<div v-bind="wrapperProps" ref="elementToScroll"> <div v-bind="wrapperProps" ref="elementToScroll">
@ -14,10 +14,10 @@
<div class="flex items-center gap-2.5"> <div class="flex items-center gap-2.5">
<span>{{ sprite.name }}</span> <span>{{ sprite.name }}</span>
</div> </div>
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div> <div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
</div> </div>
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-lg bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop"> <button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" /> <img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
</button> </button>
</div> </div>

View File

@ -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">

View File

@ -7,7 +7,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg> </svg>
</label> </label>
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div> <div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</div> </div>
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll"> <div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
<div v-bind="wrapperProps" ref="elementToScroll"> <div v-bind="wrapperProps" ref="elementToScroll">
@ -18,10 +18,10 @@
</div> </div>
<span>{{ tile.name }}</span> <span>{{ tile.name }}</span>
</div> </div>
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div> <div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
</div> </div>
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-lg bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop"> <button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" /> <img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
</button> </button>
</div> </div>

View 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>

View 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>

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 = () => {

View File

@ -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()

View File

@ -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>

View File

@ -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>

View File

@ -1,10 +1,8 @@
<template> <template>
<CreateZone v-if="zoneEditorStore.isCreateZoneModalShown" /> <CreateZone v-if="zoneEditorStore.isCreateZoneModalShown" />
<Modal :is-modal-open="zoneEditorStore.isZoneListModalShown" @modal:close="() => zoneEditorStore.toggleZoneListModal()" :is-resizable="false" :modal-width="300" :modal-height="360">
<Teleport to="body">
<Modal @modal:close="() => zoneEditorStore.toggleZoneListModal()" :is-resizable="false" :is-modal-open="true" :modal-width="300" :modal-height="360">
<template #modalHeader> <template #modalHeader>
<h3 class="text-lg text-gray-300">Zones</h3> <h3 class="text-lg text-white">Zones</h3>
</template> </template>
<template #modalBody> <template #modalBody>
<div class="my-4 mx-auto"> <div class="my-4 mx-auto">
@ -13,19 +11,18 @@
<button class="btn-cyan py-1.5 min-w-[calc(50%_-_5px)]" @click="() => zoneEditorStore.toggleCreateZoneModal()">New</button> <button class="btn-cyan py-1.5 min-w-[calc(50%_-_5px)]" @click="() => zoneEditorStore.toggleCreateZoneModal()">New</button>
</div> </div>
<div class="relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap" v-for="(zone, index) in zoneEditorStore.zoneList" :key="zone.id"> <div class="relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap" v-for="(zone, index) in zoneEditorStore.zoneList" :key="zone.id">
<div class="absolute left-0 top-0 w-full h-px bg-cyan-200" v-if="index === 0"></div> <div class="absolute left-0 top-0 w-full h-px bg-gray-500" v-if="index === 0"></div>
<div class="flex gap-3 items-center w-full" @click="() => loadZone(zone.id)"> <div class="flex gap-3 items-center w-full" @click="() => loadZone(zone.id)">
<span>{{ zone.name }}</span> <span>{{ zone.name }}</span>
<span class="ml-auto gap-1 flex"> <span class="ml-auto gap-1 flex">
<button class="btn-red py-0.5 px-2.5 z-50" @click.stop="() => deleteZone(zone.id)">X</button> <button class="btn-red w-11 h-11 z-50" @click.stop="() => deleteZone(zone.id)">X</button>
</span> </span>
</div> </div>
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div> <div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</div> </div>
</div> </div>
</template> </template>
</Modal> </Modal>
</Teleport>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -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)

View File

@ -1,14 +1,15 @@
<template> <template>
<div class="w-full md:min-w-[350px] max-w-[750px] flex flex-col"> <div class="w-full md:min-w-[350px] max-w-[750px] flex flex-col absolute left-1/2 -translate-x-1/2 bottom-5">
<div ref="chatWindow" class="w-full overflow-auto h-32 mb-5 bg-gray-300/80 rounded-lg border-2 border-solid border-cyan-200" v-show="gameStore.uiSettings.isChatOpen"> <div ref="chatWindow" class="w-full overflow-auto h-32 mb-5 bg-gray rounded-md border-2 border-solid border-gray-500 text-gray-300" v-show="gameStore.uiSettings.isChatOpen">
<div v-for="message in chats" class="flex-col py-2 items-center p-3"> <div v-for="message in chats" class="flex-col py-2 items-center p-3">
<span class="text-ellipsis overflow-hidden whitespace-nowrap text-sm">{{ message.character.name }}</span> <span class="text-ellipsis overflow-hidden whitespace-nowrap text-sm" :class="{ 'text-cyan-300': gameStore.character?.role == 'gm' }">{{ message.character.name }}</span>
<p class="text-gray-50 m-0">{{ message.message }}</p> <p class="text-gray-50 m-0">{{ message.message }}</p>
</div> </div>
</div> </div>
<div class="w-full flex"> <div class="w-96 mx-auto relative">
<img src="/assets/icons/chat-icon.svg" class="absolute top-1/2 -translate-y-1/2 left-2.5 h-4 w-4 opacity-50" />
<input <input
class="w-full h-12 rounded-lg text-lg px-4 py-0 bg-gray border-2 border-solid border-gray-500 text-gray-300 bg-[url('/assets/icons/submit-icon.svg')] bg-no-repeat bg-[right_25px_center] bg-[length:30px] focus:outline-none focus:ring-0 focus:border-cyan-800" class="w-[332px] h-8 rounded-sm text-xs font-default pl-8 pr-4 py-0 bg-gray-600 border-2 border-solid border-gray-500 text-gray-300 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover focus:outline-none focus:ring-0 focus:border-cyan-800"
placeholder="Type something..." placeholder="Type something..."
v-model="message" v-model="message"
@keypress="handleKeyPress" @keypress="handleKeyPress"

View File

@ -1,6 +1,4 @@
<template> <template></template>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'

View File

@ -1,5 +1,16 @@
<template> <template>
<div class="absolute left-[66px] top-4 bg-[url('/assets/ui-rect-border-4-corners.svg')] bg-no-repeat px-4 py-2 w-[181px] h-[26px] flex flex-col justify-between">
<div class="w-full flex items-center gap-2">
<label class="text-xs leading-3 text-pixel" for="hp">HP</label>
<progress class="h-2 rounded-sm w-full max-w-44 appearance-none accent-green" id="hp" :value="gameStore.character?.hitpoints" max="100">{{ gameStore.character?.hitpoints }}%</progress>
<span class="text-xs leading-3 text-pixel">{{ gameStore.character?.hitpoints }}%</span>
</div>
<div class="w-full flex items-center gap-2">
<label class="text-xs leading-3 text-pixel" for="sp">SP</label>
<progress class="h-2 rounded-sm w-full max-w-44 appearance-none accent-blue" id="sp" :value="gameStore.character?.mana" max="100">{{ gameStore.character?.mana }}%</progress>
<span class="text-xs leading-3 text-pixel">{{ gameStore.character?.mana }}%</span>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -9,41 +20,30 @@ const gameStore = useGameStore()
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.hud-wrapper { #hp {
.hud-bg {
mask: url('/assets/shapes/hud-image-shape.svg') center/cover no-repeat;
}
#hp {
// Chrome, Safari, Edge, Opera // Chrome, Safari, Edge, Opera
&::-webkit-progress-value { &::-webkit-progress-value {
@apply bg-red rounded-lg; @apply bg-gradient-to-r from-green from-75% to-green-200 rounded-sm;
} }
&::-webkit-progress-bar { &::-webkit-progress-bar {
@apply bg-white rounded-lg border-2 border-solid border-white; @apply bg-white rounded-sm border border-solid border-black;
} }
// Firefox // Firefox
&::-moz-progress-bar { &::-moz-progress-bar {
@apply bg-red rounded-lg border-2 border-solid border-white; @apply bg-gradient-to-r from-green from-75% to-green-200 rounded-sm border border-solid border-black;
} }
} }
#mp { #sp {
// Chrome, Safari, Edge, Opera // Chrome, Safari, Edge, Opera
&::-webkit-progress-value { &::-webkit-progress-value {
@apply bg-blue rounded-lg; @apply bg-gradient-to-r from-blue from-75% to-blue-200 rounded-sm;
} }
&::-webkit-progress-bar { &::-webkit-progress-bar {
@apply bg-white rounded-lg border-2 border-solid border-white; @apply bg-white rounded-sm border border-solid border-black;
} }
// Firefox // Firefox
&::-moz-progress-bar { &::-moz-progress-bar {
@apply bg-blue rounded-lg border-2 border-solid border-white; @apply bg-gradient-to-r from-blue from-75% to-blue-200 rounded-sm border border-solid border-black;
}
}
&.other-player {
.hud-bg {
mask: url('/assets/shapes/hud-image-shape-flipped.svg') center/cover no-repeat;
}
} }
} }
</style> </style>

View File

@ -1,5 +1,44 @@
<template> <template>
<div class="absolute left-[300px] top-4">
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
<span class="z-10 text-pixel absolute top-1 left-2">F1</span>
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f1-icon.png')] bg-no-repeat"></div>
</div>
<div class="absolute left-[346px] top-4">
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
<span class="z-10 text-pixel absolute top-1 left-2">F2</span>
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f2-icon.png')] bg-no-repeat"></div>
</div>
<div class="absolute left-[392px] top-4">
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
<span class="z-10 text-pixel absolute top-1 left-2">F3</span>
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f3-icon.png')] bg-no-repeat"></div>
</div>
<div class="absolute left-[438px] top-4">
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
<span class="z-10 text-pixel absolute top-1 left-2">F4</span>
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f4-icon.png')] bg-no-repeat"></div>
</div>
<div class="absolute left-[484px] top-4">
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
<span class="z-10 text-pixel absolute top-1 left-2">F5</span>
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f5-icon.png')] bg-no-repeat"></div>
</div>
<div class="absolute left-[530px] top-4">
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
<span class="z-10 text-pixel absolute top-1 left-2">F6</span>
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f6-icon.png')] bg-no-repeat"></div>
</div>
<div class="absolute left-[576px] top-4">
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
<span class="z-10 text-pixel absolute top-1 left-2">F7</span>
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f7-icon.png')] bg-no-repeat"></div>
</div>
<div class="absolute left-[622px] top-4">
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
<span class="z-10 text-pixel absolute top-1 left-2">F8</span>
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f8-icon.png')] bg-no-repeat"></div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -1,39 +1,49 @@
<template> <template>
<ul class="list-none flex gap-2.5 items-center m-0 max-md:pl-0"> <ul class="list-none flex flex-col gap-2.5 items-center m-0 pl-0 absolute left-4 top-4">
<li class="menu-item group relative" @click="gameStore.toggleChat">
<div class="group-hover:block absolute bottom-16 left-1/2 -translate-x-1/2 w-20 h-6 text-center bg-gray-300 border-2 border-solid border-cyan rounded-3xl hidden">
<p class="absolute w-full bottom-0 m-0 text-xs leading-6">Chat</p>
<div class="group-hover:block absolute -bottom-2.5 bg-cyan h-2 w-3.5 [clip-path:polygon(100%_0,_0_0,_50%_100%)] left-1/2 -translate-x-1/2 hidden"></div>
</div>
<a class="group-hover:bg-gray/70 group-hover:cursor-pointer p-1.5 bg-gray-300/70 border-2 border-solid border-cyan-200 hover:border-cyan rounded-lg block w-11 h-9">
<img class="group-hover:drop-shadow-default w-11 h-9 object-contain" draggable="false" src="/assets/icons/chat.png" />
</a>
</li>
<li class="menu-item group relative"> <li class="menu-item group relative">
<div class="group-hover:block absolute bottom-16 left-1/2 -translate-x-1/2 w-20 h-6 text-center bg-gray-300 border-2 border-solid border-cyan rounded-3xl hidden"> <div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
<p class="absolute w-full bottom-0 m-0 text-xs leading-6">World</p> <p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">Open menu</p>
<div class="group-hover:block absolute -bottom-2.5 bg-cyan h-2 w-3.5 [clip-path:polygon(100%_0,_0_0,_50%_100%)] left-1/2 -translate-x-1/2 hidden"></div> <div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
</div> </div>
<a class="group-hover:bg-gray/70 group-hover:cursor-pointer p-1.5 bg-gray-300/70 border-2 border-solid border-cyan-200 hover:border-cyan rounded-lg block w-11 h-9"> <a class="group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners.svg')] bg-no-repeat block w-[42px] h-[42px] relative">
<img class="group-hover:drop-shadow-default w-11 h-9 object-contain" draggable="false" src="/assets/icons/world.png" /> <img class="group-hover:drop-shadow-default w-6 h-5 mx-[9px] my-[11px] object-contain" draggable="false" src="/assets/icons/menu-icon.svg" />
</a>
</li>
<li class="menu-item group relative">
<div class="group-hover:block absolute bottom-16 left-1/2 -translate-x-1/2 w-20 h-6 text-center bg-gray-300 border-2 border-solid border-cyan rounded-3xl hidden">
<p class="absolute w-full bottom-0 m-0 text-xs leading-6">Users</p>
<div class="group-hover:block absolute -bottom-2.5 bg-cyan h-2 w-3.5 [clip-path:polygon(100%_0,_0_0,_50%_100%)] left-1/2 -translate-x-1/2 hidden"></div>
</div>
<a class="group-hover:bg-gray/70 group-hover:cursor-pointer p-1.5 bg-gray-300/70 border-2 border-solid border-cyan-200 hover:border-cyan rounded-lg block w-11 h-9">
<img class="group-hover:drop-shadow-default w-11 h-9 object-contain" draggable="false" src="/assets/icons/users.png" />
</a> </a>
</li> </li>
<li class="menu-item group relative" @click="gameStore.toggleUserPanel"> <li class="menu-item group relative" @click="gameStore.toggleUserPanel">
<div class="group-hover:block absolute bottom-16 left-1/2 -translate-x-1/2 w-20 h-6 text-center bg-gray-300 border-2 border-solid border-cyan rounded-3xl hidden"> <div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
<p class="absolute w-full bottom-0 m-0 text-xs leading-6">Inventory</p> <p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">User Profile</p>
<div class="group-hover:block absolute -bottom-2.5 bg-cyan h-2 w-3.5 [clip-path:polygon(100%_0,_0_0,_50%_100%)] left-1/2 -translate-x-1/2 hidden"></div> <div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
</div> </div>
<a class="group-hover:bg-gray/70 group-hover:cursor-pointer p-1.5 bg-gray-300/70 border-2 border-solid border-cyan-200 hover:border-cyan rounded-lg block w-11 h-9"> <a class="group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners.svg')] bg-no-repeat block w-[42px] h-[42px] relative">
<img class="group-hover:drop-shadow-default w-11 h-9 object-contain" draggable="false" src="/assets/icons/treasure-chest.png" /> <img class="group-hover:drop-shadow-default w-8 h-8 m-[5px] object-contain" draggable="false" src="/assets/avatar/default/head.png" />
<p class="absolute bottom-0 -right-1.5 m-0 max-w-4 font-ui z-10 text-white text-[12px] leading-[6px] drop-shadow-pixel"><span class="font-ui text-white text-[8px] ml-0.5">LVL</span> {{ characterLevel }}</p>
</a>
</li>
<li class="menu-item group relative" @click="gameStore.toggleChat">
<div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">Open Chat</p>
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
</div>
<a class="group-hover:bg-gray-800 group-hover:cursor-pointer border border-b-4 border-solid rounded border-gray-500 block w-[34px] h-[31px]">
<img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/chat-icon.svg" />
</a>
</li>
<li class="menu-item group relative">
<div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">World map</p>
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
</div>
<a class="group-hover:bg-gray-800 group-hover:cursor-pointer border border-b-4 border-solid rounded border-gray-500 block w-[34px] h-[31px]">
<img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/map-icon.svg" />
</a>
</li>
<li class="menu-item group relative">
<div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">Users</p>
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
</div>
<a class="group-hover:bg-gray-800 group-hover:cursor-pointer border border-b-4 border-solid rounded border-gray-500 block w-[34px] h-[31px]">
<img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/socials-icon.svg" />
</a> </a>
</li> </li>
</ul> </ul>
@ -43,4 +53,5 @@
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
const gameStore = useGameStore() const gameStore = useGameStore()
let characterLevel = gameStore.character?.level.toString().padStart(2, '0')
</script> </script>

View File

@ -1,5 +1,19 @@
<template> <template>
<div class="absolute top-4 right-4">
<div class="w-40 h-40 rounded-full border border-solid border-gray-500 bg-[url('/assets/ui-texture.png')] bg-no-repeat">
<div class="w-40 h-40 rounded-full shadow-inner"></div>
</div>
<div class="absolute -bottom-3 left-1/2 -translate-x-1/2 flex gap-1">
<button class="w-6 h-6 relative p-0">
<img class="absolute w-3 h-3 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" src="/assets/icons/plus-icon.svg" />
<img class="w-full h-full" src="/assets/ui-border-4-corners.svg" />
</button>
<button class="w-6 h-6 relative p-0">
<img class="absolute w-3 h-3 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" src="/assets/icons/minus-icon.svg" />
<img class="w-full h-full" src="/assets/ui-border-4-corners.svg" />
</button>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="absolute z-50 w-full h-dvh top-0 left-0 bg-black/60" v-show="gameStore.uiSettings.isUserPanelOpen"> <div class="absolute z-50 w-full h-dvh top-0 left-0 bg-black/60" v-show="gameStore.uiSettings.isUserPanelOpen">
<div class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 max-w-[875px] max-h-[600px] h-full w-[80%] bg-gray-300/80 border-solid border-2 border-cyan-200 rounded-lg z-50 flex flex-col backdrop-blur-sm shadow-lg"> <div class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 max-w-[875px] max-h-[600px] h-full w-[80%] bg-gray-700 border-solid border-2 border-gray-500 rounded-md z-50 flex flex-col backdrop-blur-sm shadow-lg">
<div class="p-2.5 flex max-sm:flex-wrap justify-between items-center gap-5 border-solid border-0 border-b border-cyan-200"> <div class="p-2.5 flex max-sm:flex-wrap justify-between items-center gap-5 border-solid border-0 border-b border-gray-500">
<h3 class="m-0 font-medium shrink-0">Game menu</h3> <h3 class="m-0 font-medium shrink-0">Game menu</h3>
<div class="hidden sm:flex gap-1.5 flex-wrap"> <div class="hidden sm:flex gap-1.5 flex-wrap">
<button @click.stop="userPanelScreen = 'inventory'" :class="{ active: userPanelScreen === 'inventory' }" class="btn-cyan py-1.5 px-4 min-w-24">Inventory</button> <button @click.stop="userPanelScreen = 'inventory'" :class="{ active: userPanelScreen === 'inventory' }" class="btn-cyan py-1.5 px-4 min-w-24">Inventory</button>
@ -10,7 +10,7 @@
<button @click.stop="userPanelScreen = 'settings'" :class="{ active: userPanelScreen === 'settings' }" class="btn-cyan py-1.5 px-4 min-w-24">Settings</button> <button @click.stop="userPanelScreen = 'settings'" :class="{ active: userPanelScreen === 'settings' }" class="btn-cyan py-1.5 px-4 min-w-24">Settings</button>
</div> </div>
<div class="flex gap-2.5"> <div class="flex gap-2.5">
<button class="w-5 h-5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out" @click="gameStore.toggleUserPanel"> <button class="w-3.5 h-3.5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out" @click="gameStore.toggleUserPanel">
<img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" class="w-full h-full" /> <img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" class="w-full h-full" />
</button> </button>
</div> </div>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -38,24 +38,20 @@ const modalOpened = ref(props.modalOpened)
</script> </script>
<template> <template>
<Modal :closable="false" :is-resizable="false" :isModalOpen="true" @modal:close="() => (modalOpened = !modalOpened)" :modal-width="300" :modal-height="190"> <Modal :closable="false" :is-resizable="false" :isModalOpen="true" @modal:close="() => (modalOpened = !modalOpened)" :modal-width="350" :modal-height="230">
<template #modalHeader> <template #modalHeader>
<div class="text-gray-300">
<slot name="modalHeader"></slot> <slot name="modalHeader"></slot>
</div>
</template> </template>
<template #modalBody> <template #modalBody>
<div class="text-gray-300 h-full"> <div class="h-[calc(100%_-_32px)] p-4">
<div class="flex h-full flex-col justify-between"> <div class="h-full flex flex-col justify-between">
<span class="p-2">
<slot name="modalBody"></slot> <slot name="modalBody"></slot>
</span> <div class="grid grid-flow-col justify-stretch gap-4">
<div class="flex justify-between p-2"> <button class="btn-empty py-1.5 px-4 min-w-24 inline-block" @click="props.cancelFunction()">
<button class="btn-cyan py-1.5 px-4 min-w-24 inline-block" @click="props.cancelFunction()">
{{ props.cancelButtonText }} {{ props.cancelButtonText }}
</button> </button>
<button class="btn-cyan py-1.5 px-4 min-w-24 inline-block" type="submit" @click="props.confirmFunction()"> <button class="btn-red py-1.5 px-4 min-w-24 inline-block" type="submit" @click="props.confirmFunction()">
{{ props.confirmButtonText }} {{ props.confirmButtonText }}
</button> </button>
</div> </div>

View File

@ -1,23 +1,26 @@
<template> <template>
<Teleport to="body"> <Teleport to="body">
<div v-if="isModalOpenRef" class="fixed bg-gray border-solid border-2 border-gray-500 z-50 flex flex-col backdrop-blur-sm shadow-lg" :style="modalStyle"> <div v-if="isModalOpenRef" class="fixed border-solid border-2 border-gray-500 z-50 flex flex-col backdrop-blur-sm shadow-lg" :style="modalStyle">
<div @mousedown="startDrag" class="cursor-move p-2.5 flex justify-between items-center border-solid border-0 border-b border-gray-500"> <div @mousedown="startDrag" class="cursor-move p-2.5 flex justify-between items-center border-solid border-0 border-b border-gray-500 relative">
<div class="rounded-t-md absolute w-full h-full top-0 left-0 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-center bg-cover opacity-90"></div>
<div class="relative z-10">
<slot name="modalHeader" /> <slot name="modalHeader" />
</div>
<div class="flex gap-2.5"> <div class="flex gap-2.5">
<button @click="toggleFullScreen" class="w-5 h-5 m-0 p-0 relative hover:scale-110 transition-transform duration-300 ease-in-out" v-if="canFullScreen"> <button @click="toggleFullScreen" class="w-5 h-5 m-0 p-0 relative hover:scale-110 transition-transform duration-300 ease-in-out" v-if="canFullScreen">
<img :alt="isFullScreen ? 'exit full-screen' : 'full-screen'" draggable="false" :src="isFullScreen ? '/assets/icons/minimize.svg' : '/assets/icons/full-screen.svg'" class="w-full h-full invert" /> <img :alt="isFullScreen ? 'exit full-screen' : 'full-screen'" draggable="false" :src="isFullScreen ? '/assets/icons/minimize.svg' : '/assets/icons/full-screen.svg'" class="w-3.5 h-3.5 invert" />
</button> </button>
<button @click="close" v-if="closable" class="w-5 h-5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out"> <button @click="close" v-if="closable" class="w-3.5 h-3.5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out">
<img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" class="w-full h-full" /> <img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" class="w-full h-full" />
</button> </button>
</div> </div>
</div> </div>
<div class="overflow-hidden grow"> <div class="overflow-hidden grow relative">
<div class="rounded-b-md absolute w-full h-full top-0 left-0 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover bg-center opacity-90"></div>
<div class="relative z-10 h-full">
<slot name="modalBody" /> <slot name="modalBody" />
<img v-if="isResizable && !isFullScreen" src="/assets/icons/resize-icon.svg" alt="resize" class="absolute bottom-0 right-0 w-5 h-5 cursor-nwse-resize invert-[60%]" @mousedown="startResize" />
</div> </div>
<div v-if="$slots.modalFooter" class="px-5 min-h-12 flex justify-end gap-7.5 items-center border-solid border-t border-gray-500"> <img v-if="isResizable && !isFullScreen" src="/assets/icons/resize-icon.svg" alt="resize" class="absolute z-10 bottom-0 right-0 w-5 h-5 cursor-nwse-resize" @mousedown="startResize" />
<slot name="modalFooter" />
</div> </div>
</div> </div>
</Teleport> </Teleport>
@ -84,7 +87,7 @@ let startHeight = 0
let preFullScreenState = { x: 0, y: 0, width: 0, height: 0 } let preFullScreenState = { x: 0, y: 0, width: 0, height: 0 }
const modalStyle = computed(() => ({ const modalStyle = computed(() => ({
borderRadius: isFullScreen.value ? '0' : '10px', borderRadius: isFullScreen.value ? '0' : '6px',
top: isFullScreen.value ? '0' : `${y.value}px`, top: isFullScreen.value ? '0' : `${y.value}px`,
left: isFullScreen.value ? '0' : `${x.value}px`, left: isFullScreen.value ? '0' : `${x.value}px`,
width: isFullScreen.value ? '100vw' : `${width.value}px`, width: isFullScreen.value ? '100vw' : `${width.value}px`,

View File

@ -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>

View File

@ -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)

View File

@ -52,16 +52,15 @@ function createTileLayer() {
} }
function createTileArray() { function createTileArray() {
return Array.from({ length: zoneStore.zone?.width ?? 0 }, () => Array.from({ length: zoneStore.zone?.height ?? 0 }, () => 'blank_tile')) return Array.from({ length: zoneTilemap.height || 0 }, () => Array.from({ length: zoneTilemap.width || 0 }, () => 'blank_tile'))
} }
onBeforeMount(() => { onBeforeMount(() => {
if (zoneStore.zone?.tiles) { if (!zoneStore.zone?.tiles) {
return
}
setAllTiles(zoneTilemap, tiles, zoneStore.zone.tiles) setAllTiles(zoneTilemap, tiles, zoneStore.zone.tiles)
tileArray = zoneStore.zone.tiles.map((row) => row.map((tileId) => tileId || 'blank_tile')) tileArray = zoneStore.zone.tiles.map((row) => row.map((tileId) => tileId || 'blank_tile'))
} else {
tileArray.forEach((row, y) => row.forEach((_, x) => placeTile(zoneTilemap, tiles, x, y, 'blank_tile')))
}
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {

View File

@ -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,

View File

@ -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)

View File

@ -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()

View File

@ -5,14 +5,9 @@
<div class="filler"></div> <div class="filler"></div>
<div class="flex gap-14 w-full max-h-[650px] overflow-x-auto" v-if="!isLoading"> <div class="flex gap-14 w-full max-h-[650px] overflow-x-auto" v-if="!isLoading">
<!-- CHARACTER LIST --> <!-- CHARACTER LIST -->
<div <div v-for="character in characters" :key="character.id" class="group first:ml-auto last:mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 relative shadow-character" :class="{ active: selected_character == character.id }">
v-for="character in characters" <img src="/assets/ui-box-outer.svg" class="absolute w-full h-full" />
:key="character.id" <img src="/assets/ui-box-inner.svg" class="absolute left-2 bottom-2 w-[calc(100%_-_16px)] h-[calc(100%_-_40px)]" />
class="group first:ml-auto last:mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 relative shadow-character"
:class="{ active: selected_character == character.id }"
>
<img src="/assets/login/login-box-outer.svg" class="absolute w-full h-full max-lg:hidden" />
<img src="/assets/login/login-box-inner.svg" class="absolute left-2 bottom-2 w-[calc(100%_-_16px)] h-[calc(100%_-_40px)] max-lg:hidden" />
<input class="opacity-0 h-full w-full absolute m-0 z-10" type="radio" :id="character.id" name="character" :value="character.id" v-model="selected_character" /> <input class="opacity-0 h-full w-full absolute m-0 z-10" type="radio" :id="character.id" name="character" :value="character.id" v-model="selected_character" />
<label class="font-bold absolute left-1/2 top-4 max-w-32 -translate-x-1/2 -translate-y-1/2 text-center text-ellipsis overflow-hidden whitespace-nowrap drop-shadow-text" :for="character.id">{{ character.name }}</label> <label class="font-bold absolute left-1/2 top-4 max-w-32 -translate-x-1/2 -translate-y-1/2 text-center text-ellipsis overflow-hidden whitespace-nowrap drop-shadow-text" :for="character.id">{{ character.name }}</label>
@ -66,21 +61,23 @@
</div> </div>
<!-- CREATE CHARACTER MODAL --> <!-- CREATE CHARACTER MODAL -->
<Modal :isModalOpen="isModalOpen" @modal:close="isModalOpen = false"> <Modal :isModalOpen="isModalOpen" @modal:close="isModalOpen = false" :modal-width="430" :modal-height="275">
<template #modalHeader> <template #modalHeader>
<h3 class="m-0 font-medium text-gray-300">Create your character</h3> <h3 class="m-0 font-medium text-white">Create your character</h3>
</template> </template>
<template #modalBody> <template #modalBody>
<div class="m-4 character-form"> <div class="p-4 h-[calc(100%_-_32px)]">
<form method="post" @submit.prevent="create" class="inline"> <form method="post" @submit.prevent="create" class="h-full flex flex-col justify-between">
<div class="form-field-full"> <div class="form-field-full">
<label for="name">Name</label> <label for="name" class="text-white">Nickname</label>
<input class="input-field max-w-64" v-model="name" name="name" id="name" /> <input class="input-field" v-model="name" name="name" id="name" placeholder="Enter a nickname.." />
</div>
<div class="grid grid-flow-col justify-stretch gap-4">
<button type="button" class="btn-empty py-1.5 px-4 inline-block" @click.prevent="isModalOpen = false">Cancel</button>
<button class="btn-cyan py-1.5 px-4 inline-block" type="submit">Create</button>
</div> </div>
<button class="btn-cyan py-1.5 px-4 mr-5 min-w-24 inline-block" type="submit">CREATE</button>
</form> </form>
<button class="btn-cyan py-1.5 px-4 min-w-24 inline-block" @click="isModalOpen = false">CANCEL</button>
</div> </div>
</template> </template>
</Modal> </Modal>
@ -88,11 +85,13 @@
<!-- DELETE CHARACTER MODAL --> <!-- DELETE CHARACTER MODAL -->
<ConfirmationModal v-if="deletingCharacter != null" :confirm-function="delete_character.bind(this, deletingCharacter.id)" :cancel-function="(() => (deletingCharacter = null)).bind(this)" confirm-button-text="Delete"> <ConfirmationModal v-if="deletingCharacter != null" :confirm-function="delete_character.bind(this, deletingCharacter.id)" :cancel-function="(() => (deletingCharacter = null)).bind(this)" confirm-button-text="Delete">
<template #modalHeader> <template #modalHeader>
<h3 class="m-0 font-medium text-gray-300">Deleting character</h3> <h3 class="m-0 font-medium text-white">Delete character?</h3>
</template> </template>
<template #modalBody> <template #modalBody>
You are about to delete <span class="font-extrabold">{{ deletingCharacter.name }}</span <p class="mt-0 mb-5 text-white text-lg">
>, are you sure about that? Do you want to permanently delete <span class="font-extrabold text-white">{{ deletingCharacter.name }}</span
>?
</p>
</template> </template>
</ConfirmationModal> </ConfirmationModal>
</template> </template>

View File

@ -3,7 +3,6 @@
<GmTools v-if="gameStore.character?.role === 'gm'" /> <GmTools v-if="gameStore.character?.role === 'gm'" />
<GmPanel v-if="gameStore.character?.role === 'gm'" /> <GmPanel v-if="gameStore.character?.role === 'gm'" />
<div v-if="!zoneEditorStore.active">
<Game :config="gameConfig" @create="createGame"> <Game :config="gameConfig" @create="createGame">
<Scene name="main" @preload="preloadScene" @create="createScene"> <Scene name="main" @preload="preloadScene" @create="createScene">
<div v-if="isLoaded"> <div v-if="isLoaded">
@ -13,30 +12,22 @@
<Minimap /> <Minimap />
<Zone /> <Zone />
<Chat /> <Chat />
<Inventory />
<ExpBar /> <ExpBar />
<Inventory />
<Effects /> <Effects />
</div> </div>
</Scene> </Scene>
</Game> </Game>
</div> </div>
<div v-if="zoneEditorStore.active">
<Game :config="gameConfig" @create="createGame">
<Scene name="main" @preload="preloadScene" @create="createScene">
<ZoneEditor v-if="isLoaded" :key="JSON.stringify(`${zoneEditorStore.zone?.id}_${zoneEditorStore.zone?.createdAt}_${zoneEditorStore.zone?.updatedAt}`)" />
</Scene>
</Game>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import config from '@/config'
import 'phaser' import 'phaser'
import { ref, onBeforeUnmount } from 'vue' import { ref, onBeforeUnmount } from 'vue'
import { Game, Scene } from 'phavuer' import { Game, Scene } from 'phavuer'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
import Menu from '@/components/gui/Menu.vue' import Menu from '@/components/gui/Menu.vue'
import ExpBar from '@/components/gui/ExpBar.vue' import ExpBar from '@/components/gui/ExpBar.vue'
import Hud from '@/components/gui/Hud.vue' import Hud from '@/components/gui/Hud.vue'
@ -44,20 +35,17 @@ import Zone from '@/components/zone/Zone.vue'
import Keybindings from '@/components/gui/Keybindings.vue' import Keybindings from '@/components/gui/Keybindings.vue'
import Chat from '@/components/gui/Chat.vue' import Chat from '@/components/gui/Chat.vue'
import GmTools from '@/components/gameMaster/GmTools.vue' import GmTools from '@/components/gameMaster/GmTools.vue'
import ZoneEditor from '@/components/gameMaster/zoneEditor/ZoneEditor.vue'
import GmPanel from '@/components/gameMaster/GmPanel.vue' import GmPanel from '@/components/gameMaster/GmPanel.vue'
import Inventory from '@/components/gui/UserPanel.vue' import Inventory from '@/components/gui/UserPanel.vue'
import Effects from '@/components/Effects.vue' import Effects from '@/components/Effects.vue'
import { loadAssets } from '@/composables/zoneComposable' import { loadAssets } from '@/composables/zoneComposable'
import Minimap from '@/components/gui/Minimap.vue' import Minimap from '@/components/gui/Minimap.vue'
const gameStore = useGameStore() const gameStore = useGameStore()
const zoneEditorStore = useZoneEditorStore()
const isLoaded = ref(false) const isLoaded = ref(false)
const gameConfig = { const gameConfig = {
name: 'New Quest', name: config.name,
width: window.innerWidth, width: window.innerWidth,
height: window.innerHeight, height: window.innerHeight,
type: Phaser.AUTO, // AUTO, CANVAS, WEBGL, HEADLESS type: Phaser.AUTO, // AUTO, CANVAS, WEBGL, HEADLESS
@ -154,7 +142,6 @@ const createScene = async (scene: Phaser.Scene) => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
isLoaded.value = false isLoaded.value = false
gameStore.disconnectSocket()
}) })
</script> </script>

View File

@ -4,23 +4,23 @@
<div class="bg-[url('/assets/login/login-bg.png')] w-full lg:w-1/2 h-[35dvh] lg:h-dvh absolute right-0 max-lg:bottom-0 lg:top-0 bg-no-repeat bg-cover bg-center"></div> <div class="bg-[url('/assets/login/login-bg.png')] w-full lg:w-1/2 h-[35dvh] lg:h-dvh absolute right-0 max-lg:bottom-0 lg:top-0 bg-no-repeat bg-cover bg-center"></div>
<div class="bg-gray-900 z-20 w-full lg:w-1/2 h-[65dvh] lg:h-dvh relative"> <div class="bg-gray-900 z-20 w-full lg:w-1/2 h-[65dvh] lg:h-dvh relative">
<div class="h-dvh flex items-center lg:justify-center flex-col px-8 max-lg:pt-20"> <div class="h-dvh flex items-center lg:justify-center flex-col px-8 max-lg:pt-20">
<img src="/assets/login/nq-logo-v1.png" class="mb-10" /> <img src="/assets/login/sq-logo-v1.svg" class="mb-10" />
<div class="relative"> <div class="relative">
<img src="/assets/login/login-box-outer.svg" class="absolute w-full h-full max-lg:hidden" /> <img src="/assets/ui-box-outer.svg" class="absolute w-full h-full" />
<img src="/assets/login/login-box-inner.svg" class="absolute left-2 top-2 w-[calc(100%_-_16px)] h-[calc(100%_-_16px)] max-lg:hidden" /> <img src="/assets/ui-box-inner.svg" class="absolute left-2 top-2 w-[calc(100%_-_16px)] h-[calc(100%_-_16px)] max-lg:hidden" />
<!-- Login Form --> <!-- Login Form -->
<form v-show="switchForm === 'login'" @submit.prevent="loginFunc" class="relative px-6 py-11"> <form v-show="switchForm === 'login'" @submit.prevent="loginFunc" class="relative px-6 py-11">
<div class="flex flex-col gap-5 p-2 mb-8 relative"> <div class="flex flex-col gap-5 p-2 mb-8 relative">
<div class="w-full grid gap-3 relative"> <div class="w-full grid gap-3 relative">
<input class="input-field xs:min-w-[350px] min-w-64" id="username" v-model="username" type="text" name="username" placeholder="Username" required autofocus /> <input class="input-field xs:min-w-[350px] min-w-64" id="username-login" v-model="username" type="text" name="username" placeholder="Username" required autofocus />
<div class="relative"> <div class="relative">
<input class="input-field xs:min-w-[350px] min-w-64" id="password" v-model="password" type="password" name="password" placeholder="Password" required /> <input class="input-field xs:min-w-[350px] min-w-64" id="password-login" v-model="password" :type="showPassword ? 'text' : 'password'" name="password" placeholder="Password" required />
<button class="p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-[url('/assets/icons/eye.svg')] bg-no-repeat"></button> <button type="button" @click.prevent="showPassword = !showPassword" :class="{ 'eye-open': showPassword }" class="bg-[url('/assets/icons/eye.svg')] p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-no-repeat"></button>
</div> </div>
<span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span> <span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span>
</div> </div>
<button class="text-right text-cyan-300 text-base">Forgot password?</button> <button class="inline-flex self-end p-0 text-cyan-300 text-base">Forgot password?</button>
<button class="btn-cyan px-0 xs:w-full" type="submit">Play now</button> <button class="btn-cyan px-0 xs:w-full" type="submit">Play now</button>
<!-- Divider shape --> <!-- Divider shape -->
@ -39,10 +39,10 @@
<form v-show="switchForm === 'register'" @submit.prevent="registerFunc" class="relative px-6 py-11"> <form v-show="switchForm === 'register'" @submit.prevent="registerFunc" class="relative px-6 py-11">
<div class="flex flex-col gap-5 p-2 mb-8 relative"> <div class="flex flex-col gap-5 p-2 mb-8 relative">
<div class="w-full grid gap-3 relative"> <div class="w-full grid gap-3 relative">
<input class="input-field xs:min-w-[350px] min-w-64" id="username" v-model="username" type="text" name="username" placeholder="Username" required autofocus /> <input class="input-field xs:min-w-[350px] min-w-64" id="username-register" v-model="username" type="text" name="username" placeholder="Username" required autofocus />
<div class="relative"> <div class="relative">
<input class="input-field xs:min-w-[350px] min-w-64" id="password" v-model="password" type="password" name="password" placeholder="Password" required /> <input class="input-field xs:min-w-[350px] min-w-64" id="password-register" v-model="password" :type="showPassword ? 'text' : 'password'" name="password" placeholder="Password" required />
<button class="p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-[url('/assets/icons/eye.svg')] bg-no-repeat"></button> <button type="button" @click.prevent="showPassword = !showPassword" :class="{ 'eye-open': showPassword }" class="bg-[url('/assets/icons/eye.svg')] p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-no-repeat"></button>
</div> </div>
<span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span> <span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span>
</div> </div>
@ -76,6 +76,8 @@ const username = ref('')
const password = ref('') const password = ref('')
const switchForm = ref('login') const switchForm = ref('login')
const loginError = ref('') const loginError = ref('')
const showPassword = ref(false)
// automatic login because of development // automatic login because of development
onMounted(async () => { onMounted(async () => {
const token = useCookies().get('token') const token = useCookies().get('token')

View File

@ -1 +0,0 @@
<template></template>

137
src/screens/ZoneEditor.vue Normal file
View 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>

View File

@ -18,6 +18,7 @@ export async function login(username: string, password: string, gameStore = useG
const response = await axios.post(`${config.server_endpoint}/login`, { username, password }) const response = await axios.post(`${config.server_endpoint}/login`, { username, password })
useCookies().set('token', response.data.token as string, { useCookies().set('token', response.data.token as string, {
// for whole domain // for whole domain
// @TODO : #190
domain: window.location.hostname.split('.').slice(-2).join('.') domain: window.location.hostname.split('.').slice(-2).join('.')
}) })
return { success: true, token: response.data.token } return { success: true, token: response.data.token }

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { io, Socket } from 'socket.io-client' import { io, Socket } from 'socket.io-client'
import type { Asset, Character, Notification, User } from '@/types' import type { Asset, Character, Notification, User, WorldSettings } from '@/types'
import config from '@/config' import config from '@/config'
import { useCookies } from '@vueuse/integrations/useCookies' import { useCookies } from '@vueuse/integrations/useCookies'
@ -15,6 +15,12 @@ export const useGameStore = defineStore('game', {
user: null as User | null, user: null as User | null,
character: null as Character | null, character: null as Character | null,
isPlayerDraggingCamera: false, isPlayerDraggingCamera: false,
world: {
date: new Date(),
isRainEnabled: false,
isFogEnabled: false,
fogDensity: 0.5
} as WorldSettings,
gameSettings: { gameSettings: {
isCameraFollowingCharacter: false isCameraFollowingCharacter: false
}, },
@ -150,6 +156,7 @@ export const useGameStore = defineStore('game', {
useCookies().remove('token', { useCookies().remove('token', {
// for whole domain // for whole domain
// @TODO : #190
domain: window.location.hostname.split('.').slice(-2).join('.') domain: window.location.hostname.split('.').slice(-2).join('.')
}) })
@ -163,6 +170,11 @@ export const useGameStore = defineStore('game', {
this.gameSettings.isCameraFollowingCharacter = false this.gameSettings.isCameraFollowingCharacter = false
this.uiSettings.isChatOpen = false this.uiSettings.isChatOpen = false
this.uiSettings.isUserPanelOpen = false this.uiSettings.isUserPanelOpen = false
this.world.date = new Date()
this.world.isRainEnabled = false
this.world.isFogEnabled = false
this.world.fogDensity = 0.5
} }
} }
}) })

View File

@ -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

View File

@ -215,3 +215,10 @@ export type ChatMessage = {
character: Character character: Character
message: string message: string
} }
export type WorldSettings = {
date: Date
isRainEnabled: boolean
isFogEnabled: boolean
fogDensity: number
}

View File

@ -4,7 +4,8 @@ export default {
theme: { theme: {
fontFamily: { fontFamily: {
'titles': ['Poppins', 'serif'], 'titles': ['Poppins', 'serif'],
'default': ['Quicksand', 'serif'] 'default': ['Quicksand', 'serif'],
'ui': ['Upheaval', 'serif'],
}, },
backgroundSize: { backgroundSize: {
'cover': 'cover', 'cover': 'cover',
@ -43,10 +44,13 @@ export default {
dropShadow: { dropShadow: {
'default': '0 3px 6px rgb(0, 0, 0)', 'default': '0 3px 6px rgb(0, 0, 0)',
'text': '1px 1px 5px rgba(0, 0, 0, 0.25)', 'text': '1px 1px 5px rgba(0, 0, 0, 0.25)',
'pixel': '2px 2px 0px rgb(77, 77, 77)',
'pixel-black': '2px 2px 0px rgb(0, 0, 0)',
'20': '0 3px 6px rgba(0, 0, 0, 0.2)' '20': '0 3px 6px rgba(0, 0, 0, 0.2)'
}, },
boxShadow: { boxShadow: {
'character': '0 4px 30px rgba(0, 0, 0, 0.1)', 'character': '0 4px 30px rgba(0, 0, 0, 0.1)',
'inner': 'inset 0 0 12px 8px rgb(0, 0, 0)',
}, },
colors: { colors: {
cyan: { cyan: {
@ -68,10 +72,14 @@ export default {
400: '#332426' 400: '#332426'
}, },
blue: { blue: {
DEFAULT: '#00c2ff' DEFAULT: '#4F5FF0',
100: '#4F5FF0',
200: '#2E378A'
}, },
green: { green: {
DEFAULT: '#09ad19' DEFAULT: '#05E300',
100: '#05E300',
200: '#027D00'
}, },
purple: { purple: {
DEFAULT: '#9841e6' DEFAULT: '#9841e6'

View File

@ -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: [

View File

@ -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/