Compare commits
151 Commits
feature/13
...
feature/#2
Author | SHA1 | Date | |
---|---|---|---|
3748c459f8 | |||
50ea3ecdab | |||
8910390f7b | |||
d820490b2b | |||
2c96caee4f | |||
84939a7d32 | |||
1e3fc2b0f8 | |||
7c8b5f3e82 | |||
570d315bf5 | |||
7871b34c60 | |||
85d64f23eb | |||
bdb6dd0d54 | |||
faf887163a | |||
dd5baa530d | |||
d9947e29cf | |||
1888521762 | |||
48fef2313b | |||
0a99d2c430 | |||
ed6f592606 | |||
46ebfaec01 | |||
1384f50406 | |||
d71f4e7b59 | |||
58929290ab | |||
63146106c0 | |||
7c5602f204 | |||
e711e124ce | |||
e1b39c42ec | |||
d81c889426 | |||
afb0edacf6 | |||
6d7d568746 | |||
8df5b6eb76 | |||
270d12821a | |||
9c244e980c | |||
25ba54c8ac | |||
9c4bef864b | |||
bdc566e30f | |||
a653b61b51 | |||
7b61f71fa9 | |||
42539cc73d | |||
864369860c | |||
89938b7f93 | |||
f076661d4a | |||
0b018f53a7 | |||
32a13f0f3c | |||
f203bf9534 | |||
67b6339ffc | |||
6233da0044 | |||
56f455a08e | |||
9d541cc46a | |||
adf86d369b | |||
bbcb84ed03 | |||
6f40c774ea | |||
e3b70df46f | |||
584262a59b | |||
7e4e613405 | |||
e2c60bfd5a | |||
e38c402266 | |||
a299d5dc55 | |||
43c0f0ab1e | |||
ed17e7f16e | |||
7d723530e6 | |||
a3532a5940 | |||
74cbf3f2c8 | |||
d402744955 | |||
39b65b3884 | |||
c62ff2efc1 | |||
08f55c9680 | |||
1afc50ea6a | |||
7c259f455c | |||
be854a79b8 | |||
a71890ab68 | |||
dc2b6b9851 | |||
d091aabeb3 | |||
c261937cf5 | |||
4aa1309797 | |||
4f8517a50c | |||
446e049e6e | |||
7db2ba322c | |||
70fb732051 | |||
5128aa83f9 | |||
f75528b2af | |||
9f50b062b0 | |||
141cd225c8 | |||
2870fea15e | |||
8374dc91da | |||
347fc0e1e8 | |||
715fe5c318 | |||
df19c1094c | |||
279b9bc7a3 | |||
6adfc727c5 | |||
1aa07daa35 | |||
36901782ea | |||
62f2e1124c | |||
f6e1a54e74 | |||
6e2885cba6 | |||
252d9c87fd | |||
856829b605 | |||
a0f0b40ed3 | |||
68222ab511 | |||
fe804037d0 | |||
5d288772b5 | |||
3c9b92ccbd | |||
13cb46658f | |||
222614b856 | |||
9f10db142b | |||
860fe705c6 | |||
352ec3fad8 | |||
c7a3d74408 | |||
1e0da5f7cc | |||
7ce054191a | |||
34f547f0a6 | |||
14e07aa4a1 | |||
95dcf237cf | |||
5cc1821922 | |||
9d774bcb18 | |||
c6869f47b1 | |||
390b9517e0 | |||
2497da30b7 | |||
66e56d3626 | |||
d68ee120ab | |||
aff32c33c7 | |||
3c8744dc75 | |||
774871510e | |||
e61b705031 | |||
3902c611fa | |||
3765cfe5e9 | |||
2fad54fd26 | |||
be3cbf77bf | |||
f24a498246 | |||
9686381745 | |||
e42c530685 | |||
e56e078042 | |||
13be1a38fa | |||
27e857b9a6 | |||
2b2c290db0 | |||
245b50c1fd | |||
32dc7a2963 | |||
a9c2b209d9 | |||
a6c22df528 | |||
7dd2d70eca | |||
5fc3547d9c | |||
b5c222cc05 | |||
8b1efca7b8 | |||
1bdd2bc75a | |||
24dff8d920 | |||
8b98fc5c4e | |||
934ae50d8e | |||
7504e3719e | |||
34bd103ec2 | |||
295ce98e33 | |||
1cfdf1857e |
@ -1,4 +1,4 @@
|
||||
VITE_NAME=New Quest
|
||||
VITE_NAME=Sylvan Quest
|
||||
VITE_DEVELOPMENT=true
|
||||
VITE_SERVER_ENDPOINT=http://localhost:4000
|
||||
VITE_TILE_SIZE_X=64
|
||||
|
@ -27,5 +27,6 @@ RUN npm run build-ntc
|
||||
# Production stage
|
||||
FROM nginx:1.26.1-alpine
|
||||
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
@ -3,8 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
|
||||
<title>New Quest - Play</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>Sylvan Quest - Play</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
16
nginx.conf
Normal file
@ -0,0 +1,16 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Redirect example
|
||||
location /discord {
|
||||
return 301 https://discord.gg/JTev3nzeDa;
|
||||
}
|
||||
|
||||
# Serve static files
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
1445
package-lock.json
generated
10
package.json
@ -18,11 +18,12 @@
|
||||
"@vueuse/core": "^10.5.0",
|
||||
"@vueuse/integrations": "^10.5.0",
|
||||
"axios": "^1.7.7",
|
||||
"phaser": "^3.85.2",
|
||||
"dexie": "^4.0.8",
|
||||
"phaser": "^3.86.0",
|
||||
"pinia": "^2.1.6",
|
||||
"socket.io-client": "^4.8.0",
|
||||
"universal-cookie": "^6.1.3",
|
||||
"vue": "^3.5.10",
|
||||
"vue": "^3.5.12",
|
||||
"zod": "^3.22.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -48,8 +49,9 @@
|
||||
"sass": "^1.79.4",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.8",
|
||||
"vite-plugin-vue-devtools": "^7.4.6",
|
||||
"vite": "^5.4.9",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-vue-devtools": "^7.5.2",
|
||||
"vitest": "^2.0.3",
|
||||
"vue-tsc": "^1.6.5"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.5 MiB |
Before Width: | Height: | Size: 4.5 MiB |
Before Width: | Height: | Size: 2.7 MiB |
Before Width: | Height: | Size: 440 KiB |
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 16L15 12M15 12L11 8M15 12H3M4.51555 17C6.13007 19.412 8.87958 21 12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C8.87958 3 6.13007 4.58803 4.51555 7" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 453 B |
Before Width: | Height: | Size: 1.4 KiB |
@ -1 +0,0 @@
|
||||
<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>
|
Before Width: | Height: | Size: 1.6 KiB |
7
public/assets/icons/eye-closed.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="20" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path fill="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M17.882 19.297A10.949 10.949 0 0 1 12 21c-5.392 0-9.878-3.88-10.819-9a10.982 10.982 0 0 1 3.34-6.066L1.392 2.808l1.415-1.415 19.799 19.8-1.415 1.414-3.31-3.31zM5.935 7.35A8.965 8.965 0 0 0 3.223 12a9.005 9.005 0 0 0 13.201 5.838l-2.028-2.028A4.5 4.5 0 0 1 8.19 9.604L5.935 7.35zm6.979 6.978l-3.242-3.242a2.5 2.5 0 0 0 3.241 3.241zm7.893 2.264l-1.431-1.43A8.935 8.935 0 0 0 20.777 12 9.005 9.005 0 0 0 9.552 5.338L7.974 3.76C9.221 3.27 10.58 3 12 3c5.392 0 9.878 3.88 10.819 9a10.947 10.947 0 0 1-2.012 4.592zm-9.084-9.084a4.5 4.5 0 0 1 4.769 4.769l-4.77-4.769z" fill="#808080"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 788 B |
20
public/assets/icons/ingameUI/chat-icon.svg
Normal file
After Width: | Height: | Size: 597 KiB |
BIN
public/assets/icons/ingameUI/f1-icon.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
public/assets/icons/ingameUI/f2-icon.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
public/assets/icons/ingameUI/f3-icon.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
public/assets/icons/ingameUI/f4-icon.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
public/assets/icons/ingameUI/f5-icon.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
public/assets/icons/ingameUI/f6-icon.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
public/assets/icons/ingameUI/f7-icon.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
public/assets/icons/ingameUI/f8-icon.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
25
public/assets/icons/ingameUI/map-icon.svg
Normal file
After Width: | Height: | Size: 600 KiB |
10
public/assets/icons/ingameUI/menu-icon.svg
Normal file
After Width: | Height: | Size: 600 KiB |
25
public/assets/icons/ingameUI/socials-icon.svg
Normal file
After Width: | Height: | Size: 599 KiB |
@ -1,2 +0,0 @@
|
||||
<?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 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="#b1b2b5" d="M226.125 32.032a84.287 84.287 0 0 0-3.51.026c-11.4.318-24.464 2.935-40.945 8.63l-2.682.926-2.726-.777c-38.965-11.123-65.026.52-90.262 14.957 5.557 9.074 10.35 19.569 8.365 32.049l-.045.29-.066.286c-2.043 9.01-7.64 22.012-14.363 35.676-1.723 3.502-3.57 6.712-5.37 10.1 12.791-6.326 24.681-12.344 38.95-13.885l2.53-.274 2.302 1.094c-.018-.008 2.436.899 5.261 1.853 2.826.955 6.331 2.16 9.977 3.68 5.207 2.172 10.979 4.697 15.512 9.256 22.303-6.732 40.75-6.51 60.802-1.414 18.213-10.407 34.866-11.39 47.99-6.512 8.553 3.178 15.291 8.373 20.93 13.842l10.557-22.625 20.805 3.62c9.72-15.164 9.644-33.821 3.234-49.167-7.012-16.786-20.608-27.857-34.928-26.795l-2.404.178-2.172-1.047c-16.758-8.082-30.442-13.73-47.742-13.967zM112.791 138.688a44.845 44.845 0 0 0-3.287.745c-7.307 47.909-26.314 187.24-6.9 293.404 158.558 12.92 313.945 3.207 334.82 1.793.099-2.715.066-5.485-.121-8.313-1.354-20.44-10.822-42.312-22.235-55.045-6.318-7.049-23.99-13.695-48.029-16.789-24.04-3.094-54.057-3.4-85.057-2.056l-6.337.275c-21.183 1.115-40.742 2.29-58.89 5.111-4.051 8.895-13.356 14.998-23.735 14.998-13.904 0-25.885-10.947-25.885-24.892s11.981-24.89 25.885-24.89c5.778 0 11.22 1.896 15.613 5.107 8.084-10.448 16.365-21.222 25.375-31.897-8.386-8.22-17.33-15.993-27.44-22.722-3.941 2.49-8.595 3.943-13.548 3.943-13.842 0-25.39-11.288-25.39-25.14 0-13.854 11.548-25.141 25.39-25.141 2.64 0 5.194.413 7.603 1.174l33.02-38.875c-8.516-9.7-17.736-19.149-28.409-28.37-4.074 2.75-8.973 4.364-14.2 4.364-10.517 0-19.703-6.518-23.513-15.723-5.762 1.045-11.908 2.718-18.841 5.137l-7.551 2.634-3.504-7.189c-.353-.723-5.078-4.58-11.012-7.055-2.967-1.237-6.073-2.315-8.81-3.24-1.348-.455-2.457-.895-3.582-1.31l-1.43-.038zm96.748 2.565c13.9 11.186 25.49 22.656 35.904 34.33l18.24-21.475c-.711-3.822-1.422-7.642-2.142-11.478l-52.002-1.377zm58.313 35.754l-10.586 12.463c5.451 6.674 10.675 13.403 15.857 20.174-1.556-10.837-3.35-21.714-5.271-32.637zm-22.471 26.455l-30.586 36.012a24.652 24.652 0 0 1 3.613 12.845c0 2.354-.34 4.63-.96 6.793 10.699 7.168 20.067 15.208 28.613 23.489 9.55-10.298 19.972-20.277 31.78-29.457a476.305 476.305 0 0 0-.661-8.694c-11.152-14.168-21.175-27.806-31.8-40.988zm-52.361 41.717c-4.261 0-7.39 3.165-7.39 7.14 0 3.976 3.129 7.14 7.39 7.14 4.26 0 7.388-3.164 7.388-7.14 0-3.975-3.128-7.14-7.388-7.14zm85.732 30.572c-7.128 6.278-13.756 12.912-20.057 19.762 6.337 6.753 12.324 13.432 18.213 19.724 1.44-13.148 2-26.304 1.844-39.486zm-32.096 33.514c-8.101 9.677-15.774 19.602-23.41 29.44 15.249-1.938 31.118-2.91 47.58-3.776-8.238-8.276-16.088-17.033-24.17-25.664zm-53.636 31.763c-4.748 0-7.885 3.282-7.885 6.89 0 3.61 3.137 6.893 7.885 6.893 4.747 0 7.884-3.283 7.884-6.892 0-3.609-3.137-6.89-7.884-6.89zm-86.577 110.164c1.968 8.31 4.2 16.358 6.746 24.059 103.476 5.837 209.68 7.195 303.832-1.3 8.328-5.386 13.8-12.612 16.975-21.06-36.35 2.27-180.346 9.84-327.553-1.699z"/></svg>
|
Before Width: | Height: | Size: 3.0 KiB |
@ -1,2 +0,0 @@
|
||||
<?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 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="#b1b2b5" d="M162 35.75l-94.49 27.1c-12.05 6.3-23.47 23.9-31.01 46.35-6.07 18.2-9.62 38.9-10.93 58.3L136.7 112zm188 .1L375.4 112l111 55.6c-1.3-19.3-4.9-40.2-10.9-58.3-5.7-17.05-13.6-31.35-22.5-40.05-2.7-2.8-5.5-4.9-8.4-6.4zm-172.9 11.5l-25.7 77.45-92.9 46.4 14.08 53.5 88.82 44.4 94.6-15.9 94.6 15.9 88.8-44.4 14.1-53.5-92.8-46.4-25.8-77.35h-10.5l-59.3 73.95-.1 61.1h-18.1l.1-61-59.3-74.15zM78.65 247.7l22.05 83.9 146.2-43.8v-14.7l-88.4 14.7zm354.75 0l-80 40.1-88.4-14.7v14.7l146.3 43.8zm-186.5 58.7l-31.6 9.6-35.1 70.2 66.7-33.3zm18.1 0v46.5l66.9 33.4-35.2-70.3zM191.7 323l-86.4 26 25.3 96.3zm128.6.1l61.1 122.1 25.3-96.2zm-55.3 50l.1 43.2 100.7 37.8-20.4-40.8zm-18.1 0l-80.2 40.1-20.5 40.9L247 416.3zm.1 62.4l-81.6 30.6 81.6 10.2zm18.1 0v40.7l81.7-10.2z"/></svg>
|
Before Width: | Height: | Size: 985 B |
@ -1,2 +0,0 @@
|
||||
<?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 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="#b1b2b5" d="M256 68.02L169.7 240.7l14.8 44.5-17 5.6-15.2-45.6-66.42-53-12.18 60.9 12.62 12.6-12.66 12.6-15.21-15.2-30.53-20.4 25.15 163.4 39.17-65.2 89.66 35.8 30.9 46.4h86.4l30.9-46.4 89.7-35.8 39.1 65.2 25.2-163.4-30.6 20.4-15.2 15.2-12.6-12.6 12.6-12.6-12.2-60.9-66.4 53-15.2 45.6-17-5.6 14.8-44.5zm0 122.58l55.7 92.8 1.9 3.2-17.5 70-20.4 20.3h-39.4l-20.4-20.3-17.5-70zm0 34.8l-38.4 64 14.5 58 11.6 11.7h24.6l11.6-11.7 14.5-58z"/></svg>
|
Before Width: | Height: | Size: 661 B |
@ -1,2 +0,0 @@
|
||||
<?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 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="#b1b2b5" d="M256 16c-36.446 0-73.264 13.433-139.97 40h279.94C329.263 29.433 292.445 16 256 16zM95.344 72L64 448c56 28 112 31.5 168 31.938V240H112v-48h288v48H280v239.938C336 479.5 392 476 448 448L416.656 72H95.344zm64.875 88a8 8 0 0 1 7.78 8 8 8 0 0 1-16 0 8 8 0 0 1 8.22-8zm48 0a8 8 0 0 1 7.78 8 8 8 0 0 1-16 0 8 8 0 0 1 8.22-8zm48 0a8 8 0 0 1 7.78 8 8 8 0 0 1-16 0 8 8 0 0 1 8.22-8zm48 0a8 8 0 0 1 7.78 8 8 8 0 0 1-16 0 8 8 0 0 1 8.22-8zm48 0a8 8 0 0 1 7.78 8 8 8 0 0 1-16 0 8 8 0 0 1 8.22-8zM248 240v240c2.667.002 5.333 0 8 0s5.333.002 8 0V240h-16zm-120 48h16v16h-16v-16zm32 0h16v16h-16v-16zm32 0h16v16h-16v-16zm112 0h16v16h-16v-16zm32 0h16v16h-16v-16zm32 0h16v16h-16v-16zm-240 32h16v16h-16v-16zm32 0h16v16h-16v-16zm32 0h16v16h-16v-16zm112 0h16v16h-16v-16zm32 0h16v16h-16v-16zm32 0h16v16h-16v-16zm-240 32h16v16h-16v-16zm32 0h16v16h-16v-16zm32 0h16v16h-16v-16zm112 0h16v16h-16v-16zm32 0h16v16h-16v-16zm32 0h16v16h-16v-16z"/></svg>
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,2 +0,0 @@
|
||||
<?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 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="#b1b2b5" d="M144 37.28c-25.883 0-63.05 25.96-65.22 82.845l65.345 28.47 71.625-28.626C213.124 63.69 169.852 37.28 144 37.28zm224 0c-25.852 0-69.124 26.412-71.75 82.69l71.625 28.624 65.344-28.47c-2.17-56.883-39.337-82.843-65.22-82.843zM76.594 136.626c-10.887 40.29-12.11 90.026-9.844 141.03 46.136 10.675 93.357 7.68 141.406.44 8.464-50.25 12.923-99.296 3.625-139.314l-64.81 25.907-3.095 1.25-3.063-1.343-64.218-27.97zm358.812 0l-64.22 27.97-3.06 1.343-3.095-1.25-64.81-25.907c-9.3 40.02-4.84 89.064 3.624 139.314 48.05 7.242 95.27 10.236 141.406-.438 2.266-51.005 1.043-100.74-9.844-141.03zm-250.25 160.72c-31.678 3.654-63.5 4.865-95.25.75 12.238 12.217 23.424 24.845 35.47 36.217 2.198 2.077 4.425 4.107 6.686 6.094 18.086-12.095 35.813-26.27 53.094-43.062zm141.688 0c17.28 16.792 35.008 30.966 53.094 43.06 2.26-1.986 4.488-4.016 6.687-6.092 12.045-11.373 23.23-24 35.47-36.22-31.752 4.117-63.573 2.906-95.25-.75zm-258.97 1.842c1.515 24.774 3.633 49.29 5.69 73.188 15.054-6.598 29.912-14.187 44.53-23.03-1.24-1.13-2.477-2.264-3.688-3.408-16.52-15.597-30.655-32.307-46.53-46.75zm376.25 0c-15.875 14.443-30.01 31.153-46.53 46.75-1.21 1.144-2.448 2.278-3.688 3.407 14.618 8.844 29.476 16.433 44.53 23.03 2.057-23.897 4.175-48.413 5.69-73.187zm-240.093 1.844c-19.095 19.538-38.774 35.968-58.936 49.845 12.672 9.234 27 16.825 44.78 21.625 4.59-23.255 9.642-47.365 14.157-71.47zm103.94 0c4.514 24.105 9.566 48.215 14.155 71.47 17.78-4.8 32.11-12.39 44.78-21.625-20.16-13.877-39.84-30.307-58.936-49.844zM130.936 360.19c-15.606 9.753-31.47 18.076-47.5 25.28 11.79 18.345 27.05 33.88 44.282 47.97 18.94-13.89 36.69-28.745 51.124-47.532-18.94-6.09-34.415-15.227-47.906-25.72zm250.125 0c-13.49 10.49-28.966 19.628-47.906 25.718 14.435 18.787 32.183 33.642 51.125 47.53 17.234-14.087 32.494-29.623 44.283-47.967-16.03-7.206-31.894-15.53-47.5-25.283zM76.125 403.125c1.866 23.033 3.352 44.754 3.75 64.22 11.39-8.415 22.977-16.29 34.313-24.25-14.114-11.857-27.09-24.923-38.063-39.97zm359.75 0c-10.973 15.047-23.95 28.113-38.063 39.97 11.336 7.96 22.924 15.835 34.313 24.25.398-19.466 1.884-41.187 3.75-64.22zm-252.156 2.063c-12.975 14.874-27.61 27.23-42.75 38.53 11.24 8.348 23.104 16.282 35.218 24.126.75-19.013 3.624-40.192 7.53-62.656zm144.56 0c3.908 22.464 6.783 43.643 7.533 62.656 12.113-7.844 23.977-15.778 35.218-24.125-15.14-11.303-29.775-23.658-42.75-38.533zm-200.967 48.375c-13.318 9.394-26.726 18.344-39.47 27.812 26.524 12.555 53.04 11.06 79.563-.156-13.63-8.813-27.194-17.85-40.094-27.658zm257.375 0c-12.9 9.808-26.464 18.844-40.094 27.656 26.523 11.214 53.04 12.71 79.562.155-12.743-9.468-26.15-18.418-39.47-27.813z"/></svg>
|
Before Width: | Height: | Size: 2.8 KiB |
@ -1,2 +0,0 @@
|
||||
<?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 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="#b1b2b5" d="M186.438 20.56l-13.184 26.365c6.8-.26 13.626-.488 20.47-.686l3.84-7.68h116.874l3.77 7.54c6.838.187 13.658.408 20.456.66l-13.102-26.2H186.437zm69.56 42.742c-45.757.056-91.452 1.566-135.38 4.363-3.24 50.58-8.4 100.987-.786 145.824 89.297 12.395 180.102 12.985 272.764-.054 7.055-30.988 5.117-84.68-1.04-145.89-43.974-2.893-89.73-4.3-135.558-4.244zm153.783 5.54c6.42 64.12 9.113 119.825-1.135 155.22l-1.61 5.56-5.726.842c-98.8 14.528-195.613 13.81-290.605.002l-6.285-.914-1.246-6.23c-9.89-49.49-4.085-102.785-.664-154.42-4.89.354-9.765.72-14.602 1.107-8.596 58.568-9.39 116.957-.05 175.292 110.24 12.088 222.275 12.205 336.203-.01 8.502-57.83 8.29-116.25-.017-175.313-4.725-.4-9.485-.776-14.262-1.14zM255.966 92.3c32.526-.025 65.067 2.746 97.574 8.39l7.46 1.295v7.572c0 15.554 1.683 35.105-12.69 50.25-9.912 10.444-25.655 17.337-51.31 20.585v18.164h-82v-18.452c-23.992-3.37-39.352-10.175-49.363-20.185C150.807 145.093 151 125.56 151 109.56v-7.594l7.484-1.278c32.444-5.54 64.955-8.362 97.48-8.386zm.012 17.994c-28.96.022-57.913 2.444-86.858 6.996.265 12.28 1.635 22.296 9.243 29.904 5.914 5.914 16.952 11.416 36.637 14.582v-29.22h82v29.51c21.367-3.115 32.66-8.755 38.254-14.65 7.033-7.41 7.696-17.502 7.73-30.124-29-4.63-58.006-7.02-87.007-6.998zM233 150.56v30h46v-30h-46zm209.674 92.42c-.503 3.625-1.042 7.25-1.61 10.87.214 2.352.42 4.706.63 7.06L471 290.213v-22.24l-28.326-24.995zm-373.485.12L41 267.973v22.24l29.318-29.318c.205-2.327.406-4.655.616-6.982-.618-3.605-1.202-7.21-1.745-10.813zm354.634 20.397c-10.29 1.09-20.564 2.076-30.824 2.967v74.095h16v66h-16v80.615c10.318-.633 20.63-1.313 30.928-2.082 9.445-74.01 6.478-147.698-.104-221.596zm-335.576.03C81.725 338.09 78.58 412.1 88.06 485.1c10.324.79 20.638 1.504 30.94 2.145V406.56h-16v-66h16v-74.024c-10.266-.902-20.517-1.903-30.752-3.01zm286.752 4.4c-10.014.76-20.014 1.424-30 1.992v70.64h30v-72.632zm-238 .085v72.547h30v-70.55c-10.015-.568-20.014-1.237-30-1.997zm190 2.825c-47.65 2.173-94.984 2.19-142 .078v19.314c23.95-5.165 47.8-7.652 71.516-7.59 23.638.06 47.145 2.654 70.484 7.626v-19.43zM68.05 288.62L41 315.67v56.89h23.06c.376-27.987 1.88-55.975 3.99-83.94zm375.948.047c2.12 27.872 3.61 55.83 3.957 83.892H471v-56.89l-27.002-27.003zm-187.52 11.95c-23.68-.063-47.487 2.577-71.478 8.052v31.89h16v18.443c17.033 5.346 31.73 8.493 46 9.426v-2.87h18v2.868c14.27-.932 28.967-4.08 46-9.425V340.56h16v-31.866c-23.42-5.267-46.907-8.016-70.523-8.078zM121 358.558v30h22v-23h18v23h22v-30h-62zm208 0v30h22v-23h18v23h22v-30h-62zM201 377.8v28.76h-16v15.857c48.528 10.865 95.713 10.664 142 .045V406.56h-16V377.8c-16.332 4.747-31.283 7.52-46 8.326v11.433h-18v-11.434c-14.717-.806-29.668-3.58-46-8.326zM41 390.56v14h23.14c-.09-4.667-.143-9.334-.163-14H41zm407.012 0c-.027 4.663-.083 9.33-.18 14H471v-14h-22.988zM137 406.56v19.798c6.137 7.214 11.222 9.77 14.934 9.844 3.734.075 8.697-2.122 15.066-9.79V406.56h-6v7h-18v-7h-6zm208 0v19.798c6.137 7.214 11.222 9.77 14.934 9.844 3.734.075 8.697-2.122 15.066-9.79V406.56h-6v7h-18v-7h-6zm-304 16v35.154c5.596 5.51 8.677 8.25 11.846 9.306 2.454.818 7.713 1.15 15.045 1.317-1.544-15.25-2.586-30.51-3.204-45.778H41zm406.27 0c-.628 15.224-1.674 30.483-3.21 45.78 7.358-.168 12.635-.5 15.094-1.32 3.17-1.056 6.25-3.795 11.846-9.306V422.56h-23.73zM185 440.842v49.498c47.55 1.51 94.877 1.446 142-.074V440.9c-46.316 10.03-93.74 10.185-142-.057zm-48 9.123v38.318c10.01.54 20.01 1.008 30 1.408v-39.678c-4.86 2.786-10.01 4.293-15.43 4.184-5.192-.104-10.036-1.624-14.57-4.232zm208 0v39.654c10.01-.403 20.01-.878 30-1.412v-38.194c-4.86 2.786-10.01 4.293-15.43 4.184-5.192-.104-10.036-1.624-14.57-4.232z"/></svg>
|
Before Width: | Height: | Size: 3.7 KiB |
@ -1,2 +0,0 @@
|
||||
<?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 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="#b1b2b5" d="M329.5 29.12l-8.1 11.4L359 67.16l8.1-11.44zm-88 5.04l24.2 45.36 1.8 1.29 14.8-40.36zm57.6 12.63l-16.4 44.8 40.7 28.81 35.3-31.54c-.9-.58-1.9-1.19-2.8-1.84zM59.83 48.56l10.84 45.83 29.63 2.6 2.7-29.63zM470.9 75.41c-5.6 4.71-12.2 8.59-19.5 11.74 5 46.45-14.7 83.45-45.2 109.75-26.5 22.9-60.9 38.4-95 47.9-2.5 4.8-5 9.2-7.4 13.1 41.5 5.4 93.2-21.2 129.2-60 19.8-21.3 34.8-45.9 41.1-69.2 5.2-19.4 4.7-37.42-3.2-53.29zm-351.3 8.71l-3 32.48-32.35-2.9 226.55 271 20-16.7 15.3-12.8zM434 93.09c-4.2 1-8.5 2-12.8 2.7-14.9 2.5-30.1 3.1-43.5.3l-41 36.61c4 7 5 15.7 4.5 24.5-.6 12.6-4.3 26.7-9.3 40.9-3 8.3-6.3 16.6-9.9 24.6 26.9-9.2 52.6-22.3 72.5-39.4 26.2-22.8 42.5-51.6 39.5-90.21zM274 107.4l-51.2 72.2 30.6 36.5 58.2-82.1zM173.8 248.8L34.53 445.2l37.53 26.6L204.3 285.3zm233 79.2L273.3 439.5l19.2 23.1L426 351zm-18.3 77.9l-35.3 29.4 39.7 47.6 35.3-29.4z"/></svg>
|
Before Width: | Height: | Size: 1.1 KiB |
10
public/assets/icons/male-icon.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_598_541)">
|
||||
<path d="M10.0327 5.69111L12.3905 3.33333H9.33333V2H14.6667V7.33333H13.3333V4.27614L10.9755 6.63392C11.6183 7.47513 12 8.52633 12 9.66667C12 12.4281 9.7614 14.6667 7 14.6667C4.23857 14.6667 2 12.4281 2 9.66667C2 6.90527 4.23857 4.66667 7 4.66667C8.14033 4.66667 9.19153 5.04843 10.0327 5.69111ZM7 13.3333C9.02507 13.3333 10.6667 11.6917 10.6667 9.66667C10.6667 7.6416 9.02507 6 7 6C4.97495 6 3.33333 7.6416 3.33333 9.66667C3.33333 11.6917 4.97495 13.3333 7 13.3333Z" fill="#999999"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_598_541">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 729 B |
10
public/assets/icons/minus-icon.svg
Normal file
After Width: | Height: | Size: 597 KiB |
3
public/assets/icons/modal/close-button-white.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<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>
|
After Width: | Height: | Size: 918 B |
Before Width: | Height: | Size: 768 B After Width: | Height: | Size: 768 B |
51
public/assets/icons/modal/increase-size-option.svg
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="438.529px" height="438.529px" viewBox="0 0 438.529 438.529" style="enable-background:new 0 0 438.529 438.529;"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M180.156,225.828c-1.903-1.902-4.093-2.854-6.567-2.854c-2.475,0-4.665,0.951-6.567,2.854l-94.787,94.787l-41.112-41.117
|
||||
c-3.617-3.61-7.895-5.421-12.847-5.421c-4.952,0-9.235,1.811-12.851,5.421c-3.617,3.621-5.424,7.905-5.424,12.854v127.907
|
||||
c0,4.948,1.807,9.229,5.424,12.847c3.619,3.613,7.902,5.424,12.851,5.424h127.906c4.949,0,9.23-1.811,12.847-5.424
|
||||
c3.615-3.617,5.424-7.898,5.424-12.847s-1.809-9.233-5.424-12.854l-41.112-41.104l94.787-94.793
|
||||
c1.902-1.903,2.853-4.086,2.853-6.564c0-2.478-0.953-4.66-2.853-6.57L180.156,225.828z"/>
|
||||
<path d="M433.11,5.424C429.496,1.807,425.212,0,420.263,0H292.356c-4.948,0-9.227,1.807-12.847,5.424
|
||||
c-3.614,3.615-5.421,7.898-5.421,12.847s1.807,9.233,5.421,12.847l41.106,41.112l-94.786,94.787
|
||||
c-1.901,1.906-2.854,4.093-2.854,6.567s0.953,4.665,2.854,6.567l32.552,32.548c1.902,1.903,4.086,2.853,6.563,2.853
|
||||
s4.661-0.95,6.563-2.853l94.794-94.787l41.104,41.109c3.62,3.616,7.905,5.428,12.854,5.428s9.229-1.812,12.847-5.428
|
||||
c3.614-3.614,5.421-7.898,5.421-12.847V18.268C438.53,13.315,436.734,9.04,433.11,5.424z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 375 B After Width: | Height: | Size: 375 B |
Before Width: | Height: | Size: 652 B After Width: | Height: | Size: 652 B |
@ -1,4 +1,4 @@
|
||||
<?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">
|
||||
<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>
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 346 B |
10
public/assets/icons/plus-green-icon.svg
Normal file
After Width: | Height: | Size: 485 KiB |
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 597 KiB |
3
public/assets/icons/profile/boots.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.15761 1.28332C3.96537 1.15583 3.71087 1.17639 3.54159 1.3331C3.23088 1.62074 2.98798 2.00897 2.80986 2.34288C2.69323 2.27414 2.5689 2.21392 2.43729 2.1694C2.27798 2.11552 2.10225 2.14533 1.96963 2.24875C1.83701 2.35216 1.76526 2.51531 1.77868 2.68295C1.96117 4.96193 2.51766 7.79669 3.14534 10.1876C3.15716 10.2326 3.17507 10.2753 3.19825 10.3146C2.89349 10.543 2.6183 10.8624 2.48626 11.2592C2.45915 11.3406 2.45376 11.4266 2.46923 11.5092L1.01702 12.5343C0.971683 12.5663 0.93199 12.6056 0.899569 12.6506C0.521457 13.1759 0.404537 13.8064 0.579107 14.3656C0.635354 14.5458 0.788273 14.679 0.974476 14.7099C2.62972 14.9848 3.76851 14.6009 4.99802 13.9624C5.09724 13.9109 5.17356 13.8294 5.21926 13.7331C5.28208 13.7365 5.3457 13.7281 5.40668 13.7074L6.36918 13.3813C6.58065 13.3096 6.71897 13.1064 6.70811 12.8834L6.66986 12.0978C6.6651 11.9999 6.6317 11.9057 6.5738 11.8266C6.56495 11.8145 6.55602 11.8025 6.54703 11.7905C6.68693 11.6781 6.75773 11.4969 6.72648 11.3158C6.51037 10.0625 6.7448 8.79384 7.001 7.4073C7.02616 7.27116 7.05153 7.13387 7.0767 6.99535C7.35132 5.48411 7.59458 3.84747 7.06807 2.17953C6.98994 1.93202 6.73543 1.78545 6.48214 1.84209C6.12136 1.92278 5.70715 2.0529 5.33106 2.21214C5.27748 2.23483 5.22893 2.26639 5.1871 2.30496C5.15959 2.27265 5.13071 2.23811 5.09985 2.201C5.09087 2.1902 5.08173 2.17919 5.07242 2.16799C5.00591 2.0879 4.93138 1.99816 4.85218 1.90837C4.6703 1.7022 4.44556 1.47429 4.15761 1.28332ZM12.4584 1.33311C12.2891 1.1764 12.0346 1.15583 11.8424 1.28331C11.5544 1.47427 11.3296 1.70219 11.1478 1.90836C11.0685 1.99816 10.994 2.08791 10.9275 2.16801C10.9182 2.1792 10.9091 2.19021 10.9001 2.201C10.8692 2.2381 10.8404 2.27263 10.8129 2.30493C10.771 2.26634 10.7224 2.23476 10.6688 2.21207C10.2927 2.05285 9.87856 1.9228 9.51784 1.8421C9.26454 1.78544 9.01 1.93201 8.93187 2.17953C8.40536 3.84747 8.64862 5.48411 8.92324 6.99535C8.94841 7.13386 8.97378 7.27114 8.99893 7.40728C9.25513 8.79381 9.48956 10.0625 9.27343 11.3157C9.24218 11.4969 9.31299 11.6781 9.45292 11.7905C9.44392 11.8025 9.435 11.8145 9.42615 11.8266C9.36825 11.9056 9.33484 11.9999 9.33008 12.0978L9.29183 12.8834C9.28097 13.1064 9.41929 13.3096 9.63076 13.3813L10.5933 13.7074C10.6543 13.7281 10.7179 13.7365 10.7807 13.7331C10.8264 13.8294 10.9028 13.9109 11.002 13.9624C12.2315 14.6008 13.3703 14.9848 15.0255 14.7099C15.2117 14.679 15.3647 14.5458 15.4209 14.3656C15.5955 13.8064 15.4785 13.1759 15.1004 12.6506C15.068 12.6056 15.0283 12.5663 14.983 12.5343L13.5307 11.5091C13.5462 11.4266 13.5408 11.3406 13.5137 11.2592C13.3816 10.8624 13.1064 10.543 12.8017 10.3146C12.8248 10.2753 12.8428 10.2326 12.8546 10.1876C13.4823 7.79669 14.0388 4.9619 14.2212 2.68292C14.2347 2.51528 14.1629 2.35213 14.0303 2.24872C13.8977 2.14531 13.7219 2.11549 13.5626 2.16937C13.431 2.21389 13.3067 2.27411 13.1901 2.34286C13.0119 2.00898 12.769 1.62075 12.4584 1.33311Z" stroke="#666666" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
3
public/assets/icons/profile/chestplate.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.504 1.63801L12.252 1.84501C11.652 2.34001 11.352 2.58801 11 2.58801C10.648 2.58801 10.348 2.34001 9.74799 1.84501L9.49599 1.63801C9.00899 1.23701 8.76599 1.03601 8.47499 1.00401C8.18299 0.973011 7.90499 1.11601 7.34699 1.40401L7.04399 1.55901C6.76699 1.70201 6.62799 1.77401 6.55299 1.91901C6.47799 2.06401 6.49499 2.20301 6.52999 2.48101C6.80599 4.70301 8.49099 6.79701 10.61 7.42601C10.735 7.47216 10.8668 7.49717 11 7.50001C11.1332 7.49717 11.265 7.47216 11.39 7.42601C13.509 6.79701 15.194 4.70301 15.47 2.48101C15.505 2.20301 15.522 2.06401 15.447 1.91901C15.372 1.77401 15.233 1.70201 14.957 1.55901L14.653 1.40301C14.096 1.11601 13.817 0.973011 13.525 1.00401C13.234 1.03601 12.991 1.23701 12.505 1.63801M6.47799 3.00001C4.39199 3.40701 2.99599 4.15001 1.71899 5.23001C1.34599 5.54501 1.15899 5.70301 1.07099 5.95401C0.780994 6.78001 1.41499 9.62101 2.19699 9.94901C2.68399 10.153 3.36499 9.73501 4.72799 8.89901C5.96499 8.14001 7.48299 7.35501 8.99999 6.93201M15.522 3.00001C17.608 3.40701 19.004 4.15001 20.281 5.23001C20.654 5.54501 20.841 5.70301 20.929 5.95401C21.219 6.78001 20.585 9.62101 19.803 9.94901C19.316 10.153 18.635 9.73501 17.271 8.89901C16.036 8.14001 14.518 7.35501 13 6.93201M17 9.00001L16.395 14.442C16.207 16.134 16.113 16.98 15.544 17.49C14.974 18 14.123 18 12.42 18H9.57999C7.87699 18 7.02599 18 6.45599 17.49C5.88599 16.98 5.79299 16.134 5.60499 14.442L4.99999 9.00001M14.385 18H7.61499C7.35999 18 7.23299 18 7.11199 18.014C6.67794 18.064 6.271 18.2506 5.94999 18.547C5.86099 18.628 5.77999 18.724 5.61699 18.915C5.29699 19.291 5.13599 19.479 5.07699 19.622C5.02428 19.7507 4.9991 19.8889 5.00309 20.0279C5.00709 20.1669 5.04016 20.3034 5.10017 20.4288C5.16019 20.5543 5.24582 20.6657 5.35155 20.756C5.45727 20.8462 5.58074 20.9134 5.71399 20.953C5.86399 21 6.11499 21 6.61599 21H15.384C15.884 21 16.135 21 16.286 20.953C16.4193 20.9136 16.5429 20.8467 16.6487 20.7565C16.7545 20.6664 16.8403 20.5551 16.9004 20.4297C16.9605 20.3044 16.9937 20.1678 16.9977 20.0289C17.0018 19.8899 16.9767 19.7516 16.924 19.623C16.864 19.479 16.704 19.291 16.384 18.915C16.22 18.724 16.139 18.628 16.05 18.547C15.7286 18.2512 15.3219 18.0646 14.888 18.014C14.767 18 14.64 18 14.385 18Z" stroke="#666666" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
3
public/assets/icons/profile/helmet.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.00001 19H12M10 7H10.009M2.53801 14.094C0.888011 7.61 3.99601 3.564 10 1C16.005 3.563 19.112 7.61 17.462 14.094C17.342 14.564 17.358 15.063 17.534 15.514L18.91 19.038C19.445 20.409 17.466 20.735 16.625 20.912C14.311 21.399 12.067 19.811 11.693 17.42C11.429 15.733 12.383 15.456 13.631 14.817C13.631 14.817 15.187 14.286 15.187 12.691C15.187 11.517 14.257 10.566 13.112 10.566C12.394 10.566 11.762 10.774 11.135 11.452C10.527 12.109 10.223 12.437 10 12.437C9.77701 12.437 9.47301 12.109 8.86601 11.452C8.23801 10.774 7.60601 10.566 6.88801 10.566C5.74201 10.566 4.81301 11.517 4.81301 12.691C4.81301 14.286 6.36901 14.817 6.36901 14.817C7.61701 15.456 8.57101 15.733 8.30701 17.42C7.93301 19.81 5.68901 21.4 3.37501 20.912C2.53401 20.735 0.555011 20.409 1.09101 19.038L2.46601 15.514C2.64201 15.063 2.65701 14.564 2.53801 14.094Z" stroke="#666666" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1010 B |
3
public/assets/icons/profile/legs.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.16 4V3C16.16 2.057 16.16 1.586 15.86 1.293C15.559 1 15.075 1 14.107 1H5.89301C4.92501 1 4.44101 1 4.14001 1.293C3.83901 1.586 3.84001 2.057 3.84001 3V4M16.16 4H3.84001M16.16 4L17.6096 11M3.84001 4L2.39047 11M2.39047 11L1.78774 14.3265M2.39047 11C3.48635 11.3237 4.13173 12.9685 4.99991 13C6.07854 13.0392 7.37081 11.3877 8.76949 11M8.76949 11C9.1675 9.93337 9.99991 9 9.99991 9C9.99991 9 10.8327 9.93306 11.2308 11M8.76949 11C8.55307 11.58 8.31985 12.3176 8.03701 13.213L7.68545 14.3265M11.2308 11C11.4472 11.5799 11.6802 12.3175 11.963 13.213L12.3148 14.3265M11.2308 11C12.6566 11.3875 13.9154 13.041 14.9999 13C15.8598 12.9675 16.5386 11.3237 17.6096 11M17.6096 11L18.2123 14.326M1.78774 14.3265L1.18501 18.653C0.987006 19.741 0.889006 20.285 1.19701 20.643C1.50401 21 2.07201 21 3.20701 21H4.37601C5.10701 21 5.47401 21 5.74601 20.804C6.01801 20.608 6.12501 20.268 6.34001 19.588L7.68545 14.3265M1.78774 14.3265C2.76898 14.5762 3.5689 16.9715 4.37601 17C5.35607 17.0346 6.34671 14.6477 7.68545 14.3265M12.3148 14.3265L13.661 19.588C13.875 20.269 13.983 20.609 14.255 20.804C14.527 21 14.893 21 15.623 21H16.793C17.928 21 18.496 21 18.803 20.642C19.111 20.285 19.012 19.742 18.815 18.652L18.2123 14.326M12.3148 14.3265C13.6343 14.7183 14.621 16.5451 15.623 16.5C16.4184 16.4642 17.2235 14.6375 18.2123 14.326" stroke="#666666" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -1 +0,0 @@
|
||||
<svg width="48" xmlns="http://www.w3.org/2000/svg" height="48" id="screenshot-e9346f42-72c8-800c-8004-507b356b7f18" viewBox="0 0 48 48" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-e9346f42-72c8-800c-8004-507b356b7f18" width="1em" height="1em" rx="0" ry="0" style="fill: rgb(0, 0, 0);"><g id="shape-e9346f42-72c8-800c-8004-507b356b7f1b"><defs style="fill: rgb(0, 0, 0);"/></g><g id="shape-e9346f42-72c8-800c-8004-507b356b7f1c"><defs><mask width="1.2" height="1.2" x="-0.1" id="render-3-ipSEnterKey0" data-old-y="-0.1" data-old-width="1.2" data-old-x="-0.1" y="-0.1" data-old-height="1.2"><g class="svg-mask-wrapper" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"><path fill="#fff" stroke="#fff" d="M44 44V4H24v16H4v24z"/><path stroke="#000" d="m21 28l-4 4l4 4"/><path stroke="#000" d="M34 23v9H17"/></g></g></mask></defs><g class="fills" id="fills-e9346f42-72c8-800c-8004-507b356b7f1c"><path d="M0.000,0.000L48.000,0.000L48.000,48.000L0.000,48.000ZZ" mask="url(#render-3-ipSEnterKey0)" style="fill: #696969; fill-opacity: 1;"/></g></g></g></svg>
|
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 2.1 KiB |
3
public/assets/icons/triangle-icon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="13" height="16" viewBox="0 0 13 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.00044 7.42339L12.0005 1.07252C12.3193 0.888385 12.7271 0.997653 12.9111 1.31652C12.9697 1.41785 13.0005 1.53285 13.0005 1.64985L13.0005 14.3516C13.0005 14.7198 12.702 15.0182 12.3338 15.0182C12.2167 15.0182 12.1018 14.9874 12.0005 14.9289L1.00044 8.57805C0.681573 8.39399 0.572326 7.98625 0.756419 7.66739C0.814932 7.56605 0.899092 7.48185 1.00044 7.42339Z" fill="#4D4D4D"/>
|
||||
</svg>
|
After Width: | Height: | Size: 490 B |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.3 KiB |
3
public/assets/icons/x-button-gray.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.5859 10.0001L0.792969 2.20718L2.20718 0.792969L10.0001 8.58582L17.793 0.792969L19.2072 2.20718L11.4143 10.0001L19.2072 17.7929L17.793 19.2072L10.0001 11.4143L2.20718 19.2072L0.792969 17.7929L8.5859 10.0001Z" fill="#999999"/>
|
||||
</svg>
|
After Width: | Height: | Size: 340 B |
15
public/assets/login/sq-logo-v1.svg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/music/click-btn.mp3
Normal file
BIN
public/assets/music/login.mp3
Normal file
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.8 KiB |
@ -1 +0,0 @@
|
||||
<svg width="170" xmlns="http://www.w3.org/2000/svg" height="275" id="screenshot-c7008730-586b-8052-8004-79a2f5807f5c" viewBox="0 0 170 275" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-c7008730-586b-8052-8004-79a2f5807f5c"><defs><linearGradient id="fill-color-gradient-render-1-0" x1="0.4238862507773219" y1="0.26897966364321624" x2="0.7513711107948178" y2="0.9146156628521559" gradientTransform=""><stop offset="0" stop-color="#ffffff" stop-opacity="0.4"/><stop offset="1" stop-color="#ffffff" stop-opacity="0"/></linearGradient><pattern patternUnits="userSpaceOnUse" x="0" y="0" width="170" height="275" patternTransform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" id="fill-0-render-1"><g><rect width="170" height="275" style="fill: url("#fill-color-gradient-render-1-0");"/></g></pattern></defs><g class="fills" id="fills-c7008730-586b-8052-8004-79a2f5807f5c"><path d="M170.000,20.000L170.000,255.000C170.000,266.038,161.038,275.000,150.000,275.000L20.000,275.000C8.962,275.000,0.000,266.038,0.000,255.000L0.000,20.000C0.000,8.962,8.962,0.000,20.000,0.000L150.000,0.000C150.000,0.000,150.000,0.000,150.000,0.000C150.000,11.038,158.962,20.000,170.000,20.000Z" fill="url(#fill-0-render-1)"/></g><g id="strokes-c7008730-586b-8052-8004-79a2f5807f5c" class="strokes"><g class="inner-stroke-shape"><defs><clipPath id="inner-stroke-render-1-c7008730-586b-8052-8004-79a2f5807f5c-0"><use href="#stroke-shape-render-1-c7008730-586b-8052-8004-79a2f5807f5c-0"/></clipPath><path d="M170.000,20.000L170.000,255.000C170.000,266.038,161.038,275.000,150.000,275.000L20.000,275.000C8.962,275.000,0.000,266.038,0.000,255.000L0.000,20.000C0.000,8.962,8.962,0.000,20.000,0.000L150.000,0.000C150.000,0.000,150.000,0.000,150.000,0.000C150.000,11.038,158.962,20.000,170.000,20.000Z" id="stroke-shape-render-1-c7008730-586b-8052-8004-79a2f5807f5c-0" style="fill: none; stroke-width: 4; stroke: rgb(255, 255, 255); stroke-opacity: 1;"/></defs><use href="#stroke-shape-render-1-c7008730-586b-8052-8004-79a2f5807f5c-0" clip-path="url('#inner-stroke-render-1-c7008730-586b-8052-8004-79a2f5807f5c-0')"/></g></g></g></svg>
|
Before Width: | Height: | Size: 2.1 KiB |
@ -1 +0,0 @@
|
||||
<svg width="290" xmlns="http://www.w3.org/2000/svg" height="87" id="screenshot-e9942e24-155b-8096-8004-7eb5ea5d2669" viewBox="0 0 290 87" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-e9942e24-155b-8096-8004-7eb5ea5d2669"><g class="fills" id="fills-e9942e24-155b-8096-8004-7eb5ea5d2669"><path d="M286.515,78.278C282.911,83.543,276.856,87.000,270.000,87.000L20.000,87.000C8.962,87.000,0.000,78.038,0.000,67.000L0.000,20.000C0.000,8.962,8.962,0.000,20.000,0.000L270.000,0.000C276.834,0.000,282.872,3.435,286.480,8.671C268.843,10.411,255.000,25.352,255.000,43.500C255.000,61.610,268.784,76.525,286.515,78.278ZM290.000,20.000L290.000,67.000" style="fill: #fff; fill-opacity: 1;"/></g></g></svg>
|
Before Width: | Height: | Size: 726 B |
@ -1 +0,0 @@
|
||||
<svg width="290" xmlns="http://www.w3.org/2000/svg" height="87" id="screenshot-e38d8c7f-bba0-801b-8004-7d6eeffceb00" viewBox="4058.354 6110 290 87" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-e38d8c7f-bba0-801b-8004-7d6eeffceb00"><g class="fills" id="fills-e38d8c7f-bba0-801b-8004-7d6eeffceb00"><path d="M4061.840,6118.722C4065.444,6113.457,4071.499,6110.000,4078.354,6110.000L4328.354,6110.000C4339.393,6110.000,4348.354,6118.962,4348.354,6130.000L4348.354,6177.000C4348.354,6188.038,4339.393,6197.000,4328.354,6197.000L4078.354,6197.000C4071.521,6197.000,4065.483,6193.565,4061.875,6188.329C4079.512,6186.589,4093.354,6171.648,4093.354,6153.500C4093.354,6135.390,4079.571,6120.475,4061.840,6118.722ZM4058.354,6177.000L4058.354,6130.000" style="fill: #fff; fill-opacity: 1;"/></g></g></svg>
|
Before Width: | Height: | Size: 829 B |
@ -1 +0,0 @@
|
||||
<svg width="290" xmlns="http://www.w3.org/2000/svg" height="87" id="screenshot-e9942e24-155b-8096-8004-7eaff9882cd6" 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-7eaff9882cd6"><g class="fills" id="fills-e9942e24-155b-8096-8004-7eaff9882cd6"><path d="M3.485,8.722C7.089,3.457,13.144,0.000,20.000,0.000L270.000,0.000C281.038,0.000,290.000,8.962,290.000,20.000L290.000,67.000C290.000,78.038,281.038,87.000,270.000,87.000L20.000,87.000C13.166,87.000,7.128,83.565,3.520,78.329C21.157,76.589,35.000,61.648,35.000,43.500C35.000,25.390,21.216,10.475,3.485,8.722ZM0.000,67.000L0.000,20.000"/></g><g id="strokes-e9942e24-155b-8096-8004-7eaff9882cd6" class="strokes"><g class="inner-stroke-shape"><defs><clipPath id="inner-stroke-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0"><use href="#stroke-shape-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0"/></clipPath><path d="M3.485,8.722C7.089,3.457,13.144,0.000,20.000,0.000L270.000,0.000C281.038,0.000,290.000,8.962,290.000,20.000L290.000,67.000C290.000,78.038,281.038,87.000,270.000,87.000L20.000,87.000C13.166,87.000,7.128,83.565,3.520,78.329C21.157,76.589,35.000,61.648,35.000,43.500C35.000,25.390,21.216,10.475,3.485,8.722ZM0.000,67.000L0.000,20.000" id="stroke-shape-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0" style="fill: none; stroke-width: 6; stroke: rgb(77 77 77); stroke-opacity: 1;"/></defs><use href="#stroke-shape-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0" clip-path="url('#inner-stroke-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0')"/></g></g></g></svg>
|
Before Width: | Height: | Size: 1.6 KiB |
@ -1 +0,0 @@
|
||||
<svg width="290" xmlns="http://www.w3.org/2000/svg" height="87" id="screenshot-e9942e24-155b-8096-8004-7eaff9882cd6" 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-7eaff9882cd6"><g class="fills" id="fills-e9942e24-155b-8096-8004-7eaff9882cd6"><path d="M3.485,8.722C7.089,3.457,13.144,0.000,20.000,0.000L270.000,0.000C281.038,0.000,290.000,8.962,290.000,20.000L290.000,67.000C290.000,78.038,281.038,87.000,270.000,87.000L20.000,87.000C13.166,87.000,7.128,83.565,3.520,78.329C21.157,76.589,35.000,61.648,35.000,43.500C35.000,25.390,21.216,10.475,3.485,8.722ZM0.000,67.000L0.000,20.000" style="fill: rgb(127, 127, 127); fill-opacity: 0.7;"/></g><g id="strokes-e9942e24-155b-8096-8004-7eaff9882cd6" class="strokes"><g class="inner-stroke-shape"><defs><clipPath id="inner-stroke-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0"><use href="#stroke-shape-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0"/></clipPath><path d="M3.485,8.722C7.089,3.457,13.144,0.000,20.000,0.000L270.000,0.000C281.038,0.000,290.000,8.962,290.000,20.000L290.000,67.000C290.000,78.038,281.038,87.000,270.000,87.000L20.000,87.000C13.166,87.000,7.128,83.565,3.520,78.329C21.157,76.589,35.000,61.648,35.000,43.500C35.000,25.390,21.216,10.475,3.485,8.722ZM0.000,67.000L0.000,20.000" id="stroke-shape-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0" style="fill: none; stroke-width: 6; stroke: rgb(255, 255, 255); stroke-opacity: 1;"/></defs><use href="#stroke-shape-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0" clip-path="url('#inner-stroke-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0')"/></g></g></g></svg>
|
Before Width: | Height: | Size: 1.6 KiB |
@ -1 +0,0 @@
|
||||
<svg width="1508.086" xmlns="http://www.w3.org/2000/svg" height="1511.251" id="screenshot-0d120e2a-8725-8061-8004-79728483f7ea" viewBox="-201.784 -208.012 1508.086 1511.251" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-0d120e2a-8725-8061-8004-79728483f7ea" width="800px" height="800px" rx="0" ry="0" style="opacity: 0.3; fill: rgb(0, 0, 0);"><g id="shape-0d120e2a-8725-8061-8004-79728484b3fe"><g class="fills" id="fills-0d120e2a-8725-8061-8004-79728484b3fe"><path d="M1190.359,745.690L1043.367,575.945L1133.504,630.603C1099.180,585.722,1047.978,532.622,975.722,469.519L780.783,401.898L896.468,404.538C851.234,371.382,797.068,339.090,738.130,311.073L601.350,337.338L689.349,289.039C627.425,263.088,562.143,241.922,497.713,229.160C430.172,215.674,363.453,211.534,303.512,221.641L314.753,151.382C271.664,177.012,239.130,209.992,214.226,251.017L204.390,177.710C166.181,212.950,148.095,250.172,143.131,301.343C69.092,307.974,-2.300,327.925,-73.861,347.005L-63.628,384.898C361.675,238.903,753.109,407.667,987.467,615.054L960.305,643.506C749.259,458.743,490.332,358.712,193.406,380.541C209.110,415.226,228.858,447.126,251.288,474.785L371.998,430.671L277.417,504.449C294.635,521.771,312.591,536.670,331.111,548.765L396.081,470.998L358.366,564.253C377.625,573.924,397.183,580.217,416.674,582.837C534.164,599.232,652.310,618.566,782.785,703.535L773.618,601.955L831.163,737.792C852.261,754.489,876.370,771.191,897.168,782.701L861.169,684.190L960.409,811.519C976.589,817.512,992.991,822.477,1008.953,826.486C1066.083,840.287,1120.015,842.594,1157.256,819.002C1175.975,807.393,1189.205,786.963,1190.791,762.853C1191.080,756.933,1190.907,750.762,1190.359,745.690ZZ" style="fill: rgb(13 109 105);"/></g></g></g></svg>
|
Before Width: | Height: | Size: 1.7 KiB |
23
public/assets/ui-elements/button-ui-box-textured-small.svg
Normal file
After Width: | Height: | Size: 470 KiB |
26
public/assets/ui-elements/button-ui-box-textured.svg
Normal file
After Width: | Height: | Size: 471 KiB |
22
public/assets/ui-elements/button-ui-frame-empty.svg
Normal file
After Width: | Height: | Size: 598 KiB |
25
public/assets/ui-elements/character-select-ui-shape.svg
Normal file
@ -0,0 +1,25 @@
|
||||
<svg width="190" height="202" viewBox="0 0 190 202" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 8H8V0C7.40741 4.14815 4.14815 7.40741 0 8Z" fill="#1A1A1A"/>
|
||||
<path d="M7.55 0V0C7.55 4.16975 4.16975 7.55 0 7.55V7.55" stroke="#4D4D4D"/>
|
||||
<mask id="path-3-inside-1_632_705" fill="white">
|
||||
<path d="M0 8H8V194H0V8Z"/>
|
||||
</mask>
|
||||
<path d="M0 8H8V194H0V8Z" fill="#1A1A1A"/>
|
||||
<path d="M1 194V8H-1V194H1Z" fill="#4D4D4D" mask="url(#path-3-inside-1_632_705)"/>
|
||||
<path d="M0 194H8V202C7.40741 197.852 4.14815 194.593 0 194Z" fill="#1A1A1A"/>
|
||||
<path d="M7.55 202V202C7.55 197.83 4.16975 194.45 0 194.45V194.45" stroke="#4D4D4D"/>
|
||||
<mask id="path-7-inside-2_632_705" fill="white">
|
||||
<path d="M8 0H182V202H8V0Z"/>
|
||||
</mask>
|
||||
<path d="M8 0H182V202H8V0Z" fill="#1A1A1A"/>
|
||||
<path d="M8 1H182V-1H8V1ZM182 201H8V203H182V201Z" fill="#4D4D4D" mask="url(#path-7-inside-2_632_705)"/>
|
||||
<path d="M190 8H182V0C182.593 4.14815 185.852 7.40741 190 8Z" fill="#1A1A1A"/>
|
||||
<path d="M182.45 0V0C182.45 4.16975 185.83 7.55 190 7.55V7.55" stroke="#4D4D4D"/>
|
||||
<mask id="path-11-inside-3_632_705" fill="white">
|
||||
<path d="M190 8H182V194H190V8Z"/>
|
||||
</mask>
|
||||
<path d="M190 8H182V194H190V8Z" fill="#1A1A1A"/>
|
||||
<path d="M189 194V8H191V194H189Z" fill="#4D4D4D" mask="url(#path-11-inside-3_632_705)"/>
|
||||
<path d="M190 194H182V202C182.593 197.852 185.852 194.593 190 194Z" fill="#1A1A1A"/>
|
||||
<path d="M182.45 202V202C182.45 197.83 185.83 194.45 190 194.45V194.45" stroke="#4D4D4D"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
23
public/assets/ui-elements/hud-ui-box.svg
Normal file
After Width: | Height: | Size: 470 KiB |
Before Width: | Height: | Size: 301 KiB After Width: | Height: | Size: 301 KiB |
Before Width: | Height: | Size: 400 KiB After Width: | Height: | Size: 400 KiB |
23
public/assets/ui-elements/profile-ui-box-inner.svg
Normal file
After Width: | Height: | Size: 335 KiB |
23
public/assets/ui-elements/profile-ui-box-outer.svg
Normal file
After Width: | Height: | Size: 453 KiB |
BIN
public/assets/ui-texture.png
Normal file
After Width: | Height: | Size: 135 KiB |
69
src/App.vue
@ -1,36 +1,59 @@
|
||||
<template>
|
||||
<div class="overflow-hidden">
|
||||
<Notifications />
|
||||
<Login v-if="screen === 'login'" />
|
||||
<!-- <Register v-if="screen === 'register'" />-->
|
||||
<Characters v-if="screen === 'characters'" />
|
||||
<Game v-if="screen === 'game'" />
|
||||
</div>
|
||||
<BackgroundImageLoader />
|
||||
<GmPanel v-if="gameStore.character?.role === 'gm'" />
|
||||
<component :is="currentScreen" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import Notifications from '@/components/utilities/Notifications.vue'
|
||||
import Login from '@/screens/Login.vue'
|
||||
// import Register from '@/screens/Register.vue'
|
||||
import Characters from '@/screens/Characters.vue'
|
||||
import Game from '@/screens/Game.vue'
|
||||
import { computed } from 'vue'
|
||||
import BackgroundImageLoader from '@/components/utilities/BackgroundImageLoader.vue'
|
||||
import GmPanel from '@/components/gameMaster/GmPanel.vue'
|
||||
import Login from '@/components/screens/Login.vue'
|
||||
import Characters from '@/components/screens/Characters.vue'
|
||||
import Game from '@/components/screens/Game.vue'
|
||||
import ZoneEditor from '@/components/screens/ZoneEditor.vue'
|
||||
import { computed, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
|
||||
const screen = computed(() => {
|
||||
if (!gameStore.connection) {
|
||||
return 'login'
|
||||
} else if (gameStore.token && gameStore.connection) {
|
||||
if (gameStore.character) {
|
||||
return 'game'
|
||||
}
|
||||
return 'characters'
|
||||
}
|
||||
return 'login' // default fallback
|
||||
const currentScreen = computed(() => {
|
||||
if (!gameStore.connection) return Login
|
||||
if (!gameStore.token) return Login
|
||||
if (!gameStore.character) return Characters
|
||||
if (zoneEditorStore.active) return ZoneEditor
|
||||
return Game
|
||||
})
|
||||
|
||||
// Disable right click
|
||||
addEventListener('contextmenu', (event) => event.preventDefault())
|
||||
// Watch zoneEditorStore.active and empty gameStore.game.loadedAssets
|
||||
watch(
|
||||
() => zoneEditorStore.active,
|
||||
() => {
|
||||
gameStore.game.loadedAssets = []
|
||||
}
|
||||
)
|
||||
|
||||
// #209: Play sound when a button is pressed
|
||||
addEventListener('click', (event) => {
|
||||
if (!(event.target instanceof HTMLButtonElement)) {
|
||||
return
|
||||
}
|
||||
const audio = new Audio('/assets/music/click-btn.mp3')
|
||||
audio.play()
|
||||
})
|
||||
|
||||
// Watch for "G" key press and toggle the gm panel
|
||||
addEventListener('keydown', (event) => {
|
||||
if (gameStore.character?.role !== 'gm') return // Only allow toggling the gm panel if the character is a gm
|
||||
|
||||
// Check if no input is active or focus is on an input
|
||||
if (event.repeat || event.isComposing || event.defaultPrevented || document.activeElement?.tagName.toUpperCase() === 'INPUT' || document.activeElement?.tagName.toUpperCase() === 'TEXTAREA') return
|
||||
|
||||
if (event.key === 'G') {
|
||||
gameStore.toggleGmPanel()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
BIN
src/assets/fonts/upheavtt.ttf
Normal file
@ -4,11 +4,15 @@
|
||||
|
||||
// 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');
|
||||
@font-face {
|
||||
font-family: 'Upheaval';
|
||||
src: url('../fonts/upheavtt.ttf');
|
||||
}
|
||||
|
||||
//Globals
|
||||
|
||||
body {
|
||||
@apply bg-black m-0 select-none overscroll-none overflow-hidden;
|
||||
@apply bg-black m-0 select-none;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
|
||||
@ -16,6 +20,9 @@ body {
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-ms-user-select: none; /* IE 10 and IE 11 */
|
||||
user-select: none; /* Standard syntax */
|
||||
|
||||
// Disable pinch zoom
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
|
||||
h1,
|
||||
@ -57,7 +64,7 @@ input {
|
||||
}
|
||||
|
||||
.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 {
|
||||
@apply bg-gray-600/50 hover:cursor-not-allowed;
|
||||
&::placeholder {
|
||||
@ -83,7 +90,7 @@ button {
|
||||
@apply text-center;
|
||||
|
||||
&.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,
|
||||
&:hover {
|
||||
@ -92,7 +99,7 @@ button {
|
||||
}
|
||||
|
||||
&.btn-red {
|
||||
@apply bg-red text-gray-50;
|
||||
@apply bg-red text-gray-50 text-base leading-5 rounded py-2.5;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
@ -100,11 +107,57 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-empty {
|
||||
@apply text-gray-50 border-2 border-solid border-gray-500 text-base leading-5 rounded py-2.5;
|
||||
|
||||
&.active,
|
||||
&.selected,
|
||||
&:hover {
|
||||
@apply bg-gray-700 border-gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
&.eye-open {
|
||||
@apply bg-[url('/assets/icons/eye-closed.svg')] w-5 h-4 right-2.5;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
@apply bg-gray-500 border-gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
.character {
|
||||
&.active {
|
||||
@apply pr-px border-r-0;
|
||||
}
|
||||
}
|
||||
|
||||
.text-pixel {
|
||||
@apply text-white font-ui drop-shadow-pixel-black;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
&::-webkit-scrollbar {
|
||||
@apply block w-0.5 bg-gray-300 rounded-sm;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply bg-gray-175;
|
||||
}
|
||||
}
|
||||
|
||||
canvas {
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -webkit-crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
@ -1,28 +1,41 @@
|
||||
<template>
|
||||
<Scene name="effects" @preload="preloadScene" @create="createScene" @update="updateScene">
|
||||
</Scene>
|
||||
<Scene name="effects" @preload="preloadScene" @create="createScene" @update="updateScene" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Scene } from 'phavuer'
|
||||
import { useZoneStore } from '@/stores/zoneStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { onBeforeMount, onBeforeUnmount, ref } from 'vue'
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import type { WeatherState } from '@/types'
|
||||
|
||||
// Constants
|
||||
const SUNRISE_HOUR = 6
|
||||
const SUNSET_HOUR = 20
|
||||
const DAY_STRENGTH = 100
|
||||
const NIGHT_STRENGTH = 30
|
||||
|
||||
// Stores
|
||||
const gameStore = useGameStore()
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
const zoneStore = useZoneStore()
|
||||
|
||||
// Scene ref
|
||||
const sceneRef = ref<Phaser.Scene | null>(null)
|
||||
|
||||
// Effect-related refs
|
||||
const dayNightCycle = ref<Phaser.GameObjects.Graphics | null>(null)
|
||||
// Effect refs
|
||||
const lightEffect = ref<Phaser.GameObjects.Graphics | null>(null)
|
||||
const rainEmitter = ref<Phaser.GameObjects.Particles.ParticleEmitter | null>(null)
|
||||
const fogSprite = ref<Phaser.GameObjects.Sprite | null>(null)
|
||||
|
||||
// Effect parameters
|
||||
const dayNightDuration = 300000 // 5 minutes in milliseconds
|
||||
const maxDarkness = 0.7
|
||||
// State refs
|
||||
const weatherState = ref<WeatherState>({
|
||||
isRainEnabled: false,
|
||||
rainPercentage: 0,
|
||||
isFogEnabled: false,
|
||||
fogDensity: 0
|
||||
})
|
||||
|
||||
// Scene lifecycle methods
|
||||
const preloadScene = async (scene: Phaser.Scene) => {
|
||||
scene.load.image('raindrop', 'assets/raindrop.png')
|
||||
scene.load.image('fog', 'assets/fog.png')
|
||||
@ -30,28 +43,24 @@ const preloadScene = async (scene: Phaser.Scene) => {
|
||||
|
||||
const createScene = async (scene: Phaser.Scene) => {
|
||||
sceneRef.value = scene
|
||||
createDayNightCycle(scene)
|
||||
setupEffects(scene)
|
||||
setupSocketListeners()
|
||||
}
|
||||
|
||||
const updateScene = () => {
|
||||
updateEffects()
|
||||
}
|
||||
|
||||
// Effect setup
|
||||
const setupEffects = (scene: Phaser.Scene) => {
|
||||
createLightEffect(scene)
|
||||
createRainEffect(scene)
|
||||
createFogEffect(scene)
|
||||
}
|
||||
|
||||
const updateScene = (scene: Phaser.Scene, time: number) => {
|
||||
updateDayNightCycle(time)
|
||||
updateFogEffect()
|
||||
}
|
||||
|
||||
const createDayNightCycle = (scene: Phaser.Scene) => {
|
||||
dayNightCycle.value = scene.add.graphics()
|
||||
dayNightCycle.value.setDepth(1000)
|
||||
}
|
||||
|
||||
const updateDayNightCycle = (time: number) => {
|
||||
if (!dayNightCycle.value) return
|
||||
|
||||
const darkness = Math.sin((time % dayNightDuration) / dayNightDuration * Math.PI) * maxDarkness
|
||||
dayNightCycle.value.clear()
|
||||
dayNightCycle.value.fillStyle(0x000000, darkness)
|
||||
dayNightCycle.value.fillRect(0, 0, window.innerWidth, window.innerHeight)
|
||||
const createLightEffect = (scene: Phaser.Scene) => {
|
||||
lightEffect.value = scene.add.graphics()
|
||||
lightEffect.value.setDepth(1000)
|
||||
}
|
||||
|
||||
const createRainEffect = (scene: Phaser.Scene) => {
|
||||
@ -66,44 +75,144 @@ const createRainEffect = (scene: Phaser.Scene) => {
|
||||
blendMode: 'ADD'
|
||||
})
|
||||
rainEmitter.value.setDepth(900)
|
||||
toggleRain(true) // Start with rain off
|
||||
}
|
||||
|
||||
const toggleRain = (isRaining: boolean) => {
|
||||
if (rainEmitter.value) {
|
||||
rainEmitter.value.setVisible(isRaining)
|
||||
}
|
||||
rainEmitter.value.stop()
|
||||
}
|
||||
|
||||
const createFogEffect = (scene: Phaser.Scene) => {
|
||||
fogSprite.value = scene.add.sprite(window.innerWidth / 2, window.innerHeight / 2, 'fog')
|
||||
fogSprite.value.setScale(2)
|
||||
fogSprite.value.setAlpha(0) // yeetdasasdasd
|
||||
fogSprite.value.setDepth(950) // yeetdasasdasd
|
||||
fogSprite.value.setAlpha(0)
|
||||
fogSprite.value.setDepth(950)
|
||||
}
|
||||
|
||||
const updateFogEffect = () => {
|
||||
// Lighting calculations
|
||||
const calculateLightStrength = (time: Date): number => {
|
||||
const hour = time.getHours()
|
||||
const minute = time.getMinutes()
|
||||
|
||||
let strength = DAY_STRENGTH
|
||||
|
||||
// Night time (10 PM - 6 AM)
|
||||
if (hour >= SUNSET_HOUR || hour < SUNRISE_HOUR) {
|
||||
strength = NIGHT_STRENGTH
|
||||
}
|
||||
// Full daylight (7 AM - 7 PM)
|
||||
else if (hour > SUNRISE_HOUR && hour < SUNSET_HOUR - 2) {
|
||||
strength = DAY_STRENGTH
|
||||
}
|
||||
// Sunrise transition (6 AM - 7 AM)
|
||||
else if (hour === SUNRISE_HOUR) {
|
||||
strength = NIGHT_STRENGTH + ((DAY_STRENGTH - NIGHT_STRENGTH) * minute) / 60
|
||||
}
|
||||
// Sunset transition (8 PM - 10 PM)
|
||||
else if (hour >= SUNSET_HOUR - 2 && hour < SUNSET_HOUR) {
|
||||
const totalMinutes = (hour - (SUNSET_HOUR - 2)) * 60 + minute
|
||||
const transitionProgress = totalMinutes / 120 // 2 hours = 120 minutes
|
||||
strength = DAY_STRENGTH - (DAY_STRENGTH - NIGHT_STRENGTH) * transitionProgress
|
||||
}
|
||||
|
||||
return strength
|
||||
}
|
||||
|
||||
// Effect updates
|
||||
const updateEffects = () => {
|
||||
const effects = zoneStore.zone?.zoneEffects || []
|
||||
|
||||
if (effects.length > 0) {
|
||||
updateZoneEffects(effects)
|
||||
} else {
|
||||
// Make sure we're getting the current time
|
||||
const lightStrength = calculateLightStrength(gameStore.world.date)
|
||||
updateLightEffect(lightStrength)
|
||||
updateWeatherEffects()
|
||||
}
|
||||
}
|
||||
|
||||
const updateZoneEffects = (effects: any[]) => {
|
||||
// Always update light based on time when zone effects are present
|
||||
updateLightEffect(calculateLightStrength(gameStore.world.date))
|
||||
|
||||
effects.forEach((effect) => {
|
||||
switch (effect.effect) {
|
||||
case 'rain':
|
||||
updateRainEffect(effect.strength)
|
||||
break
|
||||
case 'fog':
|
||||
updateFogEffect(effect.strength)
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateWeatherEffects = () => {
|
||||
updateRainEffect(weatherState.value.isRainEnabled ? weatherState.value.rainPercentage : 0)
|
||||
updateFogEffect(weatherState.value.isFogEnabled ? weatherState.value.fogDensity * 100 : 0)
|
||||
}
|
||||
|
||||
const updateLightEffect = (strength: number) => {
|
||||
if (!lightEffect.value) return
|
||||
const darkness = 1 - strength / 100
|
||||
lightEffect.value.clear()
|
||||
lightEffect.value.fillStyle(0x000000, darkness)
|
||||
lightEffect.value.fillRect(0, 0, window.innerWidth, window.innerHeight)
|
||||
}
|
||||
|
||||
const updateRainEffect = (strength: number) => {
|
||||
if (!rainEmitter.value) return
|
||||
if (strength > 0) {
|
||||
rainEmitter.value.start()
|
||||
rainEmitter.value.setQuantity(Math.floor((strength / 100) * 10))
|
||||
} else {
|
||||
rainEmitter.value.stop()
|
||||
}
|
||||
}
|
||||
|
||||
const updateFogEffect = (strength: number) => {
|
||||
if (!fogSprite.value) return
|
||||
fogSprite.value.setAlpha(strength / 100)
|
||||
}
|
||||
|
||||
// Socket handlers
|
||||
const setupSocketListeners = () => {
|
||||
// Initial weather state
|
||||
gameStore.connection?.emit('weather', (response: WeatherState) => {
|
||||
if (zoneStore.zone?.zoneEffects) return
|
||||
weatherState.value = response
|
||||
updateEffects()
|
||||
})
|
||||
|
||||
// Weather updates
|
||||
gameStore.connection?.on('weather', (data: WeatherState) => {
|
||||
weatherState.value = data
|
||||
updateEffects()
|
||||
})
|
||||
|
||||
// Time updates
|
||||
gameStore.connection?.on('date', () => {
|
||||
if (zoneStore.zone?.zoneEffects) return
|
||||
updateEffects()
|
||||
})
|
||||
}
|
||||
|
||||
const updateEffectWindowSize = () => {
|
||||
if (rainEmitter.value) rainEmitter.value.updateConfig({ x: { min: 0, max: window.innerWidth } })
|
||||
if (fogSprite.value) {
|
||||
// Example: Oscillate fog opacity
|
||||
const fogOpacity = (Math.sin(Date.now() / 5000) + 1) / 2 * 0.3
|
||||
fogSprite.value.setAlpha(fogOpacity)
|
||||
fogSprite.value.setX(window.innerWidth / 2)
|
||||
fogSprite.value.setY(window.innerHeight / 2)
|
||||
}
|
||||
}
|
||||
|
||||
// Expose methods to control effects
|
||||
const controlEffects = {
|
||||
toggleRain,
|
||||
setFogDensity: (density: number) => {
|
||||
if (fogSprite.value) {
|
||||
fogSprite.value.setAlpha(density)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Watchers
|
||||
watch(() => zoneStore.zone?.zoneEffects, updateEffects, { deep: true })
|
||||
|
||||
// Make control methods available to parent components
|
||||
defineExpose(controlEffects)
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', updateEffectWindowSize)
|
||||
})
|
||||
|
||||
// Cleanup
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', updateEffectWindowSize)
|
||||
if (sceneRef.value) sceneRef.value.scene.remove('effects')
|
||||
gameStore.connection?.off('weather')
|
||||
})
|
||||
</script>
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="flex flex-wrap items-center input-field gap-1">
|
||||
<div v-for="(chip, i) in internalValue" :key="i" class="flex gap-2.5 items-center bg-cyan rounded py-1 px-2">
|
||||
<span class="text-xs">{{ chip }}</span>
|
||||
<span class="text-xs text-white">{{ chip }}</span>
|
||||
<button type="button" class="text-xs cursor-pointer text-white font-light font-default not-italic hover:text-gray-50" @click="deleteChip(i)" aria-label="Remove chip">×</button>
|
||||
</div>
|
||||
<input class="outline-none border-none p-1" placeholder="Tag name" v-model="currentInput" @keypress.enter.prevent="addChip" @keydown.backspace="handleBackspace" />
|
||||
<input class="outline-none border-none p-1 text-gray-300" placeholder="Tag name" v-model="currentInput" @keypress.enter.prevent="addChip" @keydown.backspace="handleBackspace" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
<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" :disable-bg-texture="true">
|
||||
<template #modalHeader>
|
||||
<h3 class="m-0 font-medium shrink-0 text-gray-300">GM Panel</h3>
|
||||
<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">Users</button>
|
||||
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">Chats</button>
|
||||
<button @mousedown.stop class="btn-cyan active py-1.5 px-4 min-w-24">Asset manager</button>
|
||||
<button class="btn-cyan py-1.5 px-4 min-w-24" type="button" @click="() => zoneEditorStore.toggleActive()">Zone manager</button>
|
||||
</div>
|
||||
</template>
|
||||
<template #modalBody>
|
||||
@ -22,8 +22,10 @@ import { ref } from 'vue'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import AssetManager from '@/components/gameMaster/assetManager/AssetManager.vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
|
||||
let toggle = ref('asset-manager')
|
||||
</script>
|
||||
|
@ -1,44 +0,0 @@
|
||||
<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">
|
||||
<template #modalHeader>
|
||||
<h3 class="m-0 font-medium shrink-0 text-gray-300">GM tools</h3>
|
||||
</template>
|
||||
<template #modalBody>
|
||||
<div class="content flex flex-col gap-2.5 m-4 h-20">
|
||||
<button class="btn-cyan py-1.5 px-4 w-full" type="button" @click="gameStore.toggleGmPanel()">Toggle GM panel</button>
|
||||
<button class="btn-cyan py-1.5 px-4 w-full" type="button" @click="() => zoneEditorStore.toggleActive()">Zone manager</button>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
const gameStore = useGameStore()
|
||||
const modalWidth = ref(200)
|
||||
const modalHeight = ref(180)
|
||||
|
||||
let posXY = ref({ x: 0, y: 0 })
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
posXY.value = customPositionGmPanel(modalWidth.value)
|
||||
})
|
||||
})
|
||||
|
||||
const customPositionGmPanel = (modalWidth: number) => {
|
||||
const padding = 25
|
||||
const width = window.innerWidth
|
||||
|
||||
const x = width - (modalWidth + 4) - 25
|
||||
const y = padding
|
||||
|
||||
return { x, y }
|
||||
}
|
||||
|
||||
posXY.value = customPositionGmPanel(modalWidth.value)
|
||||
</script>
|
@ -1,56 +1,70 @@
|
||||
<template>
|
||||
<div class="flex h-full w-full relative">
|
||||
<div class="w-2/12 flex flex-col relative">
|
||||
<div class="w-2/12 flex flex-col relative overflow-auto">
|
||||
<!-- Asset Categories -->
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'tiles' }" @click="() => (selectedCategory = 'tiles')">
|
||||
<span>Tiles</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
||||
<span :class="{ 'text-white': selectedCategory === 'tiles' }">Tiles</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'objects' }" @click="() => (selectedCategory = 'objects')">
|
||||
<span>Objects</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
||||
<span :class="{ 'text-white': selectedCategory === 'objects' }">Objects</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'sprites' }" @click="() => (selectedCategory = 'sprites')">
|
||||
<span>Sprites</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
||||
<span :class="{ 'text-white': selectedCategory === 'sprites' }">Sprites</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer">
|
||||
<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 class="relative p-2.5 hover:cursor-pointer">
|
||||
<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 class="relative p-2.5 hover:cursor-pointer">
|
||||
<span>Characters</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'shops' }" @click="() => (selectedCategory = 'shops')">
|
||||
<span :class="{ 'text-white': selectedCategory === 'shops' }">Shops</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'characterTypes' }" @click="() => (selectedCategory = 'characterTypes')">
|
||||
<span :class="{ 'text-white': selectedCategory === 'characterTypes' }">Character types</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'characterHair' }" @click="() => (selectedCategory = 'characterHair')">
|
||||
<span :class="{ 'text-white': selectedCategory === 'characterHair' }">Character hair</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer">
|
||||
<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 class="relative p-2.5 hover:cursor-pointer">
|
||||
<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 class="relative p-2.5 hover:cursor-pointer">
|
||||
<span>Emoticons</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
</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 -->
|
||||
<div class="overflow-auto h-full w-4/12 flex flex-col relative">
|
||||
<TileList v-if="selectedCategory === 'tiles'" />
|
||||
<ObjectList v-if="selectedCategory === 'objects'" />
|
||||
<SpriteList v-if="selectedCategory === 'sprites'" />
|
||||
<CharacterTypeList v-if="selectedCategory === 'characterTypes'" />
|
||||
</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 -->
|
||||
<div class="flex w-1/2 after:hidden flex-col relative overflow-auto">
|
||||
<TileDetails v-if="selectedCategory === 'tiles' && assetManagerStore.selectedTile" />
|
||||
<ObjectDetails v-if="selectedCategory === 'objects' && assetManagerStore.selectedObject" />
|
||||
<SpriteDetails v-if="selectedCategory === 'sprites' && assetManagerStore.selectedSprite" />
|
||||
<CharacterTypeDetails v-if="selectedCategory === 'characterTypes' && assetManagerStore.selectedCharacterType" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -64,6 +78,8 @@ import ObjectList from '@/components/gameMaster/assetManager/partials/object/Obj
|
||||
import ObjectDetails from '@/components/gameMaster/assetManager/partials/object/ObjectDetails.vue'
|
||||
import SpriteList from '@/components/gameMaster/assetManager/partials/sprite/SpriteList.vue'
|
||||
import SpriteDetails from '@/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue'
|
||||
import CharacterTypeList from '@/components/gameMaster/assetManager/partials/characterType/CharacterTypeList.vue'
|
||||
import CharacterTypeDetails from '@/components/gameMaster/assetManager/partials/characterType/CharacterTypeDetails.vue'
|
||||
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
const selectedCategory = ref('tiles')
|
||||
|
@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="h-full overflow-auto">
|
||||
<div class="m-2.5 p-2.5 block">
|
||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveCharacterType">
|
||||
<div class="form-field-full">
|
||||
<label for="name">Name</label>
|
||||
<input v-model="characterName" class="input-field" type="text" name="name" placeholder="Character Type Name" />
|
||||
</div>
|
||||
<div class="form-field-half">
|
||||
<label for="gender">Gender</label>
|
||||
<select v-model="characterGender" class="input-field" name="gender">
|
||||
<option v-for="gender in genderOptions" :key="gender" :value="gender">{{ gender }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-half">
|
||||
<label for="race">Race</label>
|
||||
<select v-model="characterRace" class="input-field" name="race">
|
||||
<option v-for="race in raceOptions" :key="race" :value="race">{{ race }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="isEnabledForCharCreation">Is enabled for character creation</label>
|
||||
<select v-model="characterIsEnabledForCharCreation" class="input-field" name="isEnabledForCharCreation">
|
||||
<option :value="false">No</option>
|
||||
<option :value="true">Yes</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="spriteId">Sprite</label>
|
||||
<select v-model="characterSpriteId" class="input-field" name="spriteId">
|
||||
<option disabled selected value="">Select sprite</option>
|
||||
<option v-for="sprite in assetManagerStore.spriteList" :key="sprite.id" :value="sprite.id">{{ sprite.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button>
|
||||
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="removeCharacterType">Remove</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CharacterType, CharacterGender, CharacterRace, Sprite } from '@/types'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
||||
const selectedCharacterType = computed(() => assetManagerStore.selectedCharacterType)
|
||||
|
||||
const characterName = ref('')
|
||||
const characterGender = ref<CharacterGender>('MALE' as CharacterGender.MALE)
|
||||
const characterRace = ref<CharacterRace>('HUMAN' as CharacterRace.HUMAN)
|
||||
const characterIsEnabledForCharCreation = ref<boolean>(false)
|
||||
const characterSpriteId = ref<string | null | undefined>(null)
|
||||
|
||||
const genderOptions: CharacterGender[] = ['MALE' as CharacterGender.MALE, 'FEMALE' as CharacterGender.FEMALE]
|
||||
const raceOptions: CharacterRace[] = ['HUMAN' as CharacterRace.HUMAN, 'ELF' as CharacterRace.ELF, 'DWARF' as CharacterRace.DWARF, 'ORC' as CharacterRace.ORC, 'GOBLIN' as CharacterRace.GOBLIN]
|
||||
|
||||
if (!selectedCharacterType.value) {
|
||||
console.error('No character type selected')
|
||||
}
|
||||
|
||||
if (selectedCharacterType.value) {
|
||||
characterName.value = selectedCharacterType.value.name
|
||||
characterGender.value = selectedCharacterType.value.gender
|
||||
characterRace.value = selectedCharacterType.value.race
|
||||
characterIsEnabledForCharCreation.value = selectedCharacterType.value.isEnabledForCharCreation
|
||||
characterSpriteId.value = selectedCharacterType.value.spriteId
|
||||
}
|
||||
|
||||
function removeCharacterType() {
|
||||
if (!selectedCharacterType.value) return
|
||||
|
||||
gameStore.connection?.emit('gm:characterType:remove', { id: selectedCharacterType.value.id }, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to remove character type')
|
||||
return
|
||||
}
|
||||
refreshCharacterTypeList()
|
||||
})
|
||||
}
|
||||
|
||||
function refreshCharacterTypeList(unsetSelectedCharacterType = true) {
|
||||
gameStore.connection?.emit('gm:characterType:list', {}, (response: CharacterType[]) => {
|
||||
assetManagerStore.setCharacterTypeList(response)
|
||||
|
||||
if (unsetSelectedCharacterType) {
|
||||
assetManagerStore.setSelectedCharacterType(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function saveCharacterType() {
|
||||
const characterTypeData = {
|
||||
id: selectedCharacterType.value!.id,
|
||||
name: characterName.value,
|
||||
gender: characterGender.value,
|
||||
race: characterRace.value,
|
||||
isEnabledForCharCreation: characterIsEnabledForCharCreation.value,
|
||||
spriteId: characterSpriteId.value
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:characterType:update', characterTypeData, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to save character type')
|
||||
return
|
||||
}
|
||||
refreshCharacterTypeList(false)
|
||||
})
|
||||
}
|
||||
|
||||
watch(selectedCharacterType, (characterType: CharacterType | null) => {
|
||||
if (!characterType) return
|
||||
characterName.value = characterType.name
|
||||
characterGender.value = characterType.gender
|
||||
characterRace.value = characterType.race
|
||||
characterIsEnabledForCharCreation.value = characterType.isEnabledForCharCreation
|
||||
characterSpriteId.value = characterType.spriteId
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (!selectedCharacterType.value) return
|
||||
|
||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
assetManagerStore.setSelectedCharacterType(null)
|
||||
})
|
||||
</script>
|
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
||||
<input v-model="searchQuery" class="input-field flex-grow" placeholder="Search..." @input="handleSearch" />
|
||||
<label for="create-character" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<button class="p-0 h-5" id="create-character" @click="createNewCharacterType">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="white">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</button>
|
||||
</label>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</div>
|
||||
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||
<a v-for="{ data: characterType } in list" :key="characterType.id" class="relative p-2.5 cursor-pointer block" :class="{ 'bg-cyan/80': assetManagerStore.selectedCharacterType?.id === characterType.id }" @click="assetManagerStore.setSelectedCharacterType(characterType as CharacterType)">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span :class="{ 'text-white': assetManagerStore.selectedCharacterType?.id === characterType.id }">{{ characterType.name }}</span>
|
||||
</div>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
</div>
|
||||
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan p-0 hover:bg-cyan-800" 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="" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import type { CharacterType } from '@/types'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
const hasScrolled = ref(false)
|
||||
const elementToScroll = ref()
|
||||
|
||||
const handleSearch = () => {
|
||||
// Trigger a re-render of the virtual list
|
||||
virtualList.value?.scrollTo(0)
|
||||
}
|
||||
|
||||
const createNewCharacterType = () => {
|
||||
gameStore.connection?.emit('gm:characterType:create', {}, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to create new character type')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:characterType:list', {}, (response: CharacterType[]) => {
|
||||
assetManagerStore.setCharacterTypeList(response)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const filteredCharacterTypes = computed(() => {
|
||||
if (!searchQuery.value) {
|
||||
return assetManagerStore.characterTypeList
|
||||
}
|
||||
return assetManagerStore.characterTypeList.filter((character) => character.name.toLowerCase().includes(searchQuery.value.toLowerCase()))
|
||||
})
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(filteredCharacterTypes, {
|
||||
itemHeight: 48
|
||||
})
|
||||
|
||||
const virtualList = ref({ scrollTo })
|
||||
|
||||
const onScroll = () => {
|
||||
let scrollTop = elementToScroll.value.style.marginTop.replace('px', '')
|
||||
|
||||
if (scrollTop > 80) {
|
||||
hasScrolled.value = true
|
||||
} else if (scrollTop <= 80) {
|
||||
hasScrolled.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function toTop() {
|
||||
virtualList.value?.scrollTo(0)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit('gm:characterType:list', {}, (response: CharacterType[]) => {
|
||||
assetManagerStore.setCharacterTypeList(response)
|
||||
})
|
||||
})
|
||||
</script>
|
@ -4,7 +4,7 @@
|
||||
<div class="filler"></div>
|
||||
<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>
|
||||
<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 class="m-2.5 p-2.5 block">
|
||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveObject">
|
||||
@ -118,7 +118,6 @@ function saveObject() {
|
||||
return
|
||||
}
|
||||
|
||||
console.log(objectTags.value)
|
||||
gameStore.connection?.emit(
|
||||
'gm:object:update',
|
||||
{
|
||||
|
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
||||
<input v-model="searchQuery" class="input-field flex-grow" placeholder="Search..." @input="handleSearch" />
|
||||
<label for="upload-asset" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 p-2 inline-flex items-center justify-center hover:bg-cyan hover:cursor-pointer">
|
||||
<label for="upload-asset" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<input class="hidden" id="upload-asset" ref="objectUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</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 v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||
@ -16,12 +16,12 @@
|
||||
<div class="h-7 w-16 max-w-16 flex justify-center">
|
||||
<img class="h-7" :src="`${config.server_endpoint}/assets/objects/${object.id}.png`" alt="Object" />
|
||||
</div>
|
||||
<span>{{ object.name }}</span>
|
||||
<span :class="{ 'text-white': assetManagerStore.selectedObject?.id === object.id }">{{ object.name }}</span>
|
||||
</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>
|
||||
</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 p-0 hover:bg-cyan-800" 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="" />
|
||||
</button>
|
||||
</div>
|
||||
|
@ -10,7 +10,7 @@
|
||||
<div class="w-full flex gap-2 mt-2 pb-4 relative">
|
||||
<button class="btn-cyan px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="saveSprite">Save</button>
|
||||
<button class="btn-red px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="deleteSprite">Delete</button>
|
||||
<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>
|
||||
|
||||
|
@ -1,23 +1,23 @@
|
||||
<template>
|
||||
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
||||
<input v-model="searchQuery" class="input-field flex-grow" placeholder="Search..." @input="handleSearch" />
|
||||
<button @click.prevent="newButtonClickHandler" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 p-2 inline-flex items-center justify-center hover:bg-cyan hover:cursor-pointer">
|
||||
<button @click.prevent="newButtonClickHandler" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</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 v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||
<a v-for="{ data: sprite } in list" :key="sprite.id" class="relative p-2.5 cursor-pointer block" :class="{ 'bg-cyan/80': assetManagerStore.selectedSprite?.id === sprite.id }" @click="assetManagerStore.setSelectedSprite(sprite as Sprite)">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span>{{ sprite.name }}</span>
|
||||
<span :class="{ 'text-white': assetManagerStore.selectedSprite?.id === sprite.id }">{{ sprite.name }}</span>
|
||||
</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>
|
||||
</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 p-0 hover:bg-cyan-800" 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="" />
|
||||
</button>
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div class="filler"></div>
|
||||
<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>
|
||||
<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 class="m-2.5 p-2.5 block">
|
||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveTile">
|
||||
|
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
||||
<input v-model="searchQuery" class="input-field flex-grow" placeholder="Search..." @input="handleSearch" />
|
||||
<label for="upload-asset" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 p-2 inline-flex items-center justify-center hover:bg-cyan hover:cursor-pointer">
|
||||
<label for="upload-asset" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<input class="hidden" id="upload-asset" ref="tileUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</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 v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||
@ -16,12 +16,12 @@
|
||||
<div class="h-7 w-16 max-w-16 flex justify-center">
|
||||
<img class="h-7" :src="`${config.server_endpoint}/assets/tiles/${tile.id}.png`" alt="Tile" />
|
||||
</div>
|
||||
<span>{{ tile.name }}</span>
|
||||
<span :class="{ 'text-white': assetManagerStore.selectedTile?.id === tile.id }">{{ tile.name }}</span>
|
||||
</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>
|
||||
</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 p-0 hover:bg-cyan-800" 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="" />
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1,215 +1,61 @@
|
||||
<template>
|
||||
<Toolbar :layer="tiles" @eraser="eraser" @pencil="pencil" @paint="paint" @clear="clear" @save="save" />
|
||||
<ZoneList v-if="zoneEditorStore.isZoneListModalShown" />
|
||||
<ZoneTiles @tileMap:create="tileMap = $event" />
|
||||
<ZoneObjects v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />
|
||||
<ZoneEventTiles v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />
|
||||
|
||||
<template v-if="zoneEditorStore.zone">
|
||||
<Controls :layer="tiles as TilemapLayer" />
|
||||
<Toolbar @save="save" @clear="clear" />
|
||||
|
||||
<Tiles />
|
||||
<Objects />
|
||||
<ZoneList />
|
||||
<TileList />
|
||||
<ObjectList />
|
||||
|
||||
<ZoneSettings />
|
||||
<TeleportModal v-if="shouldShowTeleportModal" />
|
||||
|
||||
<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>
|
||||
<TeleportModal />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeMount, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { Container, Image, useScene } from 'phavuer'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onUnmounted, ref } from 'vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { calculateIsometricDepth, loadAssets, placeTile, setAllTiles, sortByIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
||||
import { ZoneEventTileType, type ZoneObject, type ZoneEventTile, type Zone } from '@/types'
|
||||
import { uuidv4 } from '@/utilities'
|
||||
import config from '@/config'
|
||||
|
||||
import { type Zone } from '@/types'
|
||||
// Components
|
||||
import Controls from '@/components/utilities/Controls.vue'
|
||||
import Toolbar from '@/components/gameMaster/zoneEditor/partials/Toolbar.vue'
|
||||
import Tiles from '@/components/gameMaster/zoneEditor/partials/TileList.vue'
|
||||
import SelectedZoneObject from '@/components/gameMaster/zoneEditor/partials/SelectedZoneObject.vue'
|
||||
import TileList from '@/components/gameMaster/zoneEditor/partials/TileList.vue'
|
||||
import ObjectList from '@/components/gameMaster/zoneEditor/partials/ObjectList.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 TeleportModal from '@/components/gameMaster/zoneEditor/partials/TeleportModal.vue'
|
||||
import Tilemap = Phaser.Tilemaps.Tilemap
|
||||
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
|
||||
import ZoneTiles from '@/components/gameMaster/zoneEditor/zonePartials/ZoneTiles.vue'
|
||||
import ZoneObjects from '@/components/gameMaster/zoneEditor/zonePartials/ZoneObjects.vue'
|
||||
import ZoneEventTiles from '@/components/gameMaster/zoneEditor/zonePartials/ZoneEventTiles.vue'
|
||||
|
||||
/**
|
||||
* @TODO:
|
||||
* Clean all the code in this file
|
||||
*/
|
||||
|
||||
const scene = useScene()
|
||||
const gameStore = useGameStore()
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
|
||||
const { objectList, zone, selectedTile, selectedObject, selectedZoneObject, eraserMode, drawMode } = storeToRefs(zoneEditorStore)
|
||||
const tileMap = ref(null as Phaser.Tilemaps.Tilemap | null)
|
||||
|
||||
const zoneTilemap = createTilemap()
|
||||
const tiles = createTileLayer()
|
||||
const zoneObjects = ref<ZoneObject[]>([])
|
||||
const zoneEventTiles = ref<ZoneEventTile[]>([])
|
||||
let tileArray = createTileArray()
|
||||
function clear() {
|
||||
if (!zoneEditorStore.zone) return
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
// Clear objects, event tiles and tiles
|
||||
zoneEditorStore.zone.zoneObjects = []
|
||||
zoneEditorStore.zone.zoneEventTiles = []
|
||||
zoneEditorStore.triggerClearTiles()
|
||||
}
|
||||
|
||||
function save() {
|
||||
if (!zone.value) return
|
||||
if (!zoneEditorStore.zone) return
|
||||
|
||||
const data = {
|
||||
zoneId: zone.value.id,
|
||||
zoneId: zoneEditorStore.zone.id,
|
||||
name: zoneEditorStore.zoneSettings.name,
|
||||
width: zoneEditorStore.zoneSettings.width,
|
||||
height: zoneEditorStore.zoneSettings.height,
|
||||
tiles: tileArray,
|
||||
pvp: zone.value.pvp,
|
||||
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 }))
|
||||
tiles: zoneEditorStore.zone.tiles,
|
||||
pvp: zoneEditorStore.zone.pvp,
|
||||
zoneEffects: zoneEditorStore.zone.zoneEffects.map(({ id, zoneId, effect, strength }) => ({ id, zoneId, effect, strength })),
|
||||
zoneEventTiles: zoneEditorStore.zone.zoneEventTiles.map(({ id, zoneId, type, positionX, positionY, teleport }) => ({ id, zoneId, type, positionX, positionY, teleport })),
|
||||
zoneObjects: zoneEditorStore.zone.zoneObjects.map(({ id, zoneId, objectId, depth, isRotated, positionX, positionY }) => ({ id, zoneId, objectId, depth, isRotated, positionX, positionY }))
|
||||
}
|
||||
|
||||
if (zoneEditorStore.isSettingsModalShown) {
|
||||
@ -217,111 +63,11 @@ function save() {
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:zone_editor:zone:update', data, (response: Zone) => {
|
||||
console.log('zone updated')
|
||||
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 () => {
|
||||
await gameStore.fetchAllZoneAssets()
|
||||
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(() => {
|
||||
zoneEventTiles.value = []
|
||||
zoneObjects.value = []
|
||||
tiles?.destroy()
|
||||
zoneTilemap?.removeAllLayers()
|
||||
zoneTilemap?.destroy()
|
||||
zoneEditorStore.reset()
|
||||
})
|
||||
</script>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<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="420" :is-resizable="false" :disable-bg-texture="true">
|
||||
<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 #modalBody>
|
||||
|
@ -1,22 +1,17 @@
|
||||
<template>
|
||||
<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)" :disable-bg-texture="true">
|
||||
<template #modalHeader>
|
||||
<h3 class="text-lg text-gray-300">Objects</h3>
|
||||
<div class="flex">
|
||||
<h3 class="text-lg text-white">Objects</h3>
|
||||
</template>
|
||||
<template #modalBody>
|
||||
<div class="flex pt-4 pl-4">
|
||||
<div class="w-full flex gap-1.5 flex-row">
|
||||
<div>
|
||||
<label class="mb-1.5 font-titles hidden" for="search">Search...</label>
|
||||
<input @mousedown.stop class="input-field" type="text" name="search" placeholder="Search" v-model="searchQuery" />
|
||||
</div>
|
||||
<!-- <div>-->
|
||||
<!-- <label class="mb-1.5 font-titles hidden" for="depth">Depth</label>-->
|
||||
<!-- <input v-model="objectDepth" @mousedown.stop class="input-field" type="number" name="depth" placeholder="Depth" />-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #modalBody>
|
||||
<div class="flex flex-col h-full p-4">
|
||||
<div class="mb-4 flex flex-wrap gap-2">
|
||||
<button v-for="tag in uniqueTags" :key="tag" @click="toggleTag(tag)" class="btn-cyan" :class="{ 'opacity-50': !selectedTags.includes(tag) }">
|
||||
@ -43,28 +38,22 @@
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/config'
|
||||
import { ref, onMounted, computed, watch } from 'vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import type { Object } from '@/types'
|
||||
import type { Object, ZoneObject } from '@/types'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const isModalOpen = ref(false)
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
const searchQuery = ref('')
|
||||
// const objectDepth = ref(0)
|
||||
const selectedTags = ref<string[]>([])
|
||||
|
||||
// watch(objectDepth, (depth) => {
|
||||
// zoneEditorStore.setObjectDepth(depth)
|
||||
// })
|
||||
|
||||
const uniqueTags = computed(() => {
|
||||
const allTags = zoneEditorStore.objectList.flatMap((obj) => obj.tags || [])
|
||||
return Array.from(new Set(allTags))
|
||||
@ -87,8 +76,6 @@ const toggleTag = (tag: string) => {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
zoneEditorStore.setObjectDepth(0)
|
||||
|
||||
isModalOpen.value = true
|
||||
gameStore.connection?.emit('gm:object:list', {}, (response: Object[]) => {
|
||||
zoneEditorStore.setObjectList(response)
|
||||
|
@ -1,10 +1,6 @@
|
||||
<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>
|
||||
<label class="mb-1.5 font-titles block text-sm text-gray-700 hidden" for="depth">Depth</label>
|
||||
<input v-model="objectDepth" @mousedown.stop @input="handleDepthInput" class="input-field max-w-24 px-2 py-1 border rounded" type="number" name="depth" placeholder="Depth" />
|
||||
</div>
|
||||
<button @mousedown.stop @click="handleDelete" class="btn-red py-1.5 px-4">
|
||||
<img src="/assets/icons/trashcan.svg" class="w-4 h-4" alt="Delete" />
|
||||
</button>
|
||||
@ -15,38 +11,23 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import type { ZoneObject } from '@/types'
|
||||
|
||||
const emit = defineEmits(['update_depth', 'move', 'delete', 'rotate'])
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
const props = defineProps<{
|
||||
zoneObject: ZoneObject
|
||||
}>()
|
||||
|
||||
const objectDepth = ref(zoneEditorStore.objectDepth)
|
||||
const emit = defineEmits(['move', 'rotate', 'delete'])
|
||||
|
||||
watch(
|
||||
() => zoneEditorStore.selectedZoneObject,
|
||||
(selectedZoneObject) => {
|
||||
objectDepth.value = selectedZoneObject?.depth ?? 0
|
||||
}
|
||||
)
|
||||
|
||||
const handleDepthInput = () => {
|
||||
const depth = parseFloat(objectDepth.value.toString())
|
||||
if (!isNaN(depth)) {
|
||||
emit('update_depth', depth)
|
||||
}
|
||||
const handleMove = () => {
|
||||
emit('move', props.zoneObject.id)
|
||||
}
|
||||
|
||||
const handleRotate = () => {
|
||||
emit('rotate', zoneEditorStore.selectedZoneObject?.id)
|
||||
}
|
||||
|
||||
const handleMove = () => {
|
||||
emit('move')
|
||||
emit('rotate', props.zoneObject.id)
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
emit('delete', zoneEditorStore.selectedZoneObject?.id)
|
||||
zoneEditorStore.setSelectedZoneObject(null)
|
||||
emit('delete', props.zoneObject.id)
|
||||
}
|
||||
</script>
|
||||
|
@ -1,9 +1,8 @@
|
||||
<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" :disable-bg-texture="true">
|
||||
<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 #modalBody>
|
||||
<div class="m-4">
|
||||
<form method="post" @submit.prevent="" class="inline">
|
||||
@ -40,12 +39,13 @@
|
||||
</template>
|
||||
|
||||
<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 { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import type { Zone } from '@/types'
|
||||
|
||||
const showTeleportModal = computed(() => zoneEditorStore.tool === 'pencil' && zoneEditorStore.drawMode === 'teleport')
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
const gameStore = useGameStore()
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<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)" :disable-bg-texture="true">
|
||||
<template #modalHeader>
|
||||
<h3 class="text-lg text-gray-300">Tiles</h3>
|
||||
<div class="flex">
|
||||
<h3 class="text-lg text-white">Tiles</h3>
|
||||
</template>
|
||||
<template #modalBody>
|
||||
<div class="h-full overflow-auto" v-if="!selectedGroup">
|
||||
<div class="flex pt-4 pl-4">
|
||||
<div class="w-full flex gap-1.5 flex-row">
|
||||
<div>
|
||||
<label class="mb-1.5 font-titles hidden" for="search">Search...</label>
|
||||
@ -11,8 +13,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #modalBody>
|
||||
<div class="flex flex-col h-full p-4">
|
||||
<div class="mb-4 flex flex-wrap gap-2">
|
||||
<button v-for="tag in uniqueTags" :key="tag" @click="toggleTag(tag)" class="btn-cyan" :class="{ 'opacity-50': !selectedTags.includes(tag) }">
|
||||
@ -21,25 +21,63 @@
|
||||
</div>
|
||||
<div class="h-[calc(100%_-_60px)] flex-grow overflow-y-auto">
|
||||
<div class="grid grid-cols-8 gap-2 justify-items-center">
|
||||
<div v-for="tile in filteredTiles" :key="tile.id" class="flex items-center justify-center">
|
||||
<div v-for="group in groupedTiles" :key="group.parent.id" class="flex flex-col items-center justify-center relative">
|
||||
<img
|
||||
class="max-w-full max-h-full border-2 border-solid"
|
||||
:src="`${config.server_endpoint}/assets/tiles/${tile.id}.png`"
|
||||
alt="Tile"
|
||||
@click="zoneEditorStore.setSelectedTile(tile)"
|
||||
class="max-w-full max-h-full border-2 border-solid cursor-pointer transition-all duration-300"
|
||||
:src="`${config.server_endpoint}/assets/tiles/${group.parent.id}.png`"
|
||||
:alt="group.parent.name"
|
||||
@click="openGroup(group)"
|
||||
@load="() => processTile(group.parent)"
|
||||
:class="{
|
||||
'cursor-pointer transition-all duration-300': true,
|
||||
'border-cyan shadow-lg scale-105': zoneEditorStore.selectedTile?.id === tile.id,
|
||||
'border-transparent hover:border-gray-300': zoneEditorStore.selectedTile?.id !== tile.id
|
||||
'border-cyan shadow-lg scale-105': isActiveTile(group.parent),
|
||||
'border-transparent hover:border-gray-300': !isActiveTile(group.parent)
|
||||
}"
|
||||
/>
|
||||
<span class="text-xs mt-1">{{ getTileCategory(group.parent) }}</span>
|
||||
<span v-if="group.children.length > 0" class="absolute top-0 right-0 bg-cyan text-white rounded-full w-5 h-5 flex items-center justify-center text-xs">
|
||||
{{ group.children.length + 1 }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="h-full overflow-auto">
|
||||
<div class="p-4">
|
||||
<button @click="closeGroup" class="btn-cyan mb-4">Back to All Tiles</button>
|
||||
<h4 class="text-lg mb-4">{{ selectedGroup.parent.name }} Group</h4>
|
||||
<div class="grid grid-cols-8 gap-2 justify-items-center">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<img
|
||||
class="max-w-full max-h-full border-2 border-solid cursor-pointer transition-all duration-300"
|
||||
:src="`${config.server_endpoint}/assets/tiles/${selectedGroup.parent.id}.png`"
|
||||
:alt="selectedGroup.parent.name"
|
||||
@click="selectTile(selectedGroup.parent.id)"
|
||||
:class="{
|
||||
'border-cyan shadow-lg scale-105': isActiveTile(selectedGroup.parent),
|
||||
'border-transparent hover:border-gray-300': !isActiveTile(selectedGroup.parent)
|
||||
}"
|
||||
/>
|
||||
<span class="text-xs mt-1">{{ getTileCategory(selectedGroup.parent) }}</span>
|
||||
</div>
|
||||
<div v-for="childTile in selectedGroup.children" :key="childTile.id" class="flex flex-col items-center justify-center">
|
||||
<img
|
||||
class="max-w-full max-h-full border-2 border-solid cursor-pointer transition-all duration-300"
|
||||
:src="`${config.server_endpoint}/assets/tiles/${childTile.id}.png`"
|
||||
:alt="childTile.name"
|
||||
@click="selectTile(childTile.id)"
|
||||
:class="{
|
||||
'border-cyan shadow-lg scale-105': isActiveTile(childTile),
|
||||
'border-transparent hover:border-gray-300': !isActiveTile(childTile)
|
||||
}"
|
||||
/>
|
||||
<span class="text-xs mt-1">{{ getTileCategory(childTile) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -55,20 +93,60 @@ const isModalOpen = ref(false)
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
const searchQuery = ref('')
|
||||
const selectedTags = ref<string[]>([])
|
||||
const tileCategories = ref<Map<string, string>>(new Map())
|
||||
const selectedGroup = ref<{ parent: Tile; children: Tile[] } | null>(null)
|
||||
|
||||
const uniqueTags = computed(() => {
|
||||
const allTags = zoneEditorStore.tileList.flatMap((tile) => tile.tags || [])
|
||||
return Array.from(new Set(allTags))
|
||||
})
|
||||
|
||||
const filteredTiles = computed(() => {
|
||||
return zoneEditorStore.tileList.filter((tile) => {
|
||||
const groupedTiles = computed(() => {
|
||||
const groups: { parent: Tile; children: Tile[] }[] = []
|
||||
const filteredTiles = zoneEditorStore.tileList.filter((tile) => {
|
||||
const matchesSearch = !searchQuery.value || tile.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
const matchesTags = selectedTags.value.length === 0 || (tile.tags && selectedTags.value.some((tag) => tile.tags.includes(tag)))
|
||||
return matchesSearch && matchesTags
|
||||
})
|
||||
|
||||
filteredTiles.forEach((tile) => {
|
||||
const parentGroup = groups.find((group) => areTilesRelated(group.parent, tile))
|
||||
if (parentGroup && parentGroup.parent.id !== tile.id) {
|
||||
parentGroup.children.push(tile)
|
||||
} else {
|
||||
groups.push({ parent: tile, children: [] })
|
||||
}
|
||||
})
|
||||
|
||||
return groups
|
||||
})
|
||||
|
||||
const tileColorData = ref<Map<string, { r: number; g: number; b: number }>>(new Map())
|
||||
const tileEdgeData = ref<Map<string, number>>(new Map())
|
||||
|
||||
function areTilesRelated(tile1: Tile, tile2: Tile): boolean {
|
||||
const colorSimilarityThreshold = 30 // Adjust this value as needed
|
||||
const edgeComplexitySimilarityThreshold = 20 // Adjust this value as needed
|
||||
|
||||
const color1 = tileColorData.value.get(tile1.id)
|
||||
const color2 = tileColorData.value.get(tile2.id)
|
||||
const edge1 = tileEdgeData.value.get(tile1.id)
|
||||
const edge2 = tileEdgeData.value.get(tile2.id)
|
||||
|
||||
if (!color1 || !color2 || edge1 === undefined || edge2 === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
const colorDifference = Math.sqrt(Math.pow(color1.r - color2.r, 2) + Math.pow(color1.g - color2.g, 2) + Math.pow(color1.b - color2.b, 2))
|
||||
|
||||
const edgeComplexityDifference = Math.abs(edge1 - edge2)
|
||||
|
||||
const namePrefix1 = tile1.name.split('_')[0]
|
||||
const namePrefix2 = tile2.name.split('_')[0]
|
||||
|
||||
return colorDifference <= colorSimilarityThreshold && edgeComplexityDifference <= edgeComplexitySimilarityThreshold && namePrefix1 === namePrefix2
|
||||
}
|
||||
|
||||
const toggleTag = (tag: string) => {
|
||||
if (selectedTags.value.includes(tag)) {
|
||||
selectedTags.value = selectedTags.value.filter((t) => t !== tag)
|
||||
@ -77,10 +155,82 @@ const toggleTag = (tag: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
function processTile(tile: Tile) {
|
||||
const img = new Image()
|
||||
img.crossOrigin = 'Anonymous'
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
ctx!.drawImage(img, 0, 0, img.width, img.height)
|
||||
|
||||
const imageData = ctx!.getImageData(0, 0, canvas.width, canvas.height)
|
||||
tileColorData.value.set(tile.id, getDominantColor(imageData))
|
||||
tileEdgeData.value.set(tile.id, getEdgeComplexity(imageData))
|
||||
}
|
||||
img.src = `${config.server_endpoint}/assets/tiles/${tile.id}.png`
|
||||
}
|
||||
|
||||
function getDominantColor(imageData: ImageData) {
|
||||
let r = 0,
|
||||
g = 0,
|
||||
b = 0,
|
||||
total = 0
|
||||
for (let i = 0; i < imageData.data.length; i += 4) {
|
||||
if (imageData.data[i + 3] > 0) {
|
||||
// Only consider non-transparent pixels
|
||||
r += imageData.data[i]
|
||||
g += imageData.data[i + 1]
|
||||
b += imageData.data[i + 2]
|
||||
total++
|
||||
}
|
||||
}
|
||||
return {
|
||||
r: Math.round(r / total),
|
||||
g: Math.round(g / total),
|
||||
b: Math.round(b / total)
|
||||
}
|
||||
}
|
||||
|
||||
function getEdgeComplexity(imageData: ImageData) {
|
||||
let edgePixels = 0
|
||||
for (let y = 0; y < imageData.height; y++) {
|
||||
for (let x = 0; x < imageData.width; x++) {
|
||||
const i = (y * imageData.width + x) * 4
|
||||
if (imageData.data[i + 3] > 0 && (x === 0 || y === 0 || x === imageData.width - 1 || y === imageData.height - 1 || imageData.data[i - 1] === 0 || imageData.data[i + 7] === 0)) {
|
||||
edgePixels++
|
||||
}
|
||||
}
|
||||
}
|
||||
return edgePixels
|
||||
}
|
||||
|
||||
function getTileCategory(tile: Tile): string {
|
||||
return tileCategories.value.get(tile.id) || ''
|
||||
}
|
||||
|
||||
function openGroup(group: { parent: Tile; children: Tile[] }) {
|
||||
selectedGroup.value = group
|
||||
}
|
||||
|
||||
function closeGroup() {
|
||||
selectedGroup.value = null
|
||||
}
|
||||
|
||||
function selectTile(tile: string) {
|
||||
zoneEditorStore.setSelectedTile(tile)
|
||||
}
|
||||
|
||||
function isActiveTile(tile: Tile): boolean {
|
||||
return zoneEditorStore.selectedTile === tile.id
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
isModalOpen.value = true
|
||||
gameStore.connection?.emit('gm:tile:list', {}, (response: Tile[]) => {
|
||||
zoneEditorStore.setTileList(response)
|
||||
response.forEach((tile) => processTile(tile))
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
@ -1,34 +1,34 @@
|
||||
<template>
|
||||
<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 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')">
|
||||
<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="h-5" :class="{ 'ml-2.5': zoneEditorStore.tool !== 'move' }">(M)</span>
|
||||
</button>
|
||||
|
||||
<div class="w-px bg-cyan"></div>
|
||||
|
||||
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': zoneEditorStore.tool === 'pencil' }" @click="handleClick('pencil')">
|
||||
<img class="invert w-5 h-5" src="/assets/icons/zoneEditor/pencil.svg" alt="Pencil" /> <span :class="{ 'ml-2.5': zoneEditorStore.tool !== 'pencil' }">(P)</span>
|
||||
<img class="invert w-5 h-5" src="/assets/icons/zoneEditor/pencil.svg" alt="Pencil" /> <span class="h-5" :class="{ 'ml-2.5': zoneEditorStore.tool !== 'pencil' }">(P)</span>
|
||||
<div class="select" v-if="zoneEditorStore.tool === 'pencil'">
|
||||
<div class="select-trigger group capitalize flex gap-3.5" :class="{ open: selectPencilOpen }">
|
||||
{{ zoneEditorStore.drawMode }}
|
||||
<img class="group-[.open]:rotate-180 invert w-5 h-5 rotate-0 transition ease-in-out duration-200" src="/assets/icons/zoneEditor/chevron.svg" />
|
||||
</div>
|
||||
<div class="flex flex-col absolute bottom-full mb-5 left-1/2 -translate-x-1/2 bg-gray rounded min-w-28 border border-gray-500 border-solid text-left" v-show="selectPencilOpen && zoneEditorStore.tool === 'pencil'">
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan/50" @click="setDrawMode('tile')">
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setDrawMode('tile')">
|
||||
Tile
|
||||
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
|
||||
</span>
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan/50" @click="setDrawMode('object')">
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setDrawMode('object')">
|
||||
Object
|
||||
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
|
||||
</span>
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan/50" @click="setDrawMode('teleport')">
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setDrawMode('teleport')">
|
||||
Teleport
|
||||
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
|
||||
</span>
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan/50" @click="setDrawMode('blocking tile')">Blocking tile</span>
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setDrawMode('blocking tile')">Blocking tile</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
@ -36,26 +36,26 @@
|
||||
<div class="w-px bg-cyan"></div>
|
||||
|
||||
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': zoneEditorStore.tool === 'eraser' }" @click="handleClick('eraser')">
|
||||
<img class="invert w-5 h-5" src="/assets/icons/zoneEditor/eraser.svg" alt="Eraser" /> <span :class="{ 'ml-2.5': zoneEditorStore.tool !== 'eraser' }">(E)</span>
|
||||
<img class="invert w-5 h-5" src="/assets/icons/zoneEditor/eraser.svg" alt="Eraser" /> <span class="h-5" :class="{ 'ml-2.5': zoneEditorStore.tool !== 'eraser' }">(E)</span>
|
||||
<div class="select" v-if="zoneEditorStore.tool === 'eraser'">
|
||||
<div class="select-trigger group capitalize flex gap-3.5" :class="{ open: selectEraserOpen }">
|
||||
{{ zoneEditorStore.eraserMode }}
|
||||
<img class="group-[.open]:rotate-180 invert w-5 h-5 rotate-0 transition ease-in-out duration-200" src="/assets/icons/zoneEditor/chevron.svg" />
|
||||
</div>
|
||||
<div class="flex flex-col absolute bottom-full mb-5 left-1/2 -translate-x-1/2 bg-gray rounded min-w-28 border border-gray-500 border-solid text-left" v-show="selectEraserOpen">
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan/50" @click="setEraserMode('tile')">
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setEraserMode('tile')">
|
||||
Tile
|
||||
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
|
||||
</span>
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan/50" @click="setEraserMode('object')">
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setEraserMode('object')">
|
||||
Object
|
||||
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
|
||||
</span>
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan/50" @click="setEraserMode('teleport')">
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setEraserMode('teleport')">
|
||||
Teleport
|
||||
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
|
||||
</span>
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan/50" @click="setEraserMode('blocking tile')">Blocking tile</span>
|
||||
<span class="py-2 px-2.5 relative hover:bg-cyan hover:text-white" @click="setEraserMode('blocking tile')">Blocking tile</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
@ -63,12 +63,12 @@
|
||||
<div class="w-px bg-cyan"></div>
|
||||
|
||||
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': zoneEditorStore.tool === 'paint' }" @click="handleClick('paint')">
|
||||
<img class="invert w-5 h-5" src="/assets/icons/zoneEditor/paint.svg" alt="Paint bucket" /> <span :class="{ 'ml-2.5': zoneEditorStore.tool !== 'paint' }">(B)</span>
|
||||
<img class="invert w-5 h-5" src="/assets/icons/zoneEditor/paint.svg" alt="Paint bucket" /> <span class="h-5" :class="{ 'ml-2.5': zoneEditorStore.tool !== 'paint' }">(B)</span>
|
||||
</button>
|
||||
|
||||
<div class="w-px bg-cyan"></div>
|
||||
|
||||
<button class="flex justify-center items-center min-w-10 p-0 relative" @click="handleClick('settings')" v-if="zoneEditorStore.zone"><img class="invert w-5 h-5" src="/assets/icons/zoneEditor/gear.svg" alt="Zone settings" /> <span class="ml-2.5">(Z)</span></button>
|
||||
<button class="flex justify-center items-center min-w-10 p-0 relative" @click="handleClick('settings')" v-if="zoneEditorStore.zone"><img class="invert w-5 h-5" src="/assets/icons/zoneEditor/gear.svg" alt="Zone settings" /> <span class="h-5 ml-2.5">(Z)</span></button>
|
||||
</div>
|
||||
|
||||
<div class="toolbar fixed bottom-0 right-0 m-3 rounded flex bg-gray solid border-solid border-2 border-gray-500 text-gray-300 p-1.5 px-3 h-10 space-x-2">
|
||||
@ -83,22 +83,15 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { useScene } from 'phavuer'
|
||||
import { getTile } from '@/composables/zoneComposable'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
|
||||
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
|
||||
const props = defineProps({
|
||||
layer: Phaser.Tilemaps.TilemapLayer
|
||||
})
|
||||
const scene = useScene()
|
||||
const emit = defineEmits(['move', 'eraser', 'pencil', 'paint', 'save', 'clear'])
|
||||
const emit = defineEmits(['save', 'clear'])
|
||||
|
||||
// track when clicked outside of toolbar items
|
||||
const clickOutsideElement = ref(null)
|
||||
const toolbar = ref(null)
|
||||
|
||||
// track select state
|
||||
let selectPencilOpen = ref(false)
|
||||
@ -119,47 +112,6 @@ function setEraserMode(value: string) {
|
||||
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) {
|
||||
if (tool === 'settings') {
|
||||
zoneEditorStore.toggleSettingsModal()
|
||||
@ -172,21 +124,23 @@ function handleClick(tool: string) {
|
||||
}
|
||||
|
||||
function cycleToolMode(tool: 'pencil' | 'eraser') {
|
||||
const modes = ['tile', 'object', 'teleport', 'blocking tile'];
|
||||
const currentMode = tool === 'pencil' ? zoneEditorStore.drawMode : zoneEditorStore.eraserMode;
|
||||
const currentIndex = modes.indexOf(currentMode);
|
||||
const nextIndex = (currentIndex + 1) % modes.length;
|
||||
const nextMode = modes[nextIndex];
|
||||
const modes = ['tile', 'object', 'teleport', 'blocking tile']
|
||||
const currentMode = tool === 'pencil' ? zoneEditorStore.drawMode : zoneEditorStore.eraserMode
|
||||
const currentIndex = modes.indexOf(currentMode)
|
||||
const nextIndex = (currentIndex + 1) % modes.length
|
||||
const nextMode = modes[nextIndex]
|
||||
|
||||
if (tool === 'pencil') {
|
||||
setDrawMode(nextMode);
|
||||
setDrawMode(nextMode)
|
||||
} else {
|
||||
setEraserMode(nextMode);
|
||||
setEraserMode(nextMode)
|
||||
}
|
||||
}
|
||||
|
||||
function initKeyShortcuts(event: KeyboardEvent) {
|
||||
// Check if zone is set
|
||||
if (!zoneEditorStore.zone) return
|
||||
|
||||
// prevent if focused on composables
|
||||
if (document.activeElement?.tagName === 'INPUT') return
|
||||
|
||||
@ -199,19 +153,26 @@ function initKeyShortcuts(event: KeyboardEvent) {
|
||||
}
|
||||
|
||||
if (keyActions.hasOwnProperty(event.key)) {
|
||||
const tool = keyActions[event.key];
|
||||
const tool = keyActions[event.key]
|
||||
if ((tool === 'pencil' || tool === 'eraser') && zoneEditorStore.tool === tool) {
|
||||
cycleToolMode(tool);
|
||||
cycleToolMode(tool)
|
||||
} else {
|
||||
handleClick(tool);
|
||||
handleClick(tool)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClickOutside(clickOutsideElement, handleClickOutside)
|
||||
|
||||
function handleClickOutside() {
|
||||
selectPencilOpen.value = false
|
||||
selectEraserOpen.value = false
|
||||
}
|
||||
onClickOutside(toolbar, handleClickOutside)
|
||||
|
||||
onMounted(() => {
|
||||
addEventListener('keydown', initKeyShortcuts)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
removeEventListener('keydown', initKeyShortcuts)
|
||||
})
|
||||
</script>
|
||||
|
@ -1,10 +1,8 @@
|
||||
<template>
|
||||
<CreateZone v-if="zoneEditorStore.isCreateZoneModalShown" />
|
||||
|
||||
<Teleport to="body">
|
||||
<Modal @modal:close="() => zoneEditorStore.toggleZoneListModal()" :is-resizable="false" :is-modal-open="true" :modal-width="300" :modal-height="360">
|
||||
<Modal :is-modal-open="zoneEditorStore.isZoneListModalShown" @modal:close="() => zoneEditorStore.toggleZoneListModal()" :is-resizable="false" :modal-width="300" :modal-height="360" :disable-bg-texture="true">
|
||||
<template #modalHeader>
|
||||
<h3 class="text-lg text-gray-300">Zones</h3>
|
||||
<h3 class="text-lg text-white">Zones</h3>
|
||||
</template>
|
||||
<template #modalBody>
|
||||
<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>
|
||||
</div>
|
||||
<div class="relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap" v-for="(zone, index) in zoneEditorStore.zoneList" :key="zone.id">
|
||||
<div class="absolute left-0 top-0 w-full h-px bg-cyan-200" v-if="index === 0"></div>
|
||||
<div class="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)">
|
||||
<span>{{ zone.name }}</span>
|
||||
<span class="ml-auto gap-1 flex">
|
||||
<button class="btn-red py-0.5 px-2.5 z-50" @click.stop="() => deleteZone(zone.id)">X</button>
|
||||
<button class="btn-red w-7 h-7 z-50 flex items-center justify-center" @click.stop="() => deleteZone(zone.id)">x</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -1,24 +1,28 @@
|
||||
<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" :disable-bg-texture="true">
|
||||
<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 #modalBody>
|
||||
<div class="m-4">
|
||||
<form method="post" @submit.prevent="" class="inline">
|
||||
<div class="gap-2.5 flex flex-wrap">
|
||||
<div class="space-x-2">
|
||||
<button class="btn-cyan py-1.5 px-4" type="button" @click.prevent="screen = 'settings'">Settings</button>
|
||||
<button class="btn-cyan py-1.5 px-4" type="button" @click.prevent="screen = 'effects'">Effects</button>
|
||||
</div>
|
||||
<form method="post" @submit.prevent="" class="inline" v-if="screen === 'settings'">
|
||||
<div class="gap-2.5 flex flex-wrap mt-4">
|
||||
<div class="form-field-full">
|
||||
<label for="name">Name</label>
|
||||
<input class="input-field" v-model="name" name="name" id="name" />
|
||||
</div>
|
||||
<div class="form-field-half">
|
||||
<label for="name">Width</label>
|
||||
<input class="input-field" v-model="width" name="name" id="name" type="number" />
|
||||
<label for="width">Width</label>
|
||||
<input class="input-field" v-model="width" name="width" id="width" type="number" />
|
||||
</div>
|
||||
<div class="form-field-half">
|
||||
<label for="name">Height</label>
|
||||
<input class="input-field" v-model="height" name="name" id="name" type="number" />
|
||||
<label for="height">Height</label>
|
||||
<input class="input-field" v-model="height" name="height" id="height" type="number" />
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="pvp">PVP enabled</label>
|
||||
@ -29,6 +33,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<form method="post" @submit.prevent="" class="inline" v-if="screen === 'effects'">
|
||||
<div v-for="(effect, index) in zoneEffects" :key="effect.id" class="mb-2 flex items-center space-x-2 mt-4">
|
||||
<input class="input-field flex-grow" v-model="effect.effect" placeholder="Effect name" />
|
||||
<input class="input-field w-20" v-model.number="effect.strength" type="number" placeholder="Strength" />
|
||||
<button class="btn-red py-1 px-2" type="button" @click="removeEffect(index)">Delete</button>
|
||||
</div>
|
||||
<button class="btn-green py-1 px-2 mt-2" type="button" @click="addEffect">Add Effect</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
@ -40,16 +52,19 @@ import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
const screen = ref('settings')
|
||||
|
||||
zoneEditorStore.setZoneName(zoneEditorStore.zone.name)
|
||||
zoneEditorStore.setZoneWidth(zoneEditorStore.zone.width)
|
||||
zoneEditorStore.setZoneHeight(zoneEditorStore.zone.height)
|
||||
zoneEditorStore.setZonePvp(zoneEditorStore.zone.pvp)
|
||||
zoneEditorStore.setZoneName(zoneEditorStore.zone?.name)
|
||||
zoneEditorStore.setZoneWidth(zoneEditorStore.zone?.width)
|
||||
zoneEditorStore.setZoneHeight(zoneEditorStore.zone?.height)
|
||||
zoneEditorStore.setZonePvp(zoneEditorStore.zone?.pvp)
|
||||
zoneEditorStore.setZoneEffects(zoneEditorStore.zone?.zoneEffects)
|
||||
|
||||
const name = ref(zoneEditorStore.zoneSettings.name)
|
||||
const width = ref(zoneEditorStore.zoneSettings.width)
|
||||
const height = ref(zoneEditorStore.zoneSettings.height)
|
||||
const pvp = ref(zoneEditorStore.zoneSettings.pvp)
|
||||
const name = ref(zoneEditorStore.zoneSettings?.name)
|
||||
const width = ref(zoneEditorStore.zoneSettings?.width)
|
||||
const height = ref(zoneEditorStore.zoneSettings?.height)
|
||||
const pvp = ref(zoneEditorStore.zoneSettings?.pvp)
|
||||
const zoneEffects = ref(zoneEditorStore.zoneSettings?.zoneEffects || [])
|
||||
|
||||
watch(name, (value) => {
|
||||
zoneEditorStore.setZoneName(value)
|
||||
@ -66,4 +81,26 @@ watch(height, (value) => {
|
||||
watch(pvp, (value) => {
|
||||
zoneEditorStore.setZonePvp(value)
|
||||
})
|
||||
|
||||
watch(
|
||||
zoneEffects,
|
||||
(value) => {
|
||||
zoneEditorStore.setZoneEffects(value)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const addEffect = () => {
|
||||
zoneEffects.value.push({
|
||||
id: Date.now().toString(), // Simple unique id generation
|
||||
zoneId: zoneEditorStore.zone?.id,
|
||||
zone: zoneEditorStore.zone,
|
||||
effect: '',
|
||||
strength: 1
|
||||
})
|
||||
}
|
||||
|
||||
const removeEffect = (index) => {
|
||||
zoneEffects.value.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
|
@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<Image v-for="tile in zoneEditorStore.zone?.zoneEventTiles" v-bind="getImageProps(tile)" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type ZoneEventTile, ZoneEventTileType } from '@/types'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { Image, useScene } from 'phavuer'
|
||||
import { getTile, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
||||
import { uuidv4 } from '@/utilities'
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const scene = useScene()
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
|
||||
const props = defineProps<{
|
||||
tilemap: Phaser.Tilemaps.Tilemap
|
||||
}>()
|
||||
|
||||
function getImageProps(tile: ZoneEventTile) {
|
||||
return {
|
||||
x: tileToWorldX(props.tilemap, tile.positionX, tile.positionY),
|
||||
y: tileToWorldY(props.tilemap, tile.positionX, tile.positionY),
|
||||
texture: tile.type,
|
||||
depth: 999
|
||||
}
|
||||
}
|
||||
|
||||
function pencil(pointer: Phaser.Input.Pointer) {
|
||||
// Check if zone is set
|
||||
if (!zoneEditorStore.zone) return
|
||||
|
||||
// Check if tool is pencil
|
||||
if (zoneEditorStore.tool !== 'pencil') return
|
||||
|
||||
// Check if draw mode is blocking tile or teleport
|
||||
if (zoneEditorStore.drawMode !== 'blocking tile' && zoneEditorStore.drawMode !== 'teleport') return
|
||||
|
||||
// Check if left mouse button is pressed
|
||||
if (!pointer.isDown) return
|
||||
|
||||
// Check if shift is not pressed, this means we are moving the camera
|
||||
if (pointer.event.shiftKey) return
|
||||
|
||||
// Check if there is a tile
|
||||
const tile = getTile(props.tilemap, pointer.worldX, pointer.worldY)
|
||||
if (!tile) return
|
||||
|
||||
// Check if event tile already exists on position
|
||||
const existingEventTile = zoneEditorStore.zone.zoneEventTiles.find((eventTile) => eventTile.positionX === tile.x && eventTile.positionY === tile.y)
|
||||
if (existingEventTile) return
|
||||
|
||||
// If teleport, check if there is a selected zone
|
||||
if (zoneEditorStore.drawMode === 'teleport' && !zoneEditorStore.teleportSettings.toZoneId) return
|
||||
|
||||
const newEventTile = {
|
||||
id: uuidv4(),
|
||||
zoneId: zoneEditorStore.zone.id,
|
||||
zone: zoneEditorStore.zone,
|
||||
type: zoneEditorStore.drawMode === 'blocking tile' ? ZoneEventTileType.BLOCK : ZoneEventTileType.TELEPORT,
|
||||
positionX: tile.x,
|
||||
positionY: tile.y,
|
||||
teleport:
|
||||
zoneEditorStore.drawMode === 'teleport'
|
||||
? {
|
||||
toZoneId: zoneEditorStore.teleportSettings.toZoneId,
|
||||
toPositionX: zoneEditorStore.teleportSettings.toPositionX,
|
||||
toPositionY: zoneEditorStore.teleportSettings.toPositionY,
|
||||
toRotation: zoneEditorStore.teleportSettings.toRotation
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
||||
zoneEditorStore.zone.zoneEventTiles = zoneEditorStore.zone.zoneEventTiles.concat(newEventTile as ZoneEventTile)
|
||||
}
|
||||
|
||||
function eraser(pointer: Phaser.Input.Pointer) {
|
||||
// Check if zone is set
|
||||
if (!zoneEditorStore.zone) return
|
||||
|
||||
// Check if tool is pencil
|
||||
if (zoneEditorStore.tool !== 'eraser') return
|
||||
|
||||
// Check if draw mode is blocking tile or teleport
|
||||
if (zoneEditorStore.eraserMode !== 'blocking tile' && zoneEditorStore.eraserMode !== 'teleport') return
|
||||
|
||||
// Check if left mouse button is pressed
|
||||
if (!pointer.isDown) return
|
||||
|
||||
// Check if shift is not pressed, this means we are moving the camera
|
||||
if (pointer.event.shiftKey) return
|
||||
|
||||
// Check if there is a tile
|
||||
const tile = getTile(props.tilemap, pointer.worldX, pointer.worldY)
|
||||
if (!tile) return
|
||||
|
||||
// Check if event tile already exists on position
|
||||
const existingEventTile = zoneEditorStore.zone.zoneEventTiles.find((eventTile) => eventTile.positionX === tile.x && eventTile.positionY === tile.y)
|
||||
if (!existingEventTile) return
|
||||
|
||||
// Remove existing event tile
|
||||
zoneEditorStore.zone.zoneEventTiles = zoneEditorStore.zone.zoneEventTiles.filter((eventTile) => eventTile.id !== existingEventTile.id)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
scene.input.on(Phaser.Input.Events.POINTER_DOWN, pencil)
|
||||
scene.input.on(Phaser.Input.Events.POINTER_MOVE, pencil)
|
||||
scene.input.on(Phaser.Input.Events.POINTER_DOWN, eraser)
|
||||
scene.input.on(Phaser.Input.Events.POINTER_MOVE, eraser)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
scene.input.off(Phaser.Input.Events.POINTER_DOWN, pencil)
|
||||
scene.input.off(Phaser.Input.Events.POINTER_MOVE, pencil)
|
||||
scene.input.off(Phaser.Input.Events.POINTER_DOWN, eraser)
|
||||
scene.input.off(Phaser.Input.Events.POINTER_MOVE, eraser)
|
||||
})
|
||||
</script>
|