Compare commits
54 Commits
feature/15
...
feature/#1
Author | SHA1 | Date | |
---|---|---|---|
a6c22df528 | |||
7dd2d70eca | |||
5fc3547d9c | |||
b5c222cc05 | |||
8b1efca7b8 | |||
1bdd2bc75a | |||
24dff8d920 | |||
8b98fc5c4e | |||
934ae50d8e | |||
7504e3719e | |||
34bd103ec2 | |||
295ce98e33 | |||
3c7e96ea7f | |||
1cfdf1857e | |||
c86fd2e564 | |||
f2e439831a | |||
474de8b14a | |||
b264ab3e40 | |||
4293ec63b6 | |||
34393a31ac | |||
53daa758a8 | |||
86f2510f3a | |||
e610e866c7 | |||
15442764c2 | |||
8c3a488e7d | |||
f51cb839bf | |||
376f8653d6 | |||
9b474909b3 | |||
95d322a63c | |||
16a8435f7b | |||
334ceaa8f3 | |||
f6b6b4b8ea | |||
7a50385420 | |||
e5213cf5e6 | |||
cc07d132e8 | |||
61b2717fb5 | |||
99d66a7c42 | |||
eb4ae4a625 | |||
586600da7c | |||
0ffec44038 | |||
1b86b25bc2 | |||
cd9316a384 | |||
80bb38a6f7 | |||
0a37a09ed4 | |||
1f46b94441 | |||
df3b9db45d | |||
d214bd37ad | |||
c31dada1f9 | |||
9cf872b7e2 | |||
5b31729f64 | |||
fda5224806 | |||
fd599907e5 | |||
c2ae271306 | |||
494576b284 |
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
|
||||||
<title>New Quest - Play</title>
|
<title>Sylvan Quest - Play</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
1127
package-lock.json
generated
32
package.json
@ -15,15 +15,15 @@
|
|||||||
"format": "prettier --write src/"
|
"format": "prettier --write src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vueuse/core": "^10.11.0",
|
"@vueuse/core": "^10.5.0",
|
||||||
"@vueuse/integrations": "^10.11.0",
|
"@vueuse/integrations": "^10.5.0",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.7",
|
||||||
"phaser": "^3.80.1",
|
"phaser": "^3.85.2",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.6",
|
||||||
"socket.io-client": "^4.7.5",
|
"socket.io-client": "^4.8.0",
|
||||||
"universal-cookie": "^6.1.3",
|
"universal-cookie": "^6.1.3",
|
||||||
"vue": "^3.4.33",
|
"vue": "^3.5.10",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.22.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.10.3",
|
"@rushstack/eslint-patch": "^1.10.3",
|
||||||
@ -40,16 +40,16 @@
|
|||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-vue": "^9.27.0",
|
"eslint-plugin-vue": "^9.27.0",
|
||||||
"jsdom": "^24.1.1",
|
"jsdom": "^24.1.1",
|
||||||
"npm-run-all2": "^6.2.2",
|
"npm-run-all2": "^6.2.3",
|
||||||
"phaser3-rex-plugins": "^1.80.5",
|
"phaser3-rex-plugins": "^1.80.8",
|
||||||
"phavuer": "^0.16.1",
|
"phavuer": "^0.16.1",
|
||||||
"postcss": "^8.4.39",
|
"postcss": "^8.4.47",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.79.4",
|
||||||
"tailwindcss": "^3.4.6",
|
"tailwindcss": "^3.4.13",
|
||||||
"typescript": "~5.5.3",
|
"typescript": "~5.6.2",
|
||||||
"vite": "^5.3.4",
|
"vite": "^5.4.8",
|
||||||
"vite-plugin-vue-devtools": "^7.3.6",
|
"vite-plugin-vue-devtools": "^7.4.6",
|
||||||
"vitest": "^2.0.3",
|
"vitest": "^2.0.3",
|
||||||
"vue-tsc": "^1.6.5"
|
"vue-tsc": "^1.6.5"
|
||||||
}
|
}
|
||||||
|
BIN
public/assets/fog.png
Normal file
After Width: | Height: | Size: 432 KiB |
BIN
public/assets/fog.webp
Normal file
After Width: | Height: | Size: 6.1 KiB |
20
public/assets/icons/chat-icon.svg
Normal file
After Width: | Height: | Size: 597 KiB |
Before Width: | Height: | Size: 1.4 KiB |
@ -1 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" id="screenshot-7904a66d-0c2c-80b0-8004-7aa8e4dbc6e0" viewBox="5853.755 6642.086 23.788 23.788" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-7904a66d-0c2c-80b0-8004-7aa8e4dbc6e0"><g class="fills" id="fills-7904a66d-0c2c-80b0-8004-7aa8e4dbc6e0"><path d="M5867.837,6654.000L5877.045,6644.792C5877.199,6644.649,5877.322,6644.476,5877.408,6644.284C5877.493,6644.093,5877.539,6643.886,5877.543,6643.676C5877.546,6643.466,5877.508,6643.258,5877.429,6643.063C5877.350,6642.869,5877.234,6642.692,5877.085,6642.544C5876.937,6642.395,5876.760,6642.278,5876.565,6642.200C5876.371,6642.121,5876.163,6642.082,5875.953,6642.086C5875.743,6642.090,5875.536,6642.136,5875.344,6642.221C5875.153,6642.307,5874.980,6642.430,5874.837,6642.583L5865.629,6651.792L5856.420,6642.583C5856.124,6642.307,5855.732,6642.157,5855.328,6642.164C5854.923,6642.171,5854.537,6642.335,5854.250,6642.622C5853.964,6642.908,5853.800,6643.294,5853.793,6643.699C5853.786,6644.104,5853.936,6644.495,5854.212,6644.792L5863.420,6654.000L5854.212,6663.208C5853.919,6663.501,5853.755,6663.898,5853.755,6664.312C5853.755,6664.726,5853.919,6665.124,5854.212,6665.417C5854.505,6665.709,5854.902,6665.874,5855.316,6665.874C5855.730,6665.874,5856.127,6665.709,5856.420,6665.417L5865.629,6656.208L5874.837,6665.417C5875.130,6665.709,5875.527,6665.874,5875.941,6665.874C5876.355,6665.874,5876.753,6665.709,5877.045,6665.417C5877.338,6665.124,5877.502,6664.726,5877.502,6664.312C5877.502,6663.898,5877.338,6663.501,5877.045,6663.208L5867.837,6654.000ZZ" style="fill: rgb(255, 255, 255);"/></g></g></svg>
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.91481 9.08504C7.71955 9.2803 7.40297 9.2803 7.20771 9.08504L5.55709 7.43442C5.36183 7.23916 5.04524 7.23916 4.84998 7.43442L2.99578 9.28862C2.80052 9.48389 2.48393 9.48389 2.28867 9.28863L1.00416 8.00412C0.808899 7.80885 0.808899 7.49227 1.00416 7.29701L2.85837 5.4428C3.05363 5.24754 3.05363 4.93096 2.85837 4.7357L0.914865 2.7922C0.719603 2.59693 0.719603 2.28035 0.914865 2.08509L2.07053 0.929423C2.26579 0.734161 2.58238 0.734162 2.77764 0.929424L4.72114 2.87293C4.9164 3.06819 5.23298 3.06819 5.42825 2.87293L7.297 1.00417C7.49226 0.808906 7.80885 0.808906 8.00411 1.00417L9.28862 2.28868C9.48388 2.48394 9.48388 2.80052 9.28862 2.99578L7.41986 4.86454C7.2246 5.0598 7.2246 5.37639 7.41986 5.57165L9.07048 7.22227C9.26574 7.41753 9.26574 7.73411 9.07048 7.92937L7.91481 9.08504Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 915 B |
7
public/assets/icons/eye-closed.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg width="20" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g>
|
||||||
|
<path fill="none" d="M0 0h24v24H0z"/>
|
||||||
|
<path d="M17.882 19.297A10.949 10.949 0 0 1 12 21c-5.392 0-9.878-3.88-10.819-9a10.982 10.982 0 0 1 3.34-6.066L1.392 2.808l1.415-1.415 19.799 19.8-1.415 1.414-3.31-3.31zM5.935 7.35A8.965 8.965 0 0 0 3.223 12a9.005 9.005 0 0 0 13.201 5.838l-2.028-2.028A4.5 4.5 0 0 1 8.19 9.604L5.935 7.35zm6.979 6.978l-3.242-3.242a2.5 2.5 0 0 0 3.241 3.241zm7.893 2.264l-1.431-1.43A8.935 8.935 0 0 0 20.777 12 9.005 9.005 0 0 0 9.552 5.338L7.974 3.76C9.221 3.27 10.58 3 12 3c5.392 0 9.878 3.88 10.819 9a10.947 10.947 0 0 1-2.012 4.592zm-9.084-9.084a4.5 4.5 0 0 1 4.769 4.769l-4.77-4.769z" fill="#808080"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 788 B |
3
public/assets/icons/eye.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.00004 0C11.5948 0 14.5854 2.58651 15.2124 6C14.5854 9.41347 11.5948 12 8.00004 12C4.40525 12 1.4146 9.41347 0.787598 6C1.4146 2.58651 4.40525 0 8.00004 0ZM8.00004 10.6667C10.8238 10.6667 13.24 8.70133 13.8516 6C13.24 3.29869 10.8238 1.33333 8.00004 1.33333C5.17624 1.33333 2.75998 3.29869 2.14836 6C2.75998 8.70133 5.17624 10.6667 8.00004 10.6667ZM8.00004 9C6.34316 9 5.00001 7.65687 5.00001 6C5.00001 4.34315 6.34316 3 8.00004 3C9.65684 3 11 4.34315 11 6C11 7.65687 9.65684 9 8.00004 9ZM8.00004 7.66667C8.92051 7.66667 9.66671 6.92047 9.66671 6C9.66671 5.07953 8.92051 4.33333 8.00004 4.33333C7.07957 4.33333 6.33334 5.07953 6.33334 6C6.33334 6.92047 7.07957 7.66667 8.00004 7.66667Z" fill="#808080"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 818 B |
BIN
public/assets/icons/f1-icon.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
public/assets/icons/f2-icon.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
public/assets/icons/f3-icon.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
public/assets/icons/f4-icon.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
public/assets/icons/f5-icon.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
public/assets/icons/f6-icon.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
public/assets/icons/f7-icon.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
public/assets/icons/f8-icon.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
25
public/assets/icons/map-icon.svg
Normal file
After Width: | Height: | Size: 600 KiB |
10
public/assets/icons/menu-icon.svg
Normal file
After Width: | Height: | Size: 600 KiB |
10
public/assets/icons/minus-icon.svg
Normal file
After Width: | Height: | Size: 597 KiB |
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 597 KiB |
25
public/assets/icons/socials-icon.svg
Normal file
After Width: | Height: | Size: 599 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.3 KiB |
BIN
public/assets/login/login-bg.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
public/assets/login/nq-logo-v1.png
Normal file
After Width: | Height: | Size: 20 KiB |
15
public/assets/login/sq-logo-v1.svg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/raindrop.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
public/assets/raindrop.webp
Normal file
After Width: | Height: | Size: 274 B |
@ -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 +1 @@
|
|||||||
<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(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>
|
<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 After Width: | Height: | Size: 1.6 KiB |
@ -1 +1 @@
|
|||||||
<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(54, 143, 139);"/></g></g></g></svg>
|
<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 After Width: | Height: | Size: 1.7 KiB |
22
public/assets/ui-border-4-corners-light.svg
Normal file
After Width: | Height: | Size: 598 KiB |
26
public/assets/ui-border-4-corners.svg
Normal file
After Width: | Height: | Size: 471 KiB |
23
public/assets/ui-box-inner.svg
Normal file
After Width: | Height: | Size: 301 KiB |
23
public/assets/ui-box-outer.svg
Normal file
After Width: | Height: | Size: 400 KiB |
23
public/assets/ui-rect-border-4-corners.svg
Normal file
After Width: | Height: | Size: 470 KiB |
BIN
public/assets/ui-texture.png
Normal file
After Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 372 B After Width: | Height: | Size: 250 B |
Before Width: | Height: | Size: 135 B After Width: | Height: | Size: 109 B |
Before Width: | Height: | Size: 920 B After Width: | Height: | Size: 696 B |
Before Width: | Height: | Size: 834 B After Width: | Height: | Size: 708 B |
@ -30,4 +30,7 @@ const screen = computed(() => {
|
|||||||
}
|
}
|
||||||
return 'login' // default fallback
|
return 'login' // default fallback
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Disable right click
|
||||||
|
addEventListener('contextmenu', (event) => event.preventDefault())
|
||||||
</script>
|
</script>
|
||||||
|
BIN
src/assets/fonts/upheavtt.ttf
Normal file
@ -3,7 +3,11 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
// Fonts
|
// Fonts
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Upheaval';
|
||||||
|
src: url('../fonts/upheavtt.ttf');
|
||||||
|
}
|
||||||
|
|
||||||
//Globals
|
//Globals
|
||||||
|
|
||||||
@ -26,14 +30,14 @@ h5,
|
|||||||
h6,
|
h6,
|
||||||
button,
|
button,
|
||||||
a {
|
a {
|
||||||
@apply font-titles text-white font-medium m-0;
|
@apply font-default text-gray-200 font-medium m-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
p,
|
p,
|
||||||
span,
|
span,
|
||||||
li,
|
li,
|
||||||
label {
|
label {
|
||||||
@apply font-default text-white;
|
@apply font-default text-gray-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
@ -56,12 +60,8 @@ input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-cyan {
|
.input-field {
|
||||||
@apply py-2 px-2.5 font-titles border border-solid border-cyan bg-white/70 rounded;
|
@apply px-4 py-2.5 text-base focus-visible:outline-none bg-gray border border-solid border-gray-500 rounded text-gray-300;
|
||||||
&:focus,
|
|
||||||
&:focus-visible {
|
|
||||||
@apply outline-2 outline-cyan;
|
|
||||||
}
|
|
||||||
&.inactive {
|
&.inactive {
|
||||||
@apply bg-gray-600/50 hover:cursor-not-allowed;
|
@apply bg-gray-600/50 hover:cursor-not-allowed;
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
@ -87,26 +87,34 @@ button {
|
|||||||
@apply text-center;
|
@apply text-center;
|
||||||
|
|
||||||
&.btn-cyan {
|
&.btn-cyan {
|
||||||
@apply bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20;
|
@apply bg-cyan text-gray-50 text-base rounded py-2.5;
|
||||||
|
|
||||||
&.active,
|
&.active,
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply bg-cyan;
|
@apply bg-cyan-800;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-bordeaux {
|
&.btn-red {
|
||||||
@apply bg-bordeaux/50 border border-solid border-white/25 rounded drop-shadow-20;
|
@apply bg-red text-gray-50 text-base rounded py-2.5;
|
||||||
|
|
||||||
&.active,
|
&.active,
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply bg-bordeaux;
|
@apply bg-red-300;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply cursor-pointer;
|
@apply cursor-pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.eye-open {
|
||||||
|
@apply bg-[url('/assets/icons/eye-closed.svg')] w-5 h-4 right-2.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-pixel {
|
||||||
|
@apply text-white font-ui drop-shadow-pixel-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
109
src/components/Effects.vue
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<template>
|
||||||
|
<Scene name="effects" @preload="preloadScene" @create="createScene" @update="updateScene">
|
||||||
|
</Scene>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Scene } from 'phavuer'
|
||||||
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||||
|
import { onBeforeMount, onBeforeUnmount, ref } from 'vue'
|
||||||
|
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
|
|
||||||
|
const sceneRef = ref<Phaser.Scene | null>(null)
|
||||||
|
|
||||||
|
// Effect-related refs
|
||||||
|
const dayNightCycle = 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
|
||||||
|
|
||||||
|
const preloadScene = async (scene: Phaser.Scene) => {
|
||||||
|
scene.load.image('raindrop', 'assets/raindrop.png')
|
||||||
|
scene.load.image('fog', 'assets/fog.png')
|
||||||
|
}
|
||||||
|
|
||||||
|
const createScene = async (scene: Phaser.Scene) => {
|
||||||
|
sceneRef.value = scene
|
||||||
|
createDayNightCycle(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 createRainEffect = (scene: Phaser.Scene) => {
|
||||||
|
rainEmitter.value = scene.add.particles(0, 0, 'raindrop', {
|
||||||
|
x: { min: 0, max: window.innerWidth },
|
||||||
|
y: -50,
|
||||||
|
quantity: 5,
|
||||||
|
lifespan: 2000,
|
||||||
|
speedY: { min: 300, max: 500 },
|
||||||
|
scale: { start: 0.005, end: 0.005 },
|
||||||
|
alpha: { start: 0.5, end: 0 },
|
||||||
|
blendMode: 'ADD'
|
||||||
|
})
|
||||||
|
rainEmitter.value.setDepth(900)
|
||||||
|
toggleRain(true) // Start with rain off
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleRain = (isRaining: boolean) => {
|
||||||
|
if (rainEmitter.value) {
|
||||||
|
rainEmitter.value.setVisible(isRaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
fogSprite.value.setDepth(950)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateFogEffect = () => {
|
||||||
|
if (fogSprite.value) {
|
||||||
|
// Example: Oscillate fog opacity
|
||||||
|
const fogOpacity = (Math.sin(Date.now() / 5000) + 1) / 2 * 0.3
|
||||||
|
fogSprite.value.setAlpha(fogOpacity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose methods to control effects
|
||||||
|
const controlEffects = {
|
||||||
|
toggleRain,
|
||||||
|
setFogDensity: (density: number) => {
|
||||||
|
if (fogSprite.value) {
|
||||||
|
fogSprite.value.setAlpha(density)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make control methods available to parent components
|
||||||
|
defineExpose(controlEffects)
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (sceneRef.value) sceneRef.value.scene.remove('effects')
|
||||||
|
})
|
||||||
|
</script>
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-wrap items-center input-cyan gap-1">
|
<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">
|
<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">{{ 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>
|
<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>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal :isModalOpen="gameStore.isGmPanelOpen" @modal:close="() => gameStore.toggleGmPanel()" :modal-width="1000" :modal-height="650" :can-full-screen="true">
|
<Modal :isModalOpen="gameStore.uiSettings.isGmPanelOpen" @modal:close="() => gameStore.toggleGmPanel()" :modal-width="1000" :modal-height="650" :can-full-screen="true">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium shrink-0">GM Panel</h3>
|
<h3 class="m-0 font-medium shrink-0 text-gray-300">GM Panel</h3>
|
||||||
<div class="flex gap-1.5 flex-wrap">
|
<div class="flex gap-1.5 flex-wrap">
|
||||||
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">General</button>
|
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">General</button>
|
||||||
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">Users</button>
|
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">Users</button>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal :isModalOpen="true" :closable="false" :is-resizable="false" :modal-width="modalWidth" :modal-height="modalHeight" :modal-position-x="posXY.x" :modal-position-y="posXY.y">
|
<Modal :isModalOpen="true" :closable="false" :is-resizable="false" :modal-width="modalWidth" :modal-height="modalHeight" :modal-position-x="posXY.x" :modal-position-y="posXY.y">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium shrink-0">GM tools</h3>
|
<h3 class="m-0 font-medium shrink-0 text-gray-300">GM tools</h3>
|
||||||
</template>
|
</template>
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
<div class="content flex flex-col gap-2.5 m-4 h-20">
|
<div class="content flex flex-col gap-2.5 m-4 h-20">
|
||||||
@ -20,7 +20,7 @@ import { onMounted, ref } from 'vue'
|
|||||||
const zoneEditorStore = useZoneEditorStore()
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const modalWidth = ref(200)
|
const modalWidth = ref(200)
|
||||||
const modalHeight = ref(160)
|
const modalHeight = ref(180)
|
||||||
|
|
||||||
let posXY = ref({ x: 0, y: 0 })
|
let posXY = ref({ x: 0, y: 0 })
|
||||||
|
|
||||||
|
@ -3,22 +3,22 @@
|
|||||||
<div class="relative p-2.5 flex flex-col items-center justify-between h-72">
|
<div class="relative p-2.5 flex flex-col items-center justify-between h-72">
|
||||||
<div class="filler"></div>
|
<div class="filler"></div>
|
||||||
<img class="max-h-56" :src="`${config.server_endpoint}/assets/objects/${selectedObject?.id}.png`" :alt="'Object ' + selectedObject?.id" />
|
<img class="max-h-56" :src="`${config.server_endpoint}/assets/objects/${selectedObject?.id}.png`" :alt="'Object ' + selectedObject?.id" />
|
||||||
<button class="btn-bordeaux px-4 py-1.5 min-w-24" type="button" @click.prevent="removeObject">Remove</button>
|
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="removeObject">Remove</button>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-2.5 p-2.5 block">
|
<div class="m-2.5 p-2.5 block">
|
||||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveObject">
|
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveObject">
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
<input v-model="objectName" class="input-cyan" type="text" name="name" placeholder="Wall #1" />
|
<input v-model="objectName" class="input-field" type="text" name="name" placeholder="Wall #1" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half">
|
<div class="form-field-half">
|
||||||
<label for="origin-x">Origin X</label>
|
<label for="origin-x">Origin X</label>
|
||||||
<input v-model="objectOriginX" class="input-cyan" type="number" step="any" name="origin-x" placeholder="Origin X" />
|
<input v-model="objectOriginX" class="input-field" type="number" step="any" name="origin-x" placeholder="Origin X" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half">
|
<div class="form-field-half">
|
||||||
<label for="origin-y">Origin Y</label>
|
<label for="origin-y">Origin Y</label>
|
||||||
<input v-model="objectOriginY" class="input-cyan" type="number" step="any" name="origin-y" placeholder="Origin Y" />
|
<input v-model="objectOriginY" class="input-field" type="number" step="any" name="origin-y" placeholder="Origin Y" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="origin-x">Tags</label>
|
<label for="origin-x">Tags</label>
|
||||||
@ -26,22 +26,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="origin-x">Is animated</label>
|
<label for="origin-x">Is animated</label>
|
||||||
<select v-model="objectIsAnimated" class="input-cyan" name="is-animated">
|
<select v-model="objectIsAnimated" class="input-field" name="is-animated">
|
||||||
<option :value="false">No</option>
|
<option :value="false">No</option>
|
||||||
<option :value="true">Yes</option>
|
<option :value="true">Yes</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="frame-speed">Frame speed</label>
|
<label for="frame-speed">Frame speed</label>
|
||||||
<input v-model="objectFrameSpeed" class="input-cyan" type="number" step="any" name="frame-speed" placeholder="Frame speed" />
|
<input v-model="objectFrameSpeed" class="input-field" type="number" step="any" name="frame-speed" placeholder="Frame speed" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half">
|
<div class="form-field-half">
|
||||||
<label for="frame-width">Frame width</label>
|
<label for="frame-width">Frame width</label>
|
||||||
<input v-model="objectFrameWidth" class="input-cyan" type="number" step="any" name="frame-width" placeholder="Frame width" />
|
<input v-model="objectFrameWidth" class="input-field" type="number" step="any" name="frame-width" placeholder="Frame width" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half">
|
<div class="form-field-half">
|
||||||
<label for="frame-height">Frame height</label>
|
<label for="frame-height">Frame height</label>
|
||||||
<input v-model="objectFrameHeight" class="input-cyan" type="number" step="any" name="frame-height" placeholder="Frame height" />
|
<input v-model="objectFrameHeight" class="input-field" type="number" step="any" name="frame-height" placeholder="Frame height" />
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button>
|
<button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
||||||
<input v-model="searchQuery" class="input-cyan flex-grow" placeholder="Search..." @input="handleSearch" />
|
<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/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">
|
||||||
<input class="hidden" id="upload-asset" ref="objectUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" />
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
@ -21,7 +21,7 @@
|
|||||||
<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-cyan-200"></div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-lg bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
|
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
|
||||||
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<div class="w-full flex flex-col">
|
<div class="w-full flex flex-col">
|
||||||
<label class="mb-1.5 font-titles" for="name">Name</label>
|
<label class="mb-1.5 font-titles" for="name">Name</label>
|
||||||
<input v-model="spriteName" class="input-cyan" type="text" name="name" placeholder="New sprite" />
|
<input v-model="spriteName" class="input-field" type="text" name="name" placeholder="New sprite" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full flex gap-2 mt-2 pb-4 relative">
|
<div class="w-full flex gap-2 mt-2 pb-4 relative">
|
||||||
<button class="btn-cyan px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="saveSprite">Save</button>
|
<button class="btn-cyan px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="saveSprite">Save</button>
|
||||||
<button class="btn-bordeaux px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="deleteSprite">Delete</button>
|
<button class="btn-red px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="deleteSprite">Delete</button>
|
||||||
<div class="w-[calc(100%_+_32px)] absolute left-[-15px] bottom-0 h-px bg-cyan-200"></div>
|
<div class="w-[calc(100%_+_32px)] absolute left-[-15px] bottom-0 h-px bg-cyan-200"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -19,40 +19,40 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
{{ action.action }}
|
{{ action.action }}
|
||||||
<button class="btn-bordeaux px-4 py-1.5 min-w-24" type="button" @click.prevent="() => spriteActions.splice(spriteActions.indexOf(action), 1)">Delete</button>
|
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="() => spriteActions.splice(spriteActions.indexOf(action), 1)">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveSprite">
|
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveSprite">
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="action">Action</label>
|
<label for="action">Action</label>
|
||||||
<input v-model="action.action" class="input-cyan" type="text" name="action" placeholder="Action" />
|
<input v-model="action.action" class="input-field" type="text" name="action" placeholder="Action" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half">
|
<div class="form-field-half">
|
||||||
<label for="origin-x">Origin X</label>
|
<label for="origin-x">Origin X</label>
|
||||||
<input v-model.number="action.originX" class="input-cyan" type="number" step="any" name="origin-x" placeholder="Origin X" />
|
<input v-model.number="action.originX" class="input-field" type="number" step="any" name="origin-x" placeholder="Origin X" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half">
|
<div class="form-field-half">
|
||||||
<label for="origin-y">Origin Y</label>
|
<label for="origin-y">Origin Y</label>
|
||||||
<input v-model.number="action.originY" class="input-cyan" type="number" step="any" name="origin-y" placeholder="Origin Y" />
|
<input v-model.number="action.originY" class="input-field" type="number" step="any" name="origin-y" placeholder="Origin Y" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half">
|
<div class="form-field-half">
|
||||||
<label for="is-animated">Is animated</label>
|
<label for="is-animated">Is animated</label>
|
||||||
<select v-model="action.isAnimated" class="input-cyan" name="is-animated">
|
<select v-model="action.isAnimated" class="input-field" name="is-animated">
|
||||||
<option :value="false">No</option>
|
<option :value="false">No</option>
|
||||||
<option :value="true">Yes</option>
|
<option :value="true">Yes</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half" v-if="action.isAnimated">
|
<div class="form-field-half" v-if="action.isAnimated">
|
||||||
<label for="is-looping">Is looping</label>
|
<label for="is-looping">Is looping</label>
|
||||||
<select v-model="action.isLooping" class="input-cyan" name="is-looping">
|
<select v-model="action.isLooping" class="input-field" name="is-looping">
|
||||||
<option :value="false">No</option>
|
<option :value="false">No</option>
|
||||||
<option :value="true">Yes</option>
|
<option :value="true">Yes</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-full" v-if="action.isAnimated">
|
<div class="form-field-full" v-if="action.isAnimated">
|
||||||
<label for="frame-speed">Frame speed</label>
|
<label for="frame-speed">Frame speed</label>
|
||||||
<input v-model.number="action.frameSpeed" class="input-cyan" type="number" step="any" name="frame-speed" placeholder="Frame speed" />
|
<input v-model.number="action.frameSpeed" class="input-field" type="number" step="any" name="frame-speed" placeholder="Frame speed" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<SpriteActionsInput v-model="action.sprites" />
|
<SpriteActionsInput v-model="action.sprites" />
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
||||||
<input v-model="searchQuery" class="input-cyan flex-grow" placeholder="Search..." @input="handleSearch" />
|
<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/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">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<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" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||||
@ -17,7 +17,7 @@
|
|||||||
<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-cyan-200"></div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-lg bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
|
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
|
||||||
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
<div class="relative p-2.5 flex flex-col items-center justify-between h-72">
|
<div class="relative p-2.5 flex flex-col items-center justify-between h-72">
|
||||||
<div class="filler"></div>
|
<div class="filler"></div>
|
||||||
<img class="max-h-72" :src="`${config.server_endpoint}/assets/tiles/${selectedTile?.id}.png`" :alt="'Tile ' + selectedTile?.id" />
|
<img class="max-h-72" :src="`${config.server_endpoint}/assets/tiles/${selectedTile?.id}.png`" :alt="'Tile ' + selectedTile?.id" />
|
||||||
<button class="btn-bordeaux px-4 py-1.5 min-w-24" type="button" @click.prevent="deleteTile">Delete</button>
|
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="deleteTile">Delete</button>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-2.5 p-2.5 block">
|
<div class="m-2.5 p-2.5 block">
|
||||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveTile">
|
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveTile">
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
<input v-model="tileName" class="input-cyan" type="text" name="name" placeholder="Tile #1" />
|
<input v-model="tileName" class="input-field" type="text" name="name" placeholder="Tile #1" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="origin-x">Tags</label>
|
<label for="origin-x">Tags</label>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
||||||
<input v-model="searchQuery" class="input-cyan flex-grow" placeholder="Search..." @input="handleSearch" />
|
<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/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">
|
||||||
<input class="hidden" id="upload-asset" ref="tileUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" />
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
@ -21,7 +21,7 @@
|
|||||||
<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-cyan-200"></div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-lg bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
|
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan/50 p-0 hover:bg-cyan" v-show="hasScrolled" @click="toTop">
|
||||||
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,14 +12,14 @@
|
|||||||
<TeleportModal v-if="shouldShowTeleportModal" />
|
<TeleportModal v-if="shouldShowTeleportModal" />
|
||||||
|
|
||||||
<Container :depth="2">
|
<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)" />
|
<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>
|
||||||
|
|
||||||
<Container :depth="3">
|
<Container :depth="3">
|
||||||
<Image v-for="tile in zoneEventTiles" :key="tile.id" v-bind="getEventTileImageProps(tile)" />
|
<Image v-for="tile in zoneEventTiles" :key="tile.id" v-bind="getEventTileImageProps(tile)" />
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<SelectedZoneObject v-if="zoneEditorStore.selectedZoneObject" @update_depth="updateZoneObjectDepth" @delete="deleteZoneObject" @move="handleMove" />
|
<SelectedZoneObject v-if="zoneEditorStore.selectedZoneObject" @update_depth="updateZoneObjectDepth" @delete="deleteZoneObject" @move="handleMove" @rotate="handleRotate" />
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -79,8 +79,8 @@ function createTilemap() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createTileLayer() {
|
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, 0, 0, index + 1, { x: 0, y: -config.tile_size.y }))
|
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, 0, 0, 0, { 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
|
const layer = zoneTilemap.createBlankLayer('tiles', tilesetImages as any, 0, config.tile_size.y) as Phaser.Tilemaps.TilemapLayer
|
||||||
|
|
||||||
@ -146,6 +146,7 @@ function addZoneObject(tile: Phaser.Tilemaps.Tile) {
|
|||||||
objectId: selectedObject.value!.id,
|
objectId: selectedObject.value!.id,
|
||||||
object: selectedObject.value!,
|
object: selectedObject.value!,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
|
isRotated: false,
|
||||||
positionX: tile.x,
|
positionX: tile.x,
|
||||||
positionY: tile.y
|
positionY: tile.y
|
||||||
}
|
}
|
||||||
@ -208,7 +209,7 @@ function save() {
|
|||||||
tiles: tileArray,
|
tiles: tileArray,
|
||||||
pvp: zone.value.pvp,
|
pvp: zone.value.pvp,
|
||||||
zoneEventTiles: zoneEventTiles.value.map(({ id, zoneId, type, positionX, positionY, teleport }) => ({ id, zoneId, type, positionX, positionY, teleport })),
|
zoneEventTiles: zoneEventTiles.value.map(({ id, zoneId, type, positionX, positionY, teleport }) => ({ id, zoneId, type, positionX, positionY, teleport })),
|
||||||
zoneObjects: zoneObjects.value.map(({ id, zoneId, objectId, depth, positionX, positionY }) => ({ id, zoneId, objectId, depth, positionX, positionY }))
|
zoneObjects: zoneObjects.value.map(({ id, zoneId, objectId, depth, isRotated, positionX, positionY }) => ({ id, zoneId, objectId, depth, isRotated, positionX, positionY }))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zoneEditorStore.isSettingsModalShown) {
|
if (zoneEditorStore.isSettingsModalShown) {
|
||||||
@ -248,6 +249,13 @@ function handleMove() {
|
|||||||
console.log('move btn clicked')
|
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 zoneEditorStore.objectList and update originX and originY of objects in zoneObjects
|
||||||
watch(
|
watch(
|
||||||
objectList,
|
objectList,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal :isModalOpen="true" @modal:close="() => zoneEditorStore.toggleCreateZoneModal()" :modal-width="300" :modal-height="400" :is-resizable="false">
|
<Modal :isModalOpen="true" @modal:close="() => zoneEditorStore.toggleCreateZoneModal()" :modal-width="300" :modal-height="400" :is-resizable="false">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium shrink-0">Create new zone</h3>
|
<h3 class="m-0 font-medium shrink-0 text-gray-300">Create new zone</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
@ -10,19 +10,19 @@
|
|||||||
<div class="gap-2.5 flex flex-wrap">
|
<div class="gap-2.5 flex flex-wrap">
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
<input class="input-cyan max-w-64" v-model="name" name="name" id="name" />
|
<input class="input-field max-w-64" v-model="name" name="name" id="name" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half">
|
<div class="form-field-half">
|
||||||
<label for="name">Width</label>
|
<label for="name">Width</label>
|
||||||
<input class="input-cyan max-w-64" v-model="width" name="name" id="name" type="number" />
|
<input class="input-field max-w-64" v-model="width" name="name" id="name" type="number" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half">
|
<div class="form-field-half">
|
||||||
<label for="name">Height</label>
|
<label for="name">Height</label>
|
||||||
<input class="input-cyan max-w-64" v-model="height" name="name" id="name" type="number" />
|
<input class="input-field max-w-64" v-model="height" name="name" id="name" type="number" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="name">PVP enabled</label>
|
<label for="name">PVP enabled</label>
|
||||||
<select class="input-cyan" name="pvp" id="pvp">
|
<select class="input-field" name="pvp" id="pvp">
|
||||||
<option :value="false">No</option>
|
<option :value="false">No</option>
|
||||||
<option :value="true">Yes</option>
|
<option :value="true">Yes</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -2,16 +2,16 @@
|
|||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<Modal :isModalOpen="zoneEditorStore.isObjectListModalShown" :modal-width="645" :modal-height="260" @modal:close="() => (zoneEditorStore.isObjectListModalShown = false)">
|
<Modal :isModalOpen="zoneEditorStore.isObjectListModalShown" :modal-width="645" :modal-height="260" @modal:close="() => (zoneEditorStore.isObjectListModalShown = false)">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="text-lg">Objects</h3>
|
<h3 class="text-lg text-gray-300">Objects</h3>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="w-full flex gap-1.5 flex-row">
|
<div class="w-full flex gap-1.5 flex-row">
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1.5 font-titles hidden" for="search">Search...</label>
|
<label class="mb-1.5 font-titles hidden" for="search">Search...</label>
|
||||||
<input @mousedown.stop class="input-cyan" type="text" name="search" placeholder="Search" v-model="searchQuery" />
|
<input @mousedown.stop class="input-field" type="text" name="search" placeholder="Search" v-model="searchQuery" />
|
||||||
</div>
|
</div>
|
||||||
<!-- <div>-->
|
<!-- <div>-->
|
||||||
<!-- <label class="mb-1.5 font-titles hidden" for="depth">Depth</label>-->
|
<!-- <label class="mb-1.5 font-titles hidden" for="depth">Depth</label>-->
|
||||||
<!-- <input v-model="objectDepth" @mousedown.stop class="input-cyan" type="number" name="depth" placeholder="Depth" />-->
|
<!-- <input v-model="objectDepth" @mousedown.stop class="input-field" type="number" name="depth" placeholder="Depth" />-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col items-center py-5 px-3 fixed bottom-14 right-0">
|
<div class="flex flex-col items-center py-5 px-3 fixed bottom-14 right-0" v-if="zoneEditorStore.selectedZoneObject">
|
||||||
<div class="self-end mt-2 flex gap-2">
|
<div class="self-end mt-2 flex gap-2">
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1.5 font-titles block text-sm text-gray-700 hidden" for="depth">Depth</label>
|
<label class="mb-1.5 font-titles block text-sm text-gray-700 hidden" for="depth">Depth</label>
|
||||||
<input v-model="objectDepth" @mousedown.stop @input="handleDepthInput" class="input-cyan max-w-24 px-2 py-1 border rounded" type="number" name="depth" placeholder="Depth" :disabled="!isObjectSelected" />
|
<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>
|
</div>
|
||||||
<button @mousedown.stop @click="handleDelete" class="btn-bordeaux py-1.5 px-4" :disabled="!isObjectSelected">
|
<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" />
|
<img src="/assets/icons/trashcan.svg" class="w-4 h-4" alt="Delete" />
|
||||||
</button>
|
</button>
|
||||||
<button @mousedown.stop @click="zoneEditorStore.setSelectedObject(zoneEditorStore.selectedZoneObject?.object)" class="btn-cyan py-1.5 px-4" :disabled="!isObjectSelected">S</button>
|
<button @mousedown.stop @click="handleRotate" class="btn-cyan py-1.5 px-4">Rotate</button>
|
||||||
<button @mousedown.stop @click="handleMove" class="btn-cyan py-1.5 px-4 min-w-24" :disabled="!isObjectSelected">Move</button>
|
<button @mousedown.stop @click="handleMove" class="btn-cyan py-1.5 px-4 min-w-24">Move</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -18,13 +18,11 @@
|
|||||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
|
|
||||||
const emit = defineEmits(['update_depth', 'move', 'delete'])
|
const emit = defineEmits(['update_depth', 'move', 'delete', 'rotate'])
|
||||||
const zoneEditorStore = useZoneEditorStore()
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
|
|
||||||
const objectDepth = ref(zoneEditorStore.objectDepth)
|
const objectDepth = ref(zoneEditorStore.objectDepth)
|
||||||
|
|
||||||
const isObjectSelected = computed(() => !!zoneEditorStore.selectedZoneObject)
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => zoneEditorStore.selectedZoneObject,
|
() => zoneEditorStore.selectedZoneObject,
|
||||||
(selectedZoneObject) => {
|
(selectedZoneObject) => {
|
||||||
@ -39,6 +37,10 @@ const handleDepthInput = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleRotate = () => {
|
||||||
|
emit('rotate', zoneEditorStore.selectedZoneObject?.id)
|
||||||
|
}
|
||||||
|
|
||||||
const handleMove = () => {
|
const handleMove = () => {
|
||||||
emit('move')
|
emit('move')
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal :is-modal-open="true" @modal:close="() => zoneEditorStore.setTool('move')" :modal-width="300" :modal-height="350" :is-resizable="false">
|
<Modal :is-modal-open="true" @modal:close="() => zoneEditorStore.setTool('move')" :modal-width="300" :modal-height="350" :is-resizable="false">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium shrink-0">Teleport settings</h3>
|
<h3 class="m-0 font-medium shrink-0 text-gray-300">Teleport settings</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
@ -10,15 +10,15 @@
|
|||||||
<div class="gap-2.5 flex flex-wrap">
|
<div class="gap-2.5 flex flex-wrap">
|
||||||
<div class="form-field-half">
|
<div class="form-field-half">
|
||||||
<label for="positionX">Position X</label>
|
<label for="positionX">Position X</label>
|
||||||
<input class="input-cyan" v-model="toPositionX" name="positionX" id="positionX" type="number" />
|
<input class="input-field" v-model="toPositionX" name="positionX" id="positionX" type="number" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half">
|
<div class="form-field-half">
|
||||||
<label for="positionY">Position Y</label>
|
<label for="positionY">Position Y</label>
|
||||||
<input class="input-cyan" v-model="toPositionY" name="positionY" id="positionY" type="number" />
|
<input class="input-field" v-model="toPositionY" name="positionY" id="positionY" type="number" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="rotation">Rotation</label>
|
<label for="rotation">Rotation</label>
|
||||||
<select v-model="toRotation" class="input-cyan" name="rotation" id="rotation">
|
<select v-model="toRotation" class="input-field" name="rotation" id="rotation">
|
||||||
<option :value="0">North</option>
|
<option :value="0">North</option>
|
||||||
<option :value="2">East</option>
|
<option :value="2">East</option>
|
||||||
<option :value="4">South</option>
|
<option :value="4">South</option>
|
||||||
@ -27,7 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="toZoneId">Zone to teleport to</label>
|
<label for="toZoneId">Zone to teleport to</label>
|
||||||
<select v-model="toZoneId" class="input-cyan" name="toZoneId" id="toZoneId">
|
<select v-model="toZoneId" class="input-field" name="toZoneId" id="toZoneId">
|
||||||
<option :value="0">Select zone</option>
|
<option :value="0">Select zone</option>
|
||||||
<option v-for="zone in zoneEditorStore.zoneList" :key="zone.id" :value="zone.id">{{ zone.name }}</option>
|
<option v-for="zone in zoneEditorStore.zoneList" :key="zone.id" :value="zone.id">{{ zone.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<Modal :isModalOpen="zoneEditorStore.isTileListModalShown" :modal-width="645" :modal-height="600" @modal:close="() => (zoneEditorStore.isTileListModalShown = false)">
|
<Modal :isModalOpen="zoneEditorStore.isTileListModalShown" :modal-width="645" :modal-height="600" @modal:close="() => (zoneEditorStore.isTileListModalShown = false)">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="text-lg">Tiles</h3>
|
<h3 class="text-lg text-gray-300">Tiles</h3>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="w-full flex gap-1.5 flex-row">
|
<div class="w-full flex gap-1.5 flex-row">
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1.5 font-titles hidden" for="search">Search...</label>
|
<label class="mb-1.5 font-titles hidden" for="search">Search...</label>
|
||||||
<input @mousedown.stop class="input-cyan" type="text" name="search" placeholder="Search" v-model="searchQuery" />
|
<input @mousedown.stop class="input-field" type="text" name="search" placeholder="Search" v-model="searchQuery" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex justify-center p-5">
|
<div class="flex justify-center p-5">
|
||||||
<div class="toolbar fixed bottom-0 m-3 rounded left-0 right-0 flex bg-gray-300/80 solid border-solid border-2 border-cyan ext-gray-50 p-1.5 px-3 p min-w-11/12 h-10">
|
<div class="toolbar fixed bottom-0 left-0 m-3 rounded flex bg-gray solid border-solid border-2 border-gray-500 text-gray-300 p-1.5 px-3 h-10">
|
||||||
<div ref="clickOutsideElement" class="tools flex gap-2.5" v-if="zoneEditorStore.zone">
|
<div ref="clickOutsideElement" 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-50 gap-2.5': zoneEditorStore.tool === 'move' }" @click="handleClick('move')">
|
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': zoneEditorStore.tool === 'move' }" @click="handleClick('move')">
|
||||||
<img class="invert w-5 h-5" src="/assets/icons/zoneEditor/move.svg" alt="Move camera" /> <span :class="{ 'ml-2.5': zoneEditorStore.tool !== 'move' }">(M)</span>
|
<img class="invert w-5 h-5" src="/assets/icons/zoneEditor/move.svg" alt="Move camera" /> <span :class="{ 'ml-2.5': zoneEditorStore.tool !== 'move' }">(M)</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="w-px bg-cyan"></div>
|
<div class="w-px bg-cyan"></div>
|
||||||
|
|
||||||
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan-50 gap-2.5': zoneEditorStore.tool === 'pencil' }" @click="handleClick('pencil')">
|
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': 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="{ 'ml-2.5': zoneEditorStore.tool !== 'pencil' }">(P)</span>
|
||||||
<div class="select" v-if="zoneEditorStore.tool === 'pencil'">
|
<div class="select" v-if="zoneEditorStore.tool === 'pencil'">
|
||||||
<div class="select-trigger group capitalize flex gap-3.5" :class="{ open: selectPencilOpen }">
|
<div class="select-trigger group capitalize flex gap-3.5" :class="{ open: selectPencilOpen }">
|
||||||
{{ zoneEditorStore.drawMode }}
|
{{ 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" />
|
<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>
|
||||||
<div class="flex flex-col absolute bottom-full mb-5 left-1/2 -translate-x-1/2 bg-gray-300/80 rounded min-w-28 border border-cyan border-solid text-left" v-show="selectPencilOpen && zoneEditorStore.tool === 'pencil'">
|
<div class="flex flex-col absolute bottom-full mb-5 left-1/2 -translate-x-1/2 bg-gray rounded min-w-28 border border-gray-500 border-solid text-left" v-show="selectPencilOpen && 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/50" @click="setDrawMode('tile')">
|
||||||
Tile
|
Tile
|
||||||
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
|
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
|
||||||
@ -35,14 +35,14 @@
|
|||||||
|
|
||||||
<div class="w-px bg-cyan"></div>
|
<div class="w-px bg-cyan"></div>
|
||||||
|
|
||||||
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan-50 gap-2.5': zoneEditorStore.tool === 'eraser' }" @click="handleClick('eraser')">
|
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': 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="{ 'ml-2.5': zoneEditorStore.tool !== 'eraser' }">(E)</span>
|
||||||
<div class="select" v-if="zoneEditorStore.tool === 'eraser'">
|
<div class="select" v-if="zoneEditorStore.tool === 'eraser'">
|
||||||
<div class="select-trigger group capitalize flex gap-3.5" :class="{ open: selectEraserOpen }">
|
<div class="select-trigger group capitalize flex gap-3.5" :class="{ open: selectEraserOpen }">
|
||||||
{{ zoneEditorStore.eraserMode }}
|
{{ 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" />
|
<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>
|
||||||
<div class="flex flex-col absolute bottom-full mb-5 left-1/2 -translate-x-1/2 bg-gray-300/80 rounded min-w-28 border border-cyan border-solid text-left" v-show="selectEraserOpen">
|
<div class="flex flex-col absolute bottom-full mb-5 left-1/2 -translate-x-1/2 bg-gray rounded min-w-28 border border-gray-500 border-solid text-left" v-show="selectEraserOpen">
|
||||||
<span class="py-2 px-2.5 relative hover:bg-cyan/50" @click="setEraserMode('tile')">
|
<span class="py-2 px-2.5 relative hover:bg-cyan/50" @click="setEraserMode('tile')">
|
||||||
Tile
|
Tile
|
||||||
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
|
<div class="absolute w-4/5 left-1/2 -translate-x-1/2 bottom-0 h-px bg-cyan"></div>
|
||||||
@ -62,7 +62,7 @@
|
|||||||
|
|
||||||
<div class="w-px bg-cyan"></div>
|
<div class="w-px bg-cyan"></div>
|
||||||
|
|
||||||
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan-50 gap-2.5': zoneEditorStore.tool === 'paint' }" @click="handleClick('paint')">
|
<button class="flex justify-center items-center min-w-10 p-0 relative" :class="{ 'border-0 border-b-[3px] border-solid border-cyan gap-2.5': 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="{ 'ml-2.5': zoneEditorStore.tool !== 'paint' }">(B)</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -71,7 +71,7 @@
|
|||||||
<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="ml-2.5">(Z)</span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-2.5 ml-auto">
|
<div class="toolbar fixed bottom-0 right-0 m-3 rounded flex bg-gray solid border-solid border-2 border-gray-500 text-gray-300 p-1.5 px-3 h-10 space-x-2">
|
||||||
<button class="btn-cyan px-3.5" @click="() => zoneEditorStore.toggleZoneListModal()">Load</button>
|
<button class="btn-cyan px-3.5" @click="() => zoneEditorStore.toggleZoneListModal()">Load</button>
|
||||||
<button class="btn-cyan px-3.5" @click="() => emit('save')" v-if="zoneEditorStore.zone">Save</button>
|
<button class="btn-cyan px-3.5" @click="() => emit('save')" v-if="zoneEditorStore.zone">Save</button>
|
||||||
<button class="btn-cyan px-3.5" @click="() => emit('clear')" v-if="zoneEditorStore.zone">Clear</button>
|
<button class="btn-cyan px-3.5" @click="() => emit('clear')" v-if="zoneEditorStore.zone">Clear</button>
|
||||||
@ -171,13 +171,26 @@ function handleClick(tool: string) {
|
|||||||
selectEraserOpen.value = tool === 'eraser' ? !selectEraserOpen.value : false
|
selectEraserOpen.value = tool === 'eraser' ? !selectEraserOpen.value : false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key bindings
|
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];
|
||||||
|
|
||||||
|
if (tool === 'pencil') {
|
||||||
|
setDrawMode(nextMode);
|
||||||
|
} else {
|
||||||
|
setEraserMode(nextMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initKeyShortcuts(event: KeyboardEvent) {
|
function initKeyShortcuts(event: KeyboardEvent) {
|
||||||
if (!zoneEditorStore.zone) return
|
if (!zoneEditorStore.zone) return
|
||||||
// prevent if focused on composables
|
// prevent if focused on composables
|
||||||
if (document.activeElement?.tagName === 'INPUT') return
|
if (document.activeElement?.tagName === 'INPUT') return
|
||||||
|
|
||||||
const keyActions: any = {
|
const keyActions: { [key: string]: string } = {
|
||||||
m: 'move',
|
m: 'move',
|
||||||
p: 'pencil',
|
p: 'pencil',
|
||||||
e: 'eraser',
|
e: 'eraser',
|
||||||
@ -186,7 +199,12 @@ function initKeyShortcuts(event: KeyboardEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (keyActions.hasOwnProperty(event.key)) {
|
if (keyActions.hasOwnProperty(event.key)) {
|
||||||
handleClick(keyActions[event.key])
|
const tool = keyActions[event.key];
|
||||||
|
if ((tool === 'pencil' || tool === 'eraser') && zoneEditorStore.tool === tool) {
|
||||||
|
cycleToolMode(tool);
|
||||||
|
} else {
|
||||||
|
handleClick(tool);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<Modal @modal:close="() => zoneEditorStore.toggleZoneListModal()" :is-resizable="false" :is-modal-open="true" :modal-width="300" :modal-height="360">
|
<Modal @modal:close="() => zoneEditorStore.toggleZoneListModal()" :is-resizable="false" :is-modal-open="true" :modal-width="300" :modal-height="360">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="text-lg">Zones</h3>
|
<h3 class="text-lg text-gray-300">Zones</h3>
|
||||||
</template>
|
</template>
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
<div class="my-4 mx-auto">
|
<div class="my-4 mx-auto">
|
||||||
@ -17,7 +17,7 @@
|
|||||||
<div class="flex gap-3 items-center w-full" @click="() => loadZone(zone.id)">
|
<div class="flex gap-3 items-center w-full" @click="() => loadZone(zone.id)">
|
||||||
<span>{{ zone.name }}</span>
|
<span>{{ zone.name }}</span>
|
||||||
<span class="ml-auto gap-1 flex">
|
<span class="ml-auto gap-1 flex">
|
||||||
<button class="btn-bordeaux py-0.5 px-2.5 z-50" @click.stop="() => deleteZone(zone.id)">X</button>
|
<button class="btn-red py-0.5 px-2.5 z-50" @click.stop="() => deleteZone(zone.id)">X</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
<div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal :is-modal-open="zoneEditorStore.isSettingsModalShown" @modal:close="() => zoneEditorStore.toggleSettingsModal()" :modal-width="300" :modal-height="350" :is-resizable="false">
|
<Modal :is-modal-open="zoneEditorStore.isSettingsModalShown" @modal:close="() => zoneEditorStore.toggleSettingsModal()" :modal-width="600" :modal-height="350">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium shrink-0">Zone settings</h3>
|
<h3 class="m-0 font-medium shrink-0 text-gray-300">Zone settings</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
@ -10,19 +10,19 @@
|
|||||||
<div class="gap-2.5 flex flex-wrap">
|
<div class="gap-2.5 flex flex-wrap">
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
<input class="input-cyan" v-model="name" name="name" id="name" />
|
<input class="input-field" v-model="name" name="name" id="name" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half">
|
<div class="form-field-half">
|
||||||
<label for="name">Width</label>
|
<label for="name">Width</label>
|
||||||
<input class="input-cyan" v-model="width" name="name" id="name" type="number" />
|
<input class="input-field" v-model="width" name="name" id="name" type="number" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half">
|
<div class="form-field-half">
|
||||||
<label for="name">Height</label>
|
<label for="name">Height</label>
|
||||||
<input class="input-cyan" v-model="height" name="name" id="name" type="number" />
|
<input class="input-field" v-model="height" name="name" id="name" type="number" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="pvp">PVP enabled</label>
|
<label for="pvp">PVP enabled</label>
|
||||||
<select v-model="pvp" class="input-cyan" name="pvp" id="pvp">
|
<select v-model="pvp" class="input-field" name="pvp" id="pvp">
|
||||||
<option :value="false">No</option>
|
<option :value="false">No</option>
|
||||||
<option :value="true">Yes</option>
|
<option :value="true">Yes</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full md:min-w-[350px] max-w-[750px] flex flex-col">
|
<div class="w-full md:min-w-[350px] max-w-[750px] flex flex-col absolute left-1/2 -translate-x-1/2 bottom-5">
|
||||||
<div ref="chatWindow" class="w-full overflow-auto h-32 mb-5 bg-gray-300/80 rounded-lg border-2 border-solid border-cyan-200" v-show="gameStore.isChatOpen">
|
<div ref="chatWindow" class="w-full overflow-auto h-32 mb-5 bg-gray rounded-md border-2 border-solid border-gray-500 text-gray-300" v-show="gameStore.uiSettings.isChatOpen">
|
||||||
<div v-for="message in chats" class="flex-col py-2 items-center p-3">
|
<div v-for="message in chats" class="flex-col py-2 items-center p-3">
|
||||||
<span class="text-ellipsis overflow-hidden whitespace-nowrap text-sm">{{ message.character.name }}</span>
|
<span class="text-ellipsis overflow-hidden whitespace-nowrap text-sm" :class="{'text-cyan-300': gameStore.character?.role == 'gm'}">{{ message.character.name }}</span>
|
||||||
<p class="text-gray-50 m-0">{{ message.message }}</p>
|
<p class="text-gray-50 m-0">{{ message.message }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex">
|
<div class="w-96 mx-auto relative">
|
||||||
|
<img src="/assets/icons/chat-icon.svg" class="absolute top-1/2 -translate-y-1/2 left-2.5 h-4 w-4 opacity-50" />
|
||||||
<input
|
<input
|
||||||
class="w-full h-12 rounded-lg text-lg px-4 py-0 bg-gray-300/80 border-2 border-solid border-cyan-200 text-gray-50 bg-[url('/assets/icons/submit-icon.svg')] bg-no-repeat bg-[right_25px_center] bg-[length:30px] focus:outline-none focus:ring-0 focus:border-cyan-800"
|
class="w-[332px] h-8 rounded-sm text-xs font-default pl-8 pr-4 py-0 bg-gray-600 border-2 border-solid border-gray-500 text-gray-300 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover focus:outline-none focus:ring-0 focus:border-cyan-800"
|
||||||
placeholder="Type something..."
|
placeholder="Type something..."
|
||||||
v-model="message"
|
v-model="message"
|
||||||
@keypress="handleKeyPress"
|
@keypress="handleKeyPress"
|
||||||
|
9
src/components/gui/ExpBar.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
</script>
|
@ -1,46 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="hud-wrapper relative left-0 w-[310px] h-[84px]">
|
<div class="absolute left-[66px] top-4 bg-[url('/assets/ui-rect-border-4-corners.svg')] bg-no-repeat px-4 py-2 w-[181px] h-[26px] flex flex-col justify-between">
|
||||||
<div class="absolute w-14 h-14 bg-white/80 rounded-full border-3 border-solid border-white top-1/2 -translate-y-1/2 left-0 z-20">
|
<div class="w-full flex items-center gap-2">
|
||||||
<img class="w-7 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" draggable="false" src="/assets/avatar/default/head.png" />
|
<label class="text-xs leading-3 text-pixel" for="hp">HP</label>
|
||||||
|
<progress class="h-2 rounded-sm w-full max-w-44 appearance-none accent-green" id="hp" :value="gameStore.character?.hitpoints" max="100">{{ gameStore.character?.hitpoints }}%</progress>
|
||||||
|
<span class="text-xs leading-3 text-pixel">{{ gameStore.character?.hitpoints }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="hud-bg absolute top-0 left-8 w-[280px] h-[84px] z-10 bg-[url('/assets/bg-hud-2.png')] bg-top bg-[length:cover] bg-no-repeat mask-[url('/assets/shapes/hud-image-shape.svg')] mask-center mask-[length:cover] mask-no-repeat"></div>
|
<div class="w-full flex items-center gap-2">
|
||||||
<div class="absolute top-0 left-8 w-[280px] h-[84px] z-10 bg-[url('/assets/shapes/hud-shape-empty.svg')] bg-center bg-[length:cover] bg-no-repeat">
|
<label class="text-xs leading-3 text-pixel" for="sp">SP</label>
|
||||||
<div class="h-16 flex flex-col items-end py-2.5 pl-12 pr-5">
|
<progress class="h-2 rounded-sm w-full max-w-44 appearance-none accent-blue" id="sp" :value="gameStore.character?.mana" max="100">{{ gameStore.character?.mana }}%</progress>
|
||||||
<div class="w-full flex items-center justify-between mb-1.5">
|
<span class="text-xs leading-3 text-pixel">{{ gameStore.character?.mana }}%</span>
|
||||||
<span class="text-ellipsis overflow-hidden whitespace-nowrap max-w-32 text-sm">{{ gameStore.character.name }}</span>
|
|
||||||
<span class="text-sm">lvl. {{ gameStore.character.level }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="w-full flex items-center justify-between">
|
|
||||||
<label class="text-sm" for="hp">HP</label>
|
|
||||||
<progress class="h-2 rounded-lg w-full max-w-44 appearance-none accent-red" id="hp" :value="gameStore.character.hitpoints" max="100">{{ gameStore.character.hitpoints }}%</progress>
|
|
||||||
</div>
|
|
||||||
<div class="w-full flex items-center justify-between">
|
|
||||||
<label class="text-sm" for="mp">MP</label>
|
|
||||||
<progress class="h-2 rounded-lg w-full max-w-44 appearance-none accent-blue" id="mp" :value="gameStore.character.mana" max="100">{{ gameStore.character.mana }}%</progress>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TODO: Replace gameStore.character with other (selected) player's -->
|
|
||||||
<!-- <div class="hud-wrapper other-player relative right-0 w-[310px] h-[84px]">-->
|
|
||||||
<!-- <div class="absolute w-14 h-14 bg-white/80 rounded-full border-3 border-solid border-white top-1/2 -translate-y-1/2 right-0 z-20">-->
|
|
||||||
<!-- <img class="w-7 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 -scale-x-100" draggable="false" src="/assets/avatar/default/head.png" />-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- <div class="hud-bg absolute top-0 right-8 w-[280px] h-[84px] z-10 bg-[url('/assets/bg-hud-2.png')] bg-center bg-[length:cover] bg-no-repeat mask-[url('/assets/shapes/hud-image-shape.svg')] mask-center mask-[length:cover] mask-no-repeat"></div>-->
|
|
||||||
<!-- <div class="absolute top-0 right-8 w-[280px] h-[84px] z-10 -scale-x-100 bg-[url('/assets/shapes/hud-shape-empty.svg')] bg-center bg-[length:cover] bg-no-repeat">-->
|
|
||||||
<!-- <div class="h-16 flex flex-col items-end -scale-x-100 py-2.5 pr-12 pl-5">-->
|
|
||||||
<!-- <div class="w-full flex items-center justify-between mb-1.5">-->
|
|
||||||
<!-- <span class="text-ellipsis overflow-hidden whitespace-nowrap max-w-32 text-sm">{{ gameStore.character.name }}</span>-->
|
|
||||||
<!-- <span class="text-sm">lvl. {{ gameStore.character.level }}</span>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- <div class="w-full flex items-center justify-between">-->
|
|
||||||
<!-- <label class="text-sm" for="hp">HP</label>-->
|
|
||||||
<!-- <progress class="h-2 rounded-lg w-full max-w-44 appearance-none accent-red" id="hp" :value="gameStore.character.hitpoints" max="100">{{ gameStore.character.hitpoints }}%</progress>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -50,41 +20,30 @@ const gameStore = useGameStore()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.hud-wrapper {
|
#hp {
|
||||||
.hud-bg {
|
// Chrome, Safari, Edge, Opera
|
||||||
mask: url('/assets/shapes/hud-image-shape.svg') center/cover no-repeat;
|
&::-webkit-progress-value {
|
||||||
|
@apply bg-gradient-to-r from-green from-75% to-green-200 rounded-sm;
|
||||||
}
|
}
|
||||||
#hp {
|
&::-webkit-progress-bar {
|
||||||
// Chrome, Safari, Edge, Opera
|
@apply bg-white rounded-sm border border-solid border-black;
|
||||||
&::-webkit-progress-value {
|
|
||||||
@apply bg-red rounded-lg;
|
|
||||||
}
|
|
||||||
&::-webkit-progress-bar {
|
|
||||||
@apply bg-white rounded-lg border-2 border-solid border-white;
|
|
||||||
}
|
|
||||||
// Firefox
|
|
||||||
&::-moz-progress-bar {
|
|
||||||
@apply bg-red rounded-lg border-2 border-solid border-white;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#mp {
|
// Firefox
|
||||||
// Chrome, Safari, Edge, Opera
|
&::-moz-progress-bar {
|
||||||
&::-webkit-progress-value {
|
@apply bg-gradient-to-r from-green from-75% to-green-200 rounded-sm border border-solid border-black;
|
||||||
@apply bg-blue rounded-lg;
|
|
||||||
}
|
|
||||||
&::-webkit-progress-bar {
|
|
||||||
@apply bg-white rounded-lg border-2 border-solid border-white;
|
|
||||||
}
|
|
||||||
// Firefox
|
|
||||||
&::-moz-progress-bar {
|
|
||||||
@apply bg-blue rounded-lg border-2 border-solid border-white;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
&.other-player {
|
#sp {
|
||||||
.hud-bg {
|
// Chrome, Safari, Edge, Opera
|
||||||
mask: url('/assets/shapes/hud-image-shape-flipped.svg') center/cover no-repeat;
|
&::-webkit-progress-value {
|
||||||
}
|
@apply bg-gradient-to-r from-blue from-75% to-blue-200 rounded-sm;
|
||||||
|
}
|
||||||
|
&::-webkit-progress-bar {
|
||||||
|
@apply bg-white rounded-sm border border-solid border-black;
|
||||||
|
}
|
||||||
|
// Firefox
|
||||||
|
&::-moz-progress-bar {
|
||||||
|
@apply bg-gradient-to-r from-blue from-75% to-blue-200 rounded-sm border border-solid border-black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
48
src/components/gui/Keybindings.vue
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<div class="absolute left-[300px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F1</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f1-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[346px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F2</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f2-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[392px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F3</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f3-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[438px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F4</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f4-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[484px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F5</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f5-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[530px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F6</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f6-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[576px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F7</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f7-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute left-[622px] top-4">
|
||||||
|
<button class="z-20 group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners-light.svg')] bg-no-repeat block w-[42px] h-[42px] relative p-0"></button>
|
||||||
|
<span class="z-10 text-pixel absolute top-1 left-2">F8</span>
|
||||||
|
<div class="absolute top-0 left-0 h-full w-full bg-[url('/assets/icons/f8-icon.png')] bg-no-repeat"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
</script>
|
@ -1,39 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<ul class="list-none flex gap-2.5 items-center m-0 max-md:pl-0">
|
<ul class="list-none flex flex-col gap-2.5 items-center m-0 pl-0 absolute left-4 top-4">
|
||||||
<li class="menu-item group relative" @click="gameStore.toggleChat">
|
|
||||||
<div class="group-hover:block absolute bottom-16 left-1/2 -translate-x-1/2 w-20 h-6 text-center bg-gray-300 border-2 border-solid border-cyan rounded-3xl hidden">
|
|
||||||
<p class="absolute w-full bottom-0 m-0 text-xs leading-6">Chat</p>
|
|
||||||
<div class="group-hover:block absolute -bottom-2.5 bg-cyan h-2 w-3.5 [clip-path:polygon(100%_0,_0_0,_50%_100%)] left-1/2 -translate-x-1/2 hidden"></div>
|
|
||||||
</div>
|
|
||||||
<a class="group-hover:bg-gray/70 group-hover:cursor-pointer p-1.5 bg-gray-300/70 border-2 border-solid border-cyan-200 hover:border-cyan rounded-lg block w-11 h-9">
|
|
||||||
<img class="group-hover:drop-shadow-default w-11 h-9 object-contain" draggable="false" src="/assets/icons/chat.png" />
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="menu-item group relative">
|
<li class="menu-item group relative">
|
||||||
<div class="group-hover:block absolute bottom-16 left-1/2 -translate-x-1/2 w-20 h-6 text-center bg-gray-300 border-2 border-solid border-cyan rounded-3xl hidden">
|
<div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
|
||||||
<p class="absolute w-full bottom-0 m-0 text-xs leading-6">World</p>
|
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">Open menu</p>
|
||||||
<div class="group-hover:block absolute -bottom-2.5 bg-cyan h-2 w-3.5 [clip-path:polygon(100%_0,_0_0,_50%_100%)] left-1/2 -translate-x-1/2 hidden"></div>
|
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
<a class="group-hover:bg-gray/70 group-hover:cursor-pointer p-1.5 bg-gray-300/70 border-2 border-solid border-cyan-200 hover:border-cyan rounded-lg block w-11 h-9">
|
<a class="group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners.svg')] bg-no-repeat block w-[42px] h-[42px] relative">
|
||||||
<img class="group-hover:drop-shadow-default w-11 h-9 object-contain" draggable="false" src="/assets/icons/world.png" />
|
<img class="group-hover:drop-shadow-default w-6 h-5 mx-[9px] my-[11px] object-contain" draggable="false" src="/assets/icons/menu-icon.svg" />
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="menu-item group relative">
|
|
||||||
<div class="group-hover:block absolute bottom-16 left-1/2 -translate-x-1/2 w-20 h-6 text-center bg-gray-300 border-2 border-solid border-cyan rounded-3xl hidden">
|
|
||||||
<p class="absolute w-full bottom-0 m-0 text-xs leading-6">Users</p>
|
|
||||||
<div class="group-hover:block absolute -bottom-2.5 bg-cyan h-2 w-3.5 [clip-path:polygon(100%_0,_0_0,_50%_100%)] left-1/2 -translate-x-1/2 hidden"></div>
|
|
||||||
</div>
|
|
||||||
<a class="group-hover:bg-gray/70 group-hover:cursor-pointer p-1.5 bg-gray-300/70 border-2 border-solid border-cyan-200 hover:border-cyan rounded-lg block w-11 h-9">
|
|
||||||
<img class="group-hover:drop-shadow-default w-11 h-9 object-contain" draggable="false" src="/assets/icons/users.png" />
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="menu-item group relative" @click="gameStore.toggleUserPanel">
|
<li class="menu-item group relative" @click="gameStore.toggleUserPanel">
|
||||||
<div class="group-hover:block absolute bottom-16 left-1/2 -translate-x-1/2 w-20 h-6 text-center bg-gray-300 border-2 border-solid border-cyan rounded-3xl hidden">
|
<div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
|
||||||
<p class="absolute w-full bottom-0 m-0 text-xs leading-6">Inventory</p>
|
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">User Profile</p>
|
||||||
<div class="group-hover:block absolute -bottom-2.5 bg-cyan h-2 w-3.5 [clip-path:polygon(100%_0,_0_0,_50%_100%)] left-1/2 -translate-x-1/2 hidden"></div>
|
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
<a class="group-hover:bg-gray/70 group-hover:cursor-pointer p-1.5 bg-gray-300/70 border-2 border-solid border-cyan-200 hover:border-cyan rounded-lg block w-11 h-9">
|
<a class="group-hover:cursor-pointer bg-[url('/assets/ui-border-4-corners.svg')] bg-no-repeat block w-[42px] h-[42px] relative">
|
||||||
<img class="group-hover:drop-shadow-default w-11 h-9 object-contain" draggable="false" src="/assets/icons/treasure-chest.png" />
|
<img class="group-hover:drop-shadow-default w-8 h-8 m-[5px] object-contain" draggable="false" src="/assets/avatar/default/head.png" />
|
||||||
|
<p class="absolute bottom-0 -right-1.5 m-0 max-w-4 font-ui z-10 text-white text-[12px] leading-[6px] drop-shadow-pixel"><span class="font-ui text-white text-[8px] ml-0.5">LVL</span> {{ characterLevel }}</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="menu-item group relative" @click="gameStore.toggleChat">
|
||||||
|
<div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
|
||||||
|
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">Open Chat</p>
|
||||||
|
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
|
||||||
|
</div>
|
||||||
|
<a class="group-hover:bg-gray-800 group-hover:cursor-pointer border border-b-4 border-solid rounded border-gray-500 block w-[34px] h-[31px]">
|
||||||
|
<img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/chat-icon.svg" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="menu-item group relative">
|
||||||
|
<div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
|
||||||
|
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">World map</p>
|
||||||
|
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
|
||||||
|
</div>
|
||||||
|
<a class="group-hover:bg-gray-800 group-hover:cursor-pointer border border-b-4 border-solid rounded border-gray-500 block w-[34px] h-[31px]">
|
||||||
|
<img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/map-icon.svg" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="menu-item group relative">
|
||||||
|
<div class="group-hover:block absolute top-1/2 left-14 -translate-y-1/2 w-20 h-6 text-center bg-gray-800 border-2 border-solid border-gray-500 rounded-3xl hidden">
|
||||||
|
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">Users</p>
|
||||||
|
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
|
||||||
|
</div>
|
||||||
|
<a class="group-hover:bg-gray-800 group-hover:cursor-pointer border border-b-4 border-solid rounded border-gray-500 block w-[34px] h-[31px]">
|
||||||
|
<img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/socials-icon.svg" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -43,4 +53,5 @@
|
|||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
|
let characterLevel = gameStore.character?.level.toString().padStart(2, '0');
|
||||||
</script>
|
</script>
|
||||||
|
23
src/components/gui/Minimap.vue
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<div class="absolute top-4 right-4">
|
||||||
|
<div class="w-40 h-40 rounded-full border border-solid border-gray-500 bg-[url('/assets/ui-texture.png')] bg-no-repeat">
|
||||||
|
<div class="w-40 h-40 rounded-full shadow-inner"></div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute -bottom-3 left-1/2 -translate-x-1/2 flex gap-1">
|
||||||
|
<button class="w-6 h-6 relative p-0">
|
||||||
|
<img class="absolute w-3 h-3 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" src="/assets/icons/plus-icon.svg" />
|
||||||
|
<img class="w-full h-full" src="/assets/ui-border-4-corners.svg" />
|
||||||
|
</button>
|
||||||
|
<button class="w-6 h-6 relative p-0">
|
||||||
|
<img class="absolute w-3 h-3 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" src="/assets/icons/minus-icon.svg" />
|
||||||
|
<img class="w-full h-full" src="/assets/ui-border-4-corners.svg" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
</script>
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="absolute z-50 w-full h-dvh top-0 left-0 bg-black/60" v-show="gameStore.isUserPanelOpen">
|
<div class="absolute z-50 w-full h-dvh top-0 left-0 bg-black/60" v-show="gameStore.uiSettings.isUserPanelOpen">
|
||||||
<div class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 max-w-[875px] max-h-[600px] h-full w-[80%] bg-gray-300/80 border-solid border-2 border-cyan-200 rounded-lg z-50 flex flex-col backdrop-blur-sm shadow-lg">
|
<div class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 max-w-[875px] max-h-[600px] h-full w-[80%] bg-gray-300/80 border-solid border-2 border-cyan-200 rounded-md z-50 flex flex-col backdrop-blur-sm shadow-lg">
|
||||||
<div class="p-2.5 flex max-sm:flex-wrap justify-between items-center gap-5 border-solid border-0 border-b border-cyan-200">
|
<div class="p-2.5 flex max-sm:flex-wrap justify-between items-center gap-5 border-solid border-0 border-b border-cyan-200">
|
||||||
<h3 class="m-0 font-medium shrink-0">Game menu</h3>
|
<h3 class="m-0 font-medium shrink-0">Game menu</h3>
|
||||||
<div class="hidden sm:flex gap-1.5 flex-wrap">
|
<div class="hidden sm:flex gap-1.5 flex-wrap">
|
||||||
@ -10,7 +10,7 @@
|
|||||||
<button @click.stop="userPanelScreen = 'settings'" :class="{ active: userPanelScreen === 'settings' }" class="btn-cyan py-1.5 px-4 min-w-24">Settings</button>
|
<button @click.stop="userPanelScreen = 'settings'" :class="{ active: userPanelScreen === 'settings' }" class="btn-cyan py-1.5 px-4 min-w-24">Settings</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2.5">
|
<div class="flex gap-2.5">
|
||||||
<button class="w-5 h-5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out" @click="gameStore.toggleUserPanel">
|
<button class="w-3.5 h-3.5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out" @click="gameStore.toggleUserPanel">
|
||||||
<img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" class="w-full h-full" />
|
<img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" class="w-full h-full" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,11 +6,11 @@
|
|||||||
<form class="flex gap-2.5 flex-wrap">
|
<form class="flex gap-2.5 flex-wrap">
|
||||||
<div class="form-field-half max-w-[300px]">
|
<div class="form-field-half max-w-[300px]">
|
||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
<input class="input-cyan" :class="{ inactive: !editCharacter }" type="text" name="name" placeholder="Ethereal" :disabled="!editCharacter" />
|
<input class="input-field" :class="{ inactive: !editCharacter }" type="text" name="name" placeholder="Ethereal" :disabled="!editCharacter" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field-half max-w-[300px] relative">
|
<div class="form-field-half max-w-[300px] relative">
|
||||||
<label for="class">Class</label>
|
<label for="class">Class</label>
|
||||||
<select class="input-cyan" v-model="characterClass" :class="{ inactive: !editCharacter }" name="class" :disabled="!editCharacter">
|
<select class="input-field" v-model="characterClass" :class="{ inactive: !editCharacter }" name="class" :disabled="!editCharacter">
|
||||||
<option value="Knight" :selected="characterClass == 'Knight'" :disabled="characterClass == 'Knight'">Knight</option>
|
<option value="Knight" :selected="characterClass == 'Knight'" :disabled="characterClass == 'Knight'">Knight</option>
|
||||||
<option value="Paladin" :selected="characterClass == 'Paladin'" :disabled="characterClass == 'Paladin'">Paladin</option>
|
<option value="Paladin" :selected="characterClass == 'Paladin'" :disabled="characterClass == 'Paladin'">Paladin</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<!-- Chat bubble -->
|
||||||
<Container ref="charChatContainer" :depth="999" :x="currentX" :y="currentY">
|
<Container ref="charChatContainer" :depth="999" :x="currentX" :y="currentY">
|
||||||
<RoundRectangle @create="createChatBubble" :origin-x="0.5" :origin-y="7.5" :fillColor="0xffffff" :width="194" :height="21" :radius="5" />
|
<RoundRectangle @create="createChatBubble" :origin-x="0.5" :origin-y="7.5" :fillColor="0xffffff" :width="194" :height="21" :radius="20" />
|
||||||
<Text @create="createChatText" text="" :origin-x="0.5" :origin-y="10.9" :style="{ fontSize: 13, fontFamily: 'Arial', color: '#000' }" />
|
<Text @create="createChatText" text="" :origin-x="0.5" :origin-y="10.9" :style="{ fontSize: 13, fontFamily: 'Arial', color: '#000' }" />
|
||||||
</Container>
|
</Container>
|
||||||
|
<!-- Character name and health -->
|
||||||
<Container :depth="999" :x="currentX" :y="currentY">
|
<Container :depth="999" :x="currentX" :y="currentY">
|
||||||
<Text @create="createText" :text="character.name" :origin-x="0.5" :origin-y="9" />
|
<Text @create="createText" :text="character.name" :origin-x="0.5" :origin-y="9" />
|
||||||
<RoundRectangle :origin-x="0.5" :origin-y="18.5" :fillColor="0xffffff" :width="74" :height="6" :radius="5" />
|
<RoundRectangle :origin-x="0.5" :origin-y="18.5" :fillColor="0xffffff" :width="74" :height="6" :radius="5" />
|
||||||
<RoundRectangle :origin-x="0.5" :origin-y="36.4" :fillColor="0x00b3b3" :width="70" :height="3" :radius="5" />
|
<RoundRectangle :origin-x="0.5" :origin-y="36.4" :fillColor="0x00b3b3" :width="70" :height="3" :radius="5" />
|
||||||
</Container>
|
</Container>
|
||||||
<Container :depth="isometricDepth" :x="currentX" :y="currentY" ref="charContainer">
|
<!-- Character sprite -->
|
||||||
|
<Container ref="charContainer" :depth="isometricDepth" :x="currentX" :y="currentY">
|
||||||
<Sprite ref="charSprite" :origin-y="1" :flipX="isFlippedX" :flipY="false" />
|
<Sprite ref="charSprite" :origin-y="1" :flipX="isFlippedX" :flipY="false" />
|
||||||
</Container>
|
</Container>
|
||||||
</template>
|
</template>
|
||||||
@ -19,7 +22,7 @@ import { type ExtendedCharacter } from '@/types'
|
|||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useZoneStore } from '@/stores/zoneStore'
|
import { useZoneStore } from '@/stores/zoneStore'
|
||||||
import { watch, computed, ref, onMounted, onUnmounted } from 'vue'
|
import { watch, computed, ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { Container, refObj, RoundRectangle, Sprite, Text, useGame } from 'phavuer'
|
import { Container, refObj, RoundRectangle, Sprite, Text, useGame, useScene } from 'phavuer'
|
||||||
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
||||||
|
|
||||||
enum Direction {
|
enum Direction {
|
||||||
@ -40,6 +43,7 @@ const charSprite = refObj<Phaser.GameObjects.Sprite>()
|
|||||||
const game = useGame()
|
const game = useGame()
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const zoneStore = useZoneStore()
|
const zoneStore = useZoneStore()
|
||||||
|
const scene = useScene()
|
||||||
|
|
||||||
const currentX = ref(0)
|
const currentX = ref(0)
|
||||||
const currentY = ref(0)
|
const currentY = ref(0)
|
||||||
@ -134,12 +138,11 @@ const createChatBubble = (container: Phaser.GameObjects.Container) => {
|
|||||||
const createChatText = (text: Phaser.GameObjects.Text) => {
|
const createChatText = (text: Phaser.GameObjects.Text) => {
|
||||||
text.setName(`${props.character.name}_chatText`)
|
text.setName(`${props.character.name}_chatText`)
|
||||||
text.setFontSize(13)
|
text.setFontSize(13)
|
||||||
text.setFontStyle('bold')
|
|
||||||
text.setFontFamily('Arial')
|
text.setFontFamily('Arial')
|
||||||
|
|
||||||
// Fix text alignment on Windows
|
// Fix text alignment on Windows and Android
|
||||||
if (game.device.os.windows) {
|
if (game.device.os.windows || game.device.os.android) {
|
||||||
text.setOrigin(0.5, 9.8)
|
text.setOrigin(0.5, 9.75)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,8 +150,8 @@ const createText = (text: Phaser.GameObjects.Text) => {
|
|||||||
text.setFontSize(13)
|
text.setFontSize(13)
|
||||||
text.setFontFamily('Arial')
|
text.setFontFamily('Arial')
|
||||||
|
|
||||||
// Fix text alignment on Windows
|
// Fix text alignment on Windows and Android
|
||||||
if (game.device.os.windows) {
|
if (game.device.os.windows || game.device.os.android) {
|
||||||
text.setOrigin(0.5, 8)
|
text.setOrigin(0.5, 8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,6 +178,10 @@ onMounted(() => {
|
|||||||
|
|
||||||
if (props.character.id === gameStore.character!.id) {
|
if (props.character.id === gameStore.character!.id) {
|
||||||
zoneStore.setCharacterLoaded(true)
|
zoneStore.setCharacterLoaded(true)
|
||||||
|
|
||||||
|
// #146 : Set camera position to character, need to be improved still
|
||||||
|
scene.cameras.main.startFollow(charContainer.value as Phaser.GameObjects.Container)
|
||||||
|
scene.cameras.main.stopFollow()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sprite
|
// Set sprite
|
||||||
|
@ -40,12 +40,12 @@ const modalOpened = ref(props.modalOpened)
|
|||||||
<template>
|
<template>
|
||||||
<Modal :closable="false" :is-resizable="false" :isModalOpen="true" @modal:close="() => (modalOpened = !modalOpened)" :modal-width="300" :modal-height="190">
|
<Modal :closable="false" :is-resizable="false" :isModalOpen="true" @modal:close="() => (modalOpened = !modalOpened)" :modal-width="300" :modal-height="190">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<div class="text-white">
|
<div class="text-gray-300">
|
||||||
<slot name="modalHeader"></slot>
|
<slot name="modalHeader"></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
<div class="text-white h-full">
|
<div class="text-gray-300 h-full">
|
||||||
<div class="flex h-full flex-col justify-between">
|
<div class="flex h-full flex-col justify-between">
|
||||||
<span class="p-2">
|
<span class="p-2">
|
||||||
<slot name="modalBody"></slot>
|
<slot name="modalBody"></slot>
|
||||||
|
@ -8,21 +8,13 @@ import { onBeforeUnmount, ref } from 'vue'
|
|||||||
import { usePointerHandlers } from '@/composables/usePointerHandlers'
|
import { usePointerHandlers } from '@/composables/usePointerHandlers'
|
||||||
import { useCameraControls } from '@/composables/useCameraControls'
|
import { useCameraControls } from '@/composables/useCameraControls'
|
||||||
|
|
||||||
interface Props {
|
const props = defineProps<{ layer: Phaser.Tilemaps.TilemapLayer }>()
|
||||||
layer: Phaser.Tilemaps.TilemapLayer
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
|
||||||
const scene = useScene()
|
const scene = useScene()
|
||||||
|
|
||||||
const waypoint = ref({
|
const waypoint = ref({ visible: false, x: 0, y: 0 })
|
||||||
visible: false,
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
const { camera, isDragging } = useCameraControls(scene)
|
const { camera } = useCameraControls(scene)
|
||||||
const { setupPointerHandlers, cleanupPointerHandlers } = usePointerHandlers(scene, props.layer, waypoint, camera, isDragging)
|
const { setupPointerHandlers, cleanupPointerHandlers } = usePointerHandlers(scene, props.layer, waypoint, camera)
|
||||||
|
|
||||||
setupPointerHandlers()
|
setupPointerHandlers()
|
||||||
onBeforeUnmount(cleanupPointerHandlers)
|
onBeforeUnmount(cleanupPointerHandlers)
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div v-if="isModalOpenRef" class="fixed bg-gray-300/80 border-solid border-2 border-cyan-200 z-50 flex flex-col backdrop-blur-sm shadow-lg" :style="modalStyle">
|
<div v-if="isModalOpenRef" class="fixed bg-gray border-solid border-2 border-gray-500 z-50 flex flex-col backdrop-blur-sm shadow-lg" :style="modalStyle">
|
||||||
<div @mousedown="startDrag" class="cursor-move p-2.5 flex justify-between items-center border-solid border-0 border-b border-cyan-200">
|
<div @mousedown="startDrag" class="cursor-move p-2.5 flex justify-between items-center border-solid border-0 border-b border-gray-500">
|
||||||
<slot name="modalHeader" />
|
<slot name="modalHeader" />
|
||||||
<div class="flex gap-2.5">
|
<div class="flex gap-2.5">
|
||||||
<button @click="toggleFullScreen" class="w-5 h-5 m-0 p-0 relative hover:scale-110 transition-transform duration-300 ease-in-out" v-if="canFullScreen">
|
<button @click="toggleFullScreen" class="w-5 h-5 m-0 p-0 relative hover:scale-110 transition-transform duration-300 ease-in-out" v-if="canFullScreen">
|
||||||
<img :alt="isFullScreen ? 'exit full-screen' : 'full-screen'" draggable="false" :src="isFullScreen ? '/assets/icons/minimize.svg' : '/assets/icons/full-screen.svg'" class="w-full h-full invert" />
|
<img :alt="isFullScreen ? 'exit full-screen' : 'full-screen'" draggable="false" :src="isFullScreen ? '/assets/icons/minimize.svg' : '/assets/icons/full-screen.svg'" class="w-full h-full invert" />
|
||||||
</button>
|
</button>
|
||||||
<button @click="close" v-if="closable" class="w-5 h-5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out">
|
<button @click="close" v-if="closable" class="w-3.5 h-3.5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out">
|
||||||
<img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" class="w-full h-full" />
|
<img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" class="w-full h-full" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -16,7 +16,7 @@
|
|||||||
<slot name="modalBody" />
|
<slot name="modalBody" />
|
||||||
<img v-if="isResizable && !isFullScreen" src="/assets/icons/resize-icon.svg" alt="resize" class="absolute bottom-0 right-0 w-5 h-5 cursor-nwse-resize invert-[60%]" @mousedown="startResize" />
|
<img v-if="isResizable && !isFullScreen" src="/assets/icons/resize-icon.svg" alt="resize" class="absolute bottom-0 right-0 w-5 h-5 cursor-nwse-resize invert-[60%]" @mousedown="startResize" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="$slots.modalFooter" class="px-5 min-h-12 flex justify-end gap-7.5 items-center border-solid border-t border-cyan-200">
|
<div v-if="$slots.modalFooter" class="px-5 min-h-12 flex justify-end gap-7.5 items-center border-solid border-t border-gray-500">
|
||||||
<slot name="modalFooter" />
|
<slot name="modalFooter" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal v-for="notification in gameStore.getNotifications" :key="notification.id" :isModalOpen="true" @modal:close="closeNotification(notification.id)">
|
<Modal v-for="notification in gameStore.getNotifications" :key="notification.id" :isModalOpen="true" @modal:close="closeNotification(notification.id)">
|
||||||
<template #modalHeader v-if="notification.title">
|
<template #modalHeader v-if="notification.title">
|
||||||
<h3 class="m-0 font-medium shrink-0">{{ notification.title }}</h3>
|
<h3 class="m-0 font-medium shrink-0 text-gray-300">{{ notification.title }}</h3>
|
||||||
</template>
|
</template>
|
||||||
<template #modalBody v-if="notification.message">
|
<template #modalBody v-if="notification.message">
|
||||||
<p class="m-4">{{ notification.message }}</p>
|
<p class="m-4">{{ notification.message }}</p>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Image v-for="object in zoneStore.zone?.zoneObjects" :depth="calculateIsometricDepth(object.positionX, object.positionY, object.object.frameWidth, object.object.frameHeight)" :key="object.id" v-bind="getObjectImageProps(object)" />
|
<Image v-for="object in zoneStore.zone?.zoneObjects" :depth="calculateIsometricDepth(object.positionX, object.positionY, object.object.frameWidth, object.object.frameHeight)" :key="object.id" v-bind="getObjectImageProps(object)" :flipX="object.isRotated" />
|
||||||
<!-- <Text v-for="object in zoneStore.zone?.zoneObjects" :key="object.id" :depth="99999" :text="Math.ceil(calculateIsometricDepth(object.positionX, object.positionY, object.object.frameWidth, object.object.frameHeight))" v-bind="getObjectProps(object)" />-->
|
<!-- <Text v-for="object in zoneStore.zone?.zoneObjects" :key="object.id" :depth="99999" :text="Math.ceil(calculateIsometricDepth(object.positionX, object.positionY, object.object.frameWidth, object.object.frameHeight))" v-bind="getObjectProps(object)" />-->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -38,11 +38,11 @@ function createTileLayer() {
|
|||||||
const uniqueTiles = new Set(tilesFromZone.flat().filter(Boolean))
|
const uniqueTiles = new Set(tilesFromZone.flat().filter(Boolean))
|
||||||
|
|
||||||
const tilesetImages = Array.from(uniqueTiles).map((tile, index) => {
|
const tilesetImages = Array.from(uniqueTiles).map((tile, index) => {
|
||||||
return zoneTilemap.addTilesetImage(tile, tile, config.tile_size.x, config.tile_size.y, 0, 0, index + 1, { x: 0, y: -config.tile_size.y })
|
return zoneTilemap.addTilesetImage(tile, tile, config.tile_size.x, config.tile_size.y, 1, 2, index + 1, { x: 0, y: -config.tile_size.y })
|
||||||
}) as any
|
}) as any
|
||||||
|
|
||||||
// Add blank tile
|
// Add blank tile
|
||||||
tilesetImages.push(zoneTilemap.addTilesetImage('blank_tile', 'blank_tile', config.tile_size.x, config.tile_size.y, 0, 0, 0, { 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, 0, config.tile_size.y) as Phaser.Tilemaps.TilemapLayer
|
const layer = zoneTilemap.createBlankLayer('tiles', tilesetImages, 0, config.tile_size.y) as Phaser.Tilemaps.TilemapLayer
|
||||||
|
|
||||||
layer.setDepth(0)
|
layer.setDepth(0)
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
import { useScene } from 'phavuer'
|
import { useScene } from 'phavuer'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useZoneStore } from '@/stores/zoneStore'
|
import { useZoneStore } from '@/stores/zoneStore'
|
||||||
import { onBeforeMount, onBeforeUnmount, onMounted, ref } from 'vue'
|
import { onBeforeMount, onBeforeUnmount, ref } from 'vue'
|
||||||
import type { Character as CharacterT, Zone as ZoneT, ExtendedCharacter as ExtendedCharacterT } from '@/types'
|
import type { Character as CharacterT, Zone as ZoneT, ExtendedCharacter as ExtendedCharacterT } from '@/types'
|
||||||
import Tiles from '@/components/zone/Tiles.vue'
|
import Tiles from '@/components/zone/Tiles.vue'
|
||||||
import Objects from '@/components/zone/Objects.vue'
|
import Objects from '@/components/zone/Objects.vue'
|
||||||
|
@ -1,75 +1,83 @@
|
|||||||
import { ref, type Ref } from 'vue'
|
import { type Ref, ref } from 'vue'
|
||||||
import { getTile, tileToWorldXY } from '@/composables/zoneComposable'
|
import { getTile, tileToWorldXY } from '@/composables/zoneComposable'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import config from '@/config'
|
import config from '@/config'
|
||||||
|
|
||||||
export function useGamePointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
|
export function useGamePointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const lastDragTime = ref(0)
|
const pointerStartPosition = ref({ x: 0, y: 0 })
|
||||||
const dragTimeout = 500 // 500ms timeout for dragging, to prevent accidental clicks
|
const dragThreshold = 5 // pixels
|
||||||
|
|
||||||
function updateWaypoint(pointer: Phaser.Input.Pointer) {
|
function updateWaypoint(worldX: number, worldY: number) {
|
||||||
const { x: px, y: py } = camera.getWorldPoint(pointer.x, pointer.y)
|
const pointerTile = getTile(worldX, worldY, layer)
|
||||||
const pointerTile = getTile(px, py, layer)
|
if (pointerTile) {
|
||||||
|
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
|
||||||
waypoint.value.visible = !!pointerTile
|
waypoint.value = {
|
||||||
if (!pointerTile) return
|
visible: true,
|
||||||
|
x: worldPoint.positionX,
|
||||||
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
|
y: worldPoint.positionY + config.tile_size.y + 15
|
||||||
waypoint.value.x = worldPoint.positionX
|
}
|
||||||
waypoint.value.y = worldPoint.positionY + config.tile_size.y + 15
|
} else {
|
||||||
|
waypoint.value.visible = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dragZone(pointer: Phaser.Input.Pointer) {
|
function handlePointerDown(pointer: Phaser.Input.Pointer) {
|
||||||
if (!gameStore.isPlayerDraggingCamera) return
|
pointerStartPosition.value = { x: pointer.x, y: pointer.y }
|
||||||
|
gameStore.setPlayerDraggingCamera(true)
|
||||||
const { x, y, prevPosition } = pointer
|
|
||||||
const { scrollX, scrollY, zoom } = camera
|
|
||||||
camera.setScroll(scrollX - (x - prevPosition.x) / zoom, scrollY - (y - prevPosition.y) / zoom)
|
|
||||||
|
|
||||||
lastDragTime.value = Date.now()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
||||||
dragZone(pointer)
|
const { worldX, worldY } = pointer
|
||||||
updateWaypoint(pointer)
|
updateWaypoint(worldX, worldY)
|
||||||
|
|
||||||
|
if (gameStore.isPlayerDraggingCamera) {
|
||||||
|
const distance = Phaser.Math.Distance.Between(pointerStartPosition.value.x, pointerStartPosition.value.y, pointer.x, pointer.y)
|
||||||
|
|
||||||
|
if (distance > dragThreshold) {
|
||||||
|
const { x, y, prevPosition } = pointer
|
||||||
|
const { scrollX, scrollY, zoom } = camera
|
||||||
|
camera.setScroll(scrollX - (x - prevPosition.x) / zoom, scrollY - (y - prevPosition.y) / zoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clickTile(pointer: Phaser.Input.Pointer) {
|
function handlePointerUp(pointer: Phaser.Input.Pointer) {
|
||||||
const currentTime = Date.now()
|
const distance = Phaser.Math.Distance.Between(pointerStartPosition.value.x, pointerStartPosition.value.y, pointer.x, pointer.y)
|
||||||
if (currentTime - lastDragTime.value < dragTimeout) {
|
|
||||||
return
|
if (distance <= dragThreshold) {
|
||||||
|
const pointerTile = getTile(pointer.worldX, pointer.worldY, layer)
|
||||||
|
if (pointerTile) {
|
||||||
|
gameStore.connection?.emit('character:initMove', {
|
||||||
|
positionX: pointerTile.x,
|
||||||
|
positionY: pointerTile.y
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { x: px, y: py } = camera.getWorldPoint(pointer.x, pointer.y)
|
gameStore.setPlayerDraggingCamera(false)
|
||||||
const pointerTile = getTile(px, py, layer)
|
|
||||||
|
|
||||||
if (!pointerTile) return
|
|
||||||
|
|
||||||
gameStore.connection?.emit('character:initMove', {
|
|
||||||
positionX: pointerTile.x,
|
|
||||||
positionY: pointerTile.y
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleZoom(pointer: Phaser.Input.Pointer) {
|
function handleZoom(pointer: Phaser.Input.Pointer) {
|
||||||
if (!(pointer.event instanceof WheelEvent) || !pointer.event.shiftKey) return
|
if (pointer.event instanceof WheelEvent && pointer.event.shiftKey) {
|
||||||
|
const deltaY = pointer.event.deltaY
|
||||||
const deltaY = pointer.event.deltaY
|
let zoomLevel = camera.zoom - deltaY * 0.005
|
||||||
let zoomLevel = camera.zoom - deltaY * 0.005
|
zoomLevel = Phaser.Math.Clamp(zoomLevel, 1, 3)
|
||||||
if(zoomLevel <= 0 || zoomLevel >= 3) return
|
camera.setZoom(zoomLevel)
|
||||||
camera.setZoom(zoomLevel)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setupPointerHandlers = () => {
|
const setupPointerHandlers = () => {
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_UP, clickTile)
|
scene.input.on(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
||||||
|
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_WHEEL, handleZoom)
|
scene.input.on(Phaser.Input.Events.POINTER_WHEEL, handleZoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanupPointerHandlers = () => {
|
const cleanupPointerHandlers = () => {
|
||||||
scene.input.off(Phaser.Input.Events.POINTER_UP, clickTile)
|
scene.input.off(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
|
||||||
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
||||||
|
scene.input.off(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
||||||
scene.input.off(Phaser.Input.Events.POINTER_WHEEL, handleZoom)
|
scene.input.off(Phaser.Input.Events.POINTER_WHEEL, handleZoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { computed, type Ref } from 'vue'
|
import { computed, type Ref } from 'vue'
|
||||||
import { getTile, tileToWorldXY } from '@/composables/zoneComposable'
|
import { getTile, tileToWorldXY } from '@/composables/zoneComposable'
|
||||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||||
import config from '@/config'
|
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
import config from '@/config'
|
||||||
|
|
||||||
export function useZoneEditorPointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
|
export function useZoneEditorPointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
@ -13,20 +13,24 @@ export function useZoneEditorPointerHandlers(scene: Phaser.Scene, layer: Phaser.
|
|||||||
const { x: px, y: py } = camera.getWorldPoint(pointer.x, pointer.y)
|
const { x: px, y: py } = camera.getWorldPoint(pointer.x, pointer.y)
|
||||||
const pointerTile = getTile(px, py, layer)
|
const pointerTile = getTile(px, py, layer)
|
||||||
|
|
||||||
waypoint.value.visible = !!pointerTile
|
if (pointerTile) {
|
||||||
if (!pointerTile) return
|
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
|
||||||
|
waypoint.value = {
|
||||||
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
|
visible: true,
|
||||||
waypoint.value.x = worldPoint.positionX
|
x: worldPoint.positionX,
|
||||||
waypoint.value.y = worldPoint.positionY + config.tile_size.y + 15
|
y: worldPoint.positionY + config.tile_size.y + 15
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
waypoint.value.visible = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dragZone(pointer: Phaser.Input.Pointer) {
|
function dragZone(pointer: Phaser.Input.Pointer) {
|
||||||
if (!gameStore.isPlayerDraggingCamera) return
|
if (gameStore.isPlayerDraggingCamera) {
|
||||||
|
const { x, y, prevPosition } = pointer
|
||||||
const { x, y, prevPosition } = pointer
|
const { scrollX, scrollY, zoom } = camera
|
||||||
const { scrollX, scrollY, zoom } = camera
|
camera.setScroll(scrollX - (x - prevPosition.x) / zoom, scrollY - (y - prevPosition.y) / zoom)
|
||||||
camera.setScroll(scrollX - (x - prevPosition.x) / zoom, scrollY - (y - prevPosition.y) / zoom)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
||||||
@ -37,12 +41,13 @@ export function useZoneEditorPointerHandlers(scene: Phaser.Scene, layer: Phaser.
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleZoom(pointer: Phaser.Input.Pointer) {
|
function handleZoom(pointer: Phaser.Input.Pointer) {
|
||||||
if (!(pointer.event instanceof WheelEvent) || !pointer.event.shiftKey) return
|
if (pointer.event instanceof WheelEvent && pointer.event.shiftKey) {
|
||||||
|
const deltaY = pointer.event.deltaY
|
||||||
const deltaY = pointer.event.deltaY
|
let zoomLevel = camera.zoom - deltaY * 0.005
|
||||||
let zoomLevel = camera.zoom - deltaY * 0.005
|
if (zoomLevel > 0 && zoomLevel < 3) {
|
||||||
if(zoomLevel <= 0 || zoomLevel >= 3) return
|
camera.setZoom(zoomLevel)
|
||||||
camera.setZoom(zoomLevel)
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setupPointerHandlers = () => {
|
const setupPointerHandlers = () => {
|
||||||
|
@ -1,32 +1,15 @@
|
|||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { watch } from 'vue'
|
|
||||||
import { useZoneStore } from '@/stores/zoneStore'
|
import { useZoneStore } from '@/stores/zoneStore'
|
||||||
|
|
||||||
export function useCameraControls(scene: Phaser.Scene): any {
|
export function useCameraControls(scene: Phaser.Scene) {
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const zoneStore = useZoneStore()
|
|
||||||
const camera = scene.cameras.main
|
const camera = scene.cameras.main
|
||||||
|
|
||||||
function onPointerDown(pointer: Phaser.Input.Pointer) {
|
const onPointerDown = () => gameStore.setPlayerDraggingCamera(true)
|
||||||
gameStore.setPlayerDraggingCamera(true)
|
const onPointerUp = () => gameStore.setPlayerDraggingCamera(false)
|
||||||
}
|
|
||||||
|
|
||||||
function onPointerUp() {
|
|
||||||
gameStore.setPlayerDraggingCamera(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => zoneStore.characterLoaded,
|
|
||||||
(characterLoaded) => {
|
|
||||||
if (!characterLoaded) return
|
|
||||||
// scene.cameras.main.startFollow(scene.children.getByName(gameStore.character!.name) as Phaser.GameObjects.Container);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_DOWN, onPointerDown)
|
scene.input.on(Phaser.Input.Events.POINTER_DOWN, onPointerDown)
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_UP, onPointerUp)
|
scene.input.on(Phaser.Input.Events.POINTER_UP, onPointerUp)
|
||||||
|
|
||||||
return {
|
return { camera }
|
||||||
camera
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,23 @@
|
|||||||
import { computed, type Ref, watch } from 'vue'
|
import { computed, type Ref, watch } from 'vue'
|
||||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||||
import { useGamePointerHandlers } from '@/composables/pointerHandlers/useGamePointerHandlers'
|
import { useGamePointerHandlers } from './pointerHandlers/useGamePointerHandlers'
|
||||||
import { useZoneEditorPointerHandlers } from '@/composables/pointerHandlers/useZoneEditorPointerHandlers'
|
import { useZoneEditorPointerHandlers } from './pointerHandlers/useZoneEditorPointerHandlers'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
|
||||||
|
|
||||||
export function usePointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
|
export function usePointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
|
||||||
const zoneEditorStore = useZoneEditorStore()
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
|
|
||||||
const gameHandlers = useGamePointerHandlers(scene, layer, waypoint, camera)
|
const gameHandlers = useGamePointerHandlers(scene, layer, waypoint, camera)
|
||||||
const zoneEditorHandlers = useZoneEditorPointerHandlers(scene, layer, waypoint, camera)
|
const zoneEditorHandlers = useZoneEditorPointerHandlers(scene, layer, waypoint, camera)
|
||||||
|
|
||||||
const currentHandlers = computed(() => (zoneEditorStore.active ? zoneEditorHandlers : gameHandlers))
|
const currentHandlers = computed(() => (zoneEditorStore.active ? zoneEditorHandlers : gameHandlers))
|
||||||
|
|
||||||
const setupPointerHandlers = () => {
|
const setupPointerHandlers = () => currentHandlers.value.setupPointerHandlers()
|
||||||
currentHandlers.value.setupPointerHandlers()
|
const cleanupPointerHandlers = () => currentHandlers.value.cleanupPointerHandlers()
|
||||||
}
|
|
||||||
|
|
||||||
const cleanupPointerHandlers = () => {
|
|
||||||
currentHandlers.value.cleanupPointerHandlers()
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => zoneEditorStore.active,
|
() => zoneEditorStore.active,
|
||||||
(newValue, oldValue) => {
|
() => {
|
||||||
if (newValue !== oldValue) {
|
cleanupPointerHandlers()
|
||||||
cleanupPointerHandlers()
|
setupPointerHandlers()
|
||||||
setupPointerHandlers()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
export default {
|
export default {
|
||||||
name: import.meta.env.VITE_NAME,
|
name: import.meta.env.VITE_NAME,
|
||||||
development: import.meta.env.VITE_DEVELOPMENT === 'true',
|
development: import.meta.env.VITE_DEVELOPMENT === 'true',
|
||||||
server_endpoint: import.meta.env.VITE_SERVER_ENDPOINT,
|
server_endpoint: import.meta.env.VITE_SERVER_ENDPOINT,
|
||||||
tile_size: {
|
tile_size: {
|
||||||
x: Number(import.meta.env.VITE_TILE_SIZE_X),
|
x: Number(import.meta.env.VITE_TILE_SIZE_X),
|
||||||
y: Number(import.meta.env.VITE_TILE_SIZE_Y),
|
y: Number(import.meta.env.VITE_TILE_SIZE_Y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-gray-300 relative">
|
<div class="bg-gray-900 relative">
|
||||||
<div class="absolute bg-[url('/assets/shapes/select-screen-bg-shape.svg')] bg-no-repeat bg-center w-full h-full"></div>
|
<div class="absolute bg-[url('/assets/shapes/select-screen-bg-shape.svg')] bg-no-repeat bg-center w-full h-full"></div>
|
||||||
<div class="ui-wrapper h-dvh flex flex-col justify-center items-center gap-20 px-10 sm:px-20">
|
<div class="ui-wrapper h-dvh flex flex-col justify-center items-center gap-20 px-10 sm:px-20">
|
||||||
<div class="filler"></div>
|
<div class="filler"></div>
|
||||||
@ -8,14 +8,16 @@
|
|||||||
<div
|
<div
|
||||||
v-for="character in characters"
|
v-for="character in characters"
|
||||||
:key="character.id"
|
:key="character.id"
|
||||||
class="group first:ml-auto last:mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 rounded-2xl relative bg-[url('/assets/shapes/character-select-shape.svg')] bg-no-repeat shadow-character"
|
class="group first:ml-auto last:mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 relative shadow-character"
|
||||||
:class="{ active: selected_character == character.id }"
|
:class="{ active: selected_character == character.id }"
|
||||||
>
|
>
|
||||||
|
<img src="/assets/ui-box-outer.svg" class="absolute w-full h-full max-lg:hidden" />
|
||||||
|
<img src="/assets/ui-box-inner.svg" class="absolute left-2 bottom-2 w-[calc(100%_-_16px)] h-[calc(100%_-_40px)] max-lg:hidden" />
|
||||||
<input class="opacity-0 h-full w-full absolute m-0 z-10" type="radio" :id="character.id" name="character" :value="character.id" v-model="selected_character" />
|
<input class="opacity-0 h-full w-full absolute m-0 z-10" type="radio" :id="character.id" name="character" :value="character.id" v-model="selected_character" />
|
||||||
<label class="font-bold absolute left-1/2 top-5 max-w-32 -translate-x-1/2 -translate-y-1/2 text-center text-ellipsis overflow-hidden whitespace-nowrap drop-shadow-text" :for="character.id">{{ character.name }}</label>
|
<label class="font-bold absolute left-1/2 top-4 max-w-32 -translate-x-1/2 -translate-y-1/2 text-center text-ellipsis overflow-hidden whitespace-nowrap drop-shadow-text" :for="character.id">{{ character.name }}</label>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="delete bg-red w-8 h-8 p-[3px] rounded-full absolute -right-4 top-0 -translate-y-1/2 z-10 border-2 border-solid border-white hover:bg-red-100"
|
class="delete bg-red w-8 h-8 p-[3px] rounded-full absolute -right-4 top-0 -translate-y-1/2 z-10 border-2 border-solid border-white hover:bg-red-300"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
deletingCharacter = character
|
deletingCharacter = character
|
||||||
@ -28,15 +30,15 @@
|
|||||||
<div class="sprite-container flex flex-col items-center m-auto">
|
<div class="sprite-container flex flex-col items-center m-auto">
|
||||||
<img class="drop-shadow-20" draggable="false" src="/assets/avatar/default/0.png" />
|
<img class="drop-shadow-20" draggable="false" src="/assets/avatar/default/0.png" />
|
||||||
</div>
|
</div>
|
||||||
<span class="absolute bottom-5 w-full text-center translate-y-1/2 z-10 drop-shadow-text">Lvl. {{ character.level }}</span>
|
<span class="absolute bottom-6 w-full text-center translate-y-1/2 z-10">Lvl. {{ character.level }}</span>
|
||||||
<div class="selected-character group-[.active]:max-w-[170px] absolute max-w-0 w-4/6 h-[3px] bg-white rounded-[3px] left-1/2 -bottom-4 -translate-x-1/2 transition-all ease-in-out duration-300"></div>
|
<div class="selected-character group-[.active]:max-w-[170px] absolute max-w-0 w-4/6 h-[3px] bg-gray-500 rounded-[3px] left-1/2 -bottom-4 -translate-x-1/2 transition-all ease-in-out duration-300"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="character new-character first:ml-auto mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 rounded-2xl relative bg-gray-50/50 bg-no-repeat shadow-character" v-if="characters.length < 4">
|
<div class="character new-character first:ml-auto mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 rounded-2xl relative bg-gray-500/50 bg-no-repeat shadow-character" v-if="characters.length < 4">
|
||||||
<button class="h-full w-full py-10 flex flex-col justify-between" @click="isModalOpen = true">
|
<button class="h-full w-full py-10 flex flex-col justify-between" @click="isModalOpen = true">
|
||||||
<div class="filler"></div>
|
<div class="filler"></div>
|
||||||
<img class="w-24 h-24 m-auto" draggable="false" src="/assets/icons/plus-icon.svg" />
|
<img class="w-24 h-24 m-auto" draggable="false" src="/assets/icons/plus-icon.svg" />
|
||||||
<span class="self-center text-base absolute bottom-5 w-full text-center translate-y-1/2 z-10 drop-shadow-text">Create new</span>
|
<span class="self-center text-base absolute bottom-5 w-full text-center translate-y-1/2 z-10">Create new</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -46,13 +48,13 @@
|
|||||||
|
|
||||||
<div class="button-wrapper flex gap-8" v-if="!isLoading">
|
<div class="button-wrapper flex gap-8" v-if="!isLoading">
|
||||||
<button
|
<button
|
||||||
class="btn-bordeaux py-2 pr-2.5 pl-8 min-w-24 relative rounded text-xl flex gap-4 items-center transition-all ease-in-out duration-200 hover:gap-5 disabled:bg-cyan/50 disabled:hover:bg-opacity-50 disabled:cursor-not-allowed disabled:hover:gap-[15px]"
|
class="btn-red py-2 pr-2.5 pl-8 min-w-24 relative rounded text-xl flex gap-4 items-center transition-all ease-in-out duration-200 hover:gap-5 disabled:bg-red/50 disabled:hover:bg-opacity-50 disabled:cursor-not-allowed disabled:hover:gap-[15px]"
|
||||||
@click.stop="gameStore.disconnectSocket()"
|
@click.stop="gameStore.disconnectSocket()"
|
||||||
>
|
>
|
||||||
<img class="h-8 drop-shadow-20 rotate-180" draggable="false" src="/assets/icons/arrow.svg" alt="Logout icon" />
|
<img class="h-8 drop-shadow-20 rotate-180" draggable="false" src="/assets/icons/arrow.svg" alt="Logout icon" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn-cyan py-2 pr-2.5 pl-8 min-w-24 relative rounded text-xl flex gap-4 items-center transition-all ease-in-out duration-200 hover:gap-5 disabled:bg-cyan/50 disabled:hover:bg-opacity-50 disabled:cursor-not-allowed disabled:hover:gap-[15px]"
|
class="btn-cyan py-2 px-2.5 pl-8 min-w-24 relative rounded text-xl flex gap-4 items-center transition-all ease-in-out duration-200 hover:gap-5 disabled:bg-cyan-800 disabled:hover:bg-opacity-50 disabled:cursor-not-allowed disabled:hover:gap-[15px]"
|
||||||
:disabled="!selected_character"
|
:disabled="!selected_character"
|
||||||
@click="select_character()"
|
@click="select_character()"
|
||||||
>
|
>
|
||||||
@ -66,7 +68,7 @@
|
|||||||
<!-- CREATE CHARACTER MODAL -->
|
<!-- CREATE CHARACTER MODAL -->
|
||||||
<Modal :isModalOpen="isModalOpen" @modal:close="isModalOpen = false">
|
<Modal :isModalOpen="isModalOpen" @modal:close="isModalOpen = false">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium">Create your character</h3>
|
<h3 class="m-0 font-medium text-gray-300">Create your character</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
@ -74,7 +76,7 @@
|
|||||||
<form method="post" @submit.prevent="create" class="inline">
|
<form method="post" @submit.prevent="create" class="inline">
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
<input class="input-cyan max-w-64" v-model="name" name="name" id="name" />
|
<input class="input-field max-w-64" v-model="name" name="name" id="name" />
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-cyan py-1.5 px-4 mr-5 min-w-24 inline-block" type="submit">CREATE</button>
|
<button class="btn-cyan py-1.5 px-4 mr-5 min-w-24 inline-block" type="submit">CREATE</button>
|
||||||
</form>
|
</form>
|
||||||
@ -86,7 +88,7 @@
|
|||||||
<!-- DELETE CHARACTER MODAL -->
|
<!-- DELETE CHARACTER MODAL -->
|
||||||
<ConfirmationModal v-if="deletingCharacter != null" :confirm-function="delete_character.bind(this, deletingCharacter.id)" :cancel-function="(() => (deletingCharacter = null)).bind(this)" confirm-button-text="Delete">
|
<ConfirmationModal v-if="deletingCharacter != null" :confirm-function="delete_character.bind(this, deletingCharacter.id)" :cancel-function="(() => (deletingCharacter = null)).bind(this)" confirm-button-text="Delete">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium">Deleting character</h3>
|
<h3 class="m-0 font-medium text-gray-300">Deleting character</h3>
|
||||||
</template>
|
</template>
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
You are about to delete <span class="font-extrabold">{{ deletingCharacter.name }}</span
|
You are about to delete <span class="font-extrabold">{{ deletingCharacter.name }}</span
|
||||||
|
@ -7,21 +7,16 @@
|
|||||||
<Game :config="gameConfig" @create="createGame">
|
<Game :config="gameConfig" @create="createGame">
|
||||||
<Scene name="main" @preload="preloadScene" @create="createScene">
|
<Scene name="main" @preload="preloadScene" @create="createScene">
|
||||||
<div v-if="isLoaded">
|
<div v-if="isLoaded">
|
||||||
<Inventory />
|
<Menu />
|
||||||
<div class="fixed inset-x-0 top-0 flex justify-start items-end p-10 pointer-events-none">
|
<Hud />
|
||||||
<div class="pointer-events-auto">
|
<Keybindings />
|
||||||
<Hud />
|
<Minimap />
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Zone />
|
<Zone />
|
||||||
<div class="fixed inset-x-0 bottom-0 flex justify-between gap-5 items-end py-10 px-5 xxs:p-10 pointer-events-none max-md:flex-wrap max-md:flex-col-reverse">
|
<Chat />
|
||||||
<div class="pointer-events-auto w-full">
|
<ExpBar />
|
||||||
<Chat />
|
|
||||||
</div>
|
<Inventory />
|
||||||
<div class="pointer-events-auto max-xs:m-auto mr-auto">
|
<Effects />
|
||||||
<Menubar />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Scene>
|
</Scene>
|
||||||
</Game>
|
</Game>
|
||||||
@ -29,7 +24,7 @@
|
|||||||
<div v-if="zoneEditorStore.active">
|
<div v-if="zoneEditorStore.active">
|
||||||
<Game :config="gameConfig" @create="createGame">
|
<Game :config="gameConfig" @create="createGame">
|
||||||
<Scene name="main" @preload="preloadScene" @create="createScene">
|
<Scene name="main" @preload="preloadScene" @create="createScene">
|
||||||
<ZoneEditor v-if="isLoaded" :key="JSON.stringify(zoneEditorStore.zone)" />
|
<ZoneEditor v-if="isLoaded" :key="JSON.stringify(`${zoneEditorStore.zone?.id}_${zoneEditorStore.zone?.createdAt}_${zoneEditorStore.zone?.updatedAt}`)" />
|
||||||
</Scene>
|
</Scene>
|
||||||
</Game>
|
</Game>
|
||||||
</div>
|
</div>
|
||||||
@ -42,28 +37,31 @@ import { ref, onBeforeUnmount } from 'vue'
|
|||||||
import { Game, Scene } from 'phavuer'
|
import { Game, Scene } from 'phavuer'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||||
|
import Menu from '@/components/gui/Menu.vue'
|
||||||
|
import ExpBar from '@/components/gui/ExpBar.vue'
|
||||||
import Hud from '@/components/gui/Hud.vue'
|
import Hud from '@/components/gui/Hud.vue'
|
||||||
import Zone from '@/components/zone/Zone.vue'
|
import Zone from '@/components/zone/Zone.vue'
|
||||||
|
import Keybindings from '@/components/gui/Keybindings.vue'
|
||||||
import Chat from '@/components/gui/Chat.vue'
|
import Chat from '@/components/gui/Chat.vue'
|
||||||
import Menubar from '@/components/gui/Menu.vue'
|
|
||||||
import GmTools from '@/components/gameMaster/GmTools.vue'
|
import GmTools from '@/components/gameMaster/GmTools.vue'
|
||||||
import ZoneEditor from '@/components/gameMaster/zoneEditor/ZoneEditor.vue'
|
import ZoneEditor from '@/components/gameMaster/zoneEditor/ZoneEditor.vue'
|
||||||
import GmPanel from '@/components/gameMaster/GmPanel.vue'
|
import GmPanel from '@/components/gameMaster/GmPanel.vue'
|
||||||
import Inventory from '@/components/gui/UserPanel.vue'
|
import Inventory from '@/components/gui/UserPanel.vue'
|
||||||
|
import Effects from '@/components/Effects.vue'
|
||||||
import { loadAssets } from '@/composables/zoneComposable'
|
import { loadAssets } from '@/composables/zoneComposable'
|
||||||
|
import Minimap from '@/components/gui/Minimap.vue'
|
||||||
|
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const zoneEditorStore = useZoneEditorStore()
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
const isLoaded = ref(false)
|
const isLoaded = ref(false)
|
||||||
|
|
||||||
const gameConfig = {
|
const gameConfig = {
|
||||||
name: 'New Quest',
|
name: 'Sylvan Quest',
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
type: Phaser.AUTO, // AUTO, CANVAS, WEBGL, HEADLESS
|
type: Phaser.AUTO, // AUTO, CANVAS, WEBGL, HEADLESS
|
||||||
mode: Phaser.Scale.RESIZE,
|
resolution: 5
|
||||||
resolution: 3,
|
|
||||||
render: { pixelArt: true, antialias: false }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const createGame = (game: Phaser.Game) => {
|
const createGame = (game: Phaser.Game) => {
|
||||||
@ -130,10 +128,6 @@ const preloadScene = async (scene: Phaser.Scene) => {
|
|||||||
scene.load.image('blank_tile', '/assets/zone/blank_tile.png')
|
scene.load.image('blank_tile', '/assets/zone/blank_tile.png')
|
||||||
scene.load.image('blank_object', '/assets/zone/blank_tile.png')
|
scene.load.image('blank_object', '/assets/zone/blank_tile.png')
|
||||||
scene.load.image('waypoint', '/assets/waypoint.png')
|
scene.load.image('waypoint', '/assets/waypoint.png')
|
||||||
scene.textures.addBase64(
|
|
||||||
'character',
|
|
||||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAABeCAYAAAAwnXTzAAAHWUlEQVR4nLVaQUhcRxj+DCuILruHXdbnYkBimkdFdouwFBdWKCWHHkoChragmAo9pKG3ltQGWrGHkEpzS0t7Mgn2EiIYcvQiLurBIF0RYYtbFiK6Lhqyiy4BBXt4+8/OzHvz3ryN/eCx782bN9/8//z/P//MbMvHP/wJXXTMTJ45lR+PT7XothHwQzQ8OIBUpt/2fmLaeq9D7EnYMTN5Njw4ALM3iFC8B0Y6ifZYWKjzKJ1EaSWHienJMy/SCzpkqUy/QNZqJISrPRaGkU7i/p0xpdq1CAHA7A0CAIx0UlnHD6mSkKQLxXu8+iTArWOuhCrUyhWclDZc67hJqWWlTqTt2LCV6aApQj8EMjxVWt0tAgBOD8tNEcjwlDBfOAIKm/jE7NJq8Mvr374b4affjwAAXvzyF7uXG300/4Ddz++8wkj3RRwr2vNUaWklBwBIZfqZWud3XmF+5xU66vd+oCQ8Hp9qmVtdx1p2k5Ee5PdweljGm5dPWb2R7ovCd29ePsWPd8aUhFpWupbdZLH0IL+HqGk1/BunSjm+quCqUpISqBtPHQf5PUZCF8HLXTzHkCddy26y8tPDMmrliu06PSxjYvqJcqrSDm0yKY2nfJH0KmhHmuHBAUZKSDnUc5POFyGRAg1p5Q4A3rO+L0JKL1KZflR3i8gXjpj1eklGaHFLovhchhpWobpbxL3ZJfasIneUkCciEBmlGbVyhQUEI52EgSTux3uY5HMzzvmNjZBmegCOUhEJNQwA5m6R1QvFe5CKW3WdSAVCPmkCgEvXh5gkcupgIInQSg5VjozqlFZyVi60Kovj4IeUNF26PsSSI0I4kWEXJU2yqsOJDMxb3yAU78Hw4IAt1WCEfNJkpJNoNRIArIwsWp8L+VyG3vOgMqpHnedhG8Oo2SVIxZM45TI8qC7FU0v6JaGOoFKzN4hAJCb0tFausDBGjfFxk1BaybFyoJGayGoVCGWLlBtVxU1qvLSSw+lhGaWVHPKFI8cc1aZSUiep5yC/h7XsJlIAG0sqB7gZpFD/zW5ibnUd9++MoT0WtlmrMrSRdGv1BlKZfmEmICLZMPh508mwAoDo7ABQ2cgyKeZW1zE8OCDMhYAVT6NSJneQ32tIqoBSQpKOwEedqNmFQCQGACzMWeUApI7JlsoIzd4gomYXUyUvXePDBhkt2wAgbNQtmiMiQ5LBrFS2UKcPeMnk8XEaL6dFj+AW1NhBfk8YfBmqDI0vzxeOUCtXYKSTgi8yQjnC6OKktGGLMG4QJHRSixcZRReybN5NaCrjy3wvSAF/SzVZa4yQxo8wt7ou5KR+iem7ViMhGKRNwtPDstKk6T0gWiCpToZTO4ywGYNxa5hwUtoQgrgt0ni5BFBP87nnKzeusXIvMEK/FqrTeL5whCvliqA9bSut7hY91w0q1MoV5vwX5JnCrbdEqiLmh0O2cPJF2xhWd4vMJQDgqK8PjytvrZfrb4H1A9wMtwHcIpW+yxeOrLp9faBk9J9nz3HlxjV8cHsMmF2yCPlI4GQwV69eFZ4fLywIHZDrLtB7au/Zc6R+/rUhoc5+WmdnJ7sfHR1V1tvf3xeeP39wG68Xl7H203cNQn7mdsqYebLLly8DALq7u21kOzs7trLXi8vo/OJrtG1kgdklu5W6SetGRuVUh7DwIoeT0gbCiQwAzi0oVMnz17uALPXv35+w9m0ShhMZtuOkgqy6xcVFdr+8vGwjvTe7hM/SX+F4fKolAFj+E4jEEDaA2ZFbglsAliF0dnZie3ubqYxIt7e3Gen+/j4WFhYQ3NrC8YciKd0HAMuHDFgBViYLbm2BjFx2D75D5Ao3w22Yc9FOALB85dJhWbnTezPcBoDzP0UdszfoGfgDx+NTLXMzk2epTD9C77+nrGj2BvEw3jizoJyVUslGYrzquBAlMKORM2uZLBTvQdTsYhd/YEI5rQ4uAA3zdUsZqMFAJIZAJCYQ+Nn5F9xClSoQKOMOJzIIRGKOK1xtwuPxqRbV5g4vQauRUBqXnIi5EhKpfl/9k9kIdeB1SHLuhEDzZxZNEXqReTm+L0J5g6EZ+D4KciNqj4VtsViGloRyiuhkkfyK2A2+JSTnr0Ecr3/nl2CkvY3JFyG/tpe3wKq7RVSfFT3b8GU0tL1FC1GnTQfPTvshBKzEFnA2f6fNPBm+/VCWxG8A9yWhMOdJO06+5kMv0OTLuwMvmfzODdoS8u4ANLdsA5oM3u+CcyM8l/PD/wPnRqi7R+CL0K1R3Uzg3CTUzQLOhZCOGXR2ObT+wENwU5vuloq2hHTI7JVa3B0dQsfM5JlqQav9jyEi9UIo3oO7o0PKVbRWaNMJzHydqNmFUHwPZm8Q96QzRF+zhXz0I4PPCAKRrLXInRXnx6anJ1re5QtHMFGsvwdqANph7VrQ3oxvQuv/F43zishHwzBvAak/HmJi+kl9Aeo807seyTqBl8BIW3PeSWlDiDrn9ldBWo4PYwAmrKyskWIsue4Eq+B6jk9w2yTyu8T7D0vv92u9uVoPAAAAAElFTkSuQmCC'
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the assets into the Phaser scene
|
* Load the assets into the Phaser scene
|
||||||
@ -163,3 +157,11 @@ onBeforeUnmount(() => {
|
|||||||
gameStore.disconnectSocket()
|
gameStore.disconnectSocket()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
canvas {
|
||||||
|
image-rendering: -moz-crisp-edges;
|
||||||
|
image-rendering: -webkit-crisp-edges;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,28 +1,66 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-gray-300">
|
<div class="relative max-lg:h-dvh">
|
||||||
<div class="absolute bg-[url('/assets/shapes/select-screen-bg-shape.svg')] bg-no-repeat bg-center w-full h-full z-10 pointer-events-none"></div>
|
<div class="lg:bg-gradient-to-r bg-gradient-to-b from-gray-900 to-transparent w-full lg:w-1/2 h-[35dvh] lg:h-dvh absolute right-0 max-lg:bottom-0 lg:top-0 z-10"></div>
|
||||||
<div class="z-20 w-full h-dvh flex items-center justify-between flex-col relative">
|
<div class="bg-[url('/assets/login/login-bg.png')] w-full lg:w-1/2 h-[35dvh] lg:h-dvh absolute right-0 max-lg:bottom-0 lg:top-0 bg-no-repeat bg-cover bg-center"></div>
|
||||||
<div class="filler"></div>
|
<div class="bg-gray-900 z-20 w-full lg:w-1/2 h-[65dvh] lg:h-dvh relative">
|
||||||
<h1 class="mt-28 text-center text-6xl">NEW QUEST</h1>
|
<div class="h-dvh flex items-center lg:justify-center flex-col px-8 max-lg:pt-20">
|
||||||
<form @submit.prevent="loginFunc">
|
<img src="/assets/login/sq-logo-v1.svg" class="mb-10" />
|
||||||
<div class="my-20 mx-0 w-full flex flex-col gap-6">
|
<div class="relative">
|
||||||
<div class="w-full grid gap-4">
|
<img src="/assets/ui-box-outer.svg" class="absolute w-full h-full" />
|
||||||
<div class="flex flex-col bg-white/50 rounded-[3px] border border-solid border-gray-50 sm:min-w-[500px] sm:w-unset w-full my-0 mx-auto">
|
<img src="/assets/ui-box-inner.svg" class="absolute left-2 top-2 w-[calc(100%_-_16px)] h-[calc(100%_-_16px)] max-lg:hidden" />
|
||||||
<label class="text-black bg-white/50 p-1 text-sm rounded-t-[3px]" for="username">Username</label>
|
|
||||||
<input class="p-1 text-sm focus-visible:outline-none" id="username" v-model="username" type="text" name="username" required autofocus />
|
<!-- Login Form -->
|
||||||
|
<form v-show="switchForm === 'login'" @submit.prevent="loginFunc" class="relative px-6 py-11">
|
||||||
|
<div class="flex flex-col gap-5 p-2 mb-8 relative">
|
||||||
|
<div class="w-full grid gap-3 relative">
|
||||||
|
<input class="input-field xs:min-w-[350px] min-w-64" id="username-login" v-model="username" type="text" name="username" placeholder="Username" required autofocus />
|
||||||
|
<div class="relative">
|
||||||
|
<input class="input-field xs:min-w-[350px] min-w-64" id="password-login" v-model="password" :type="showPassword ? 'text' : 'password'" name="password" placeholder="Password" required />
|
||||||
|
<button type="button" @click.prevent="showPassword = !showPassword" :class="{'eye-open': showPassword}" class="bg-[url('/assets/icons/eye.svg')] p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-no-repeat"></button>
|
||||||
|
</div>
|
||||||
|
<span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span>
|
||||||
|
</div>
|
||||||
|
<button class="inline-flex self-end p-0 text-cyan-300 text-base">Forgot password?</button>
|
||||||
|
<button class="btn-cyan px-0 xs:w-full" type="submit">Play now</button>
|
||||||
|
|
||||||
|
<!-- Divider shape -->
|
||||||
|
<div class="absolute w-40 h-0.5 -bottom-8 left-1/2 -translate-x-1/2 flex justify-between">
|
||||||
|
<div class="w-0.5 h-full bg-gray-300"></div>
|
||||||
|
<div class="w-36 h-full bg-gray-300"></div>
|
||||||
|
<div class="w-0.5 h-full bg-gray-300"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col bg-white/50 rounded-[3px] border border-solid border-gray-50 sm:min-w-[500px] sm:w-unset w-full my-0 mx-auto">
|
<div class="pt-8">
|
||||||
<label class="text-black bg-white/50 p-1 text-sm rounded-t-[3px]" for="password">Password</label>
|
<p class="m-0 text-center">Don't have an account? <button class="text-cyan-300 text-base p-0" @click.prevent="switchForm = 'register'">Sign up</button></p>
|
||||||
<input class="p-1 text-sm focus-visible:outline-none" id="password" v-model="password" type="password" name="password" required />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
<div class="flex justify-center sm:gap-4 gap-2">
|
|
||||||
<button class="btn-cyan py-2 px-0 min-w-24" type="submit"><span class="m-auto">PLAY</span></button>
|
<!-- Register Form -->
|
||||||
<button class="btn-cyan py-2 px-0 min-w-24" type="button" @click.prevent="registerFunc"><span class="m-auto">REGISTER</span></button>
|
<form v-show="switchForm === 'register'" @submit.prevent="registerFunc" class="relative px-6 py-11">
|
||||||
<button class="btn-cyan py-2 px-0 min-w-24"><span class="m-auto">CREDITS</span></button>
|
<div class="flex flex-col gap-5 p-2 mb-8 relative">
|
||||||
</div>
|
<div class="w-full grid gap-3 relative">
|
||||||
|
<input class="input-field xs:min-w-[350px] min-w-64" id="username-register" v-model="username" type="text" name="username" placeholder="Username" required autofocus />
|
||||||
|
<div class="relative">
|
||||||
|
<input class="input-field xs:min-w-[350px] min-w-64" id="password-register" v-model="password" :type="showPassword ? 'text' : 'password'" name="password" placeholder="Password" required />
|
||||||
|
<button type="button" @click.prevent="showPassword = !showPassword" :class="{'eye-open': showPassword}" class="bg-[url('/assets/icons/eye.svg')] p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-no-repeat"></button>
|
||||||
|
</div>
|
||||||
|
<span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn-cyan xs:w-full" type="submit">Register now</button>
|
||||||
|
|
||||||
|
<!-- Divider shape -->
|
||||||
|
<div class="absolute w-40 h-0.5 -bottom-8 left-1/2 -translate-x-1/2 flex justify-between">
|
||||||
|
<div class="w-0.5 h-full bg-gray-300"></div>
|
||||||
|
<div class="w-36 h-full bg-gray-300"></div>
|
||||||
|
<div class="w-0.5 h-full bg-gray-300"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pt-8">
|
||||||
|
<p class="m-0 text-center">Already have an account? <button class="text-cyan-300 text-base p-0" @click.prevent="switchForm = 'login'">Log in</button></p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -36,6 +74,9 @@ import { useCookies } from '@vueuse/integrations/useCookies'
|
|||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const username = ref('')
|
const username = ref('')
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
|
const switchForm = ref('login')
|
||||||
|
const loginError = ref('')
|
||||||
|
const showPassword = ref(false)
|
||||||
|
|
||||||
// automatic login because of development
|
// automatic login because of development
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@ -49,7 +90,7 @@ onMounted(async () => {
|
|||||||
async function loginFunc() {
|
async function loginFunc() {
|
||||||
// check if username and password are valid
|
// check if username and password are valid
|
||||||
if (username.value === '' || password.value === '') {
|
if (username.value === '' || password.value === '') {
|
||||||
gameStore.addNotification({ message: 'Please enter a valid username and password' })
|
loginError.value = 'Please enter a valid username and password'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,10 +98,9 @@ async function loginFunc() {
|
|||||||
const response = await login(username.value, password.value)
|
const response = await login(username.value, password.value)
|
||||||
|
|
||||||
if (response.success === undefined) {
|
if (response.success === undefined) {
|
||||||
gameStore.addNotification({ message: response.error })
|
loginError.value = response.error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.setToken(response.token)
|
gameStore.setToken(response.token)
|
||||||
gameStore.initConnection()
|
gameStore.initConnection()
|
||||||
return true // Indicate success
|
return true // Indicate success
|
||||||
@ -69,7 +109,7 @@ async function loginFunc() {
|
|||||||
async function registerFunc() {
|
async function registerFunc() {
|
||||||
// check if username and password are valid
|
// check if username and password are valid
|
||||||
if (username.value === '' || password.value === '') {
|
if (username.value === '' || password.value === '') {
|
||||||
gameStore.addNotification({ message: 'Please enter a valid username and password' })
|
loginError.value = 'Please enter a valid username and password'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,13 +117,14 @@ async function registerFunc() {
|
|||||||
const response = await register(username.value, password.value)
|
const response = await register(username.value, password.value)
|
||||||
|
|
||||||
if (response.success === undefined) {
|
if (response.success === undefined) {
|
||||||
gameStore.addNotification({ message: response.error })
|
loginError.value = response.error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginSuccess = await loginFunc()
|
const loginSuccess = await loginFunc()
|
||||||
if (!loginSuccess) {
|
if (!loginSuccess) {
|
||||||
gameStore.addNotification({ message: 'Login after registration failed. Please try logging in manually.' })
|
loginError.value = 'Login after registration failed. Please try logging in manually.'
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1 +0,0 @@
|
|||||||
<template></template>
|
|
@ -18,6 +18,7 @@ export async function login(username: string, password: string, gameStore = useG
|
|||||||
const response = await axios.post(`${config.server_endpoint}/login`, { username, password })
|
const response = await axios.post(`${config.server_endpoint}/login`, { username, password })
|
||||||
useCookies().set('token', response.data.token as string, {
|
useCookies().set('token', response.data.token as string, {
|
||||||
// for whole domain
|
// for whole domain
|
||||||
|
// @TODO : #190
|
||||||
domain: window.location.hostname.split('.').slice(-2).join('.')
|
domain: window.location.hostname.split('.').slice(-2).join('.')
|
||||||
})
|
})
|
||||||
return { success: true, token: response.data.token }
|
return { success: true, token: response.data.token }
|
||||||
|
@ -1,23 +1,36 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { io, Socket } from 'socket.io-client'
|
import { io, Socket } from 'socket.io-client'
|
||||||
import type { Asset, Character, Notification, User } from '@/types'
|
import type { Asset, Character, Notification, User, WorldSettings } from '@/types'
|
||||||
import config from '@/config'
|
import config from '@/config'
|
||||||
import { useCookies } from '@vueuse/integrations/useCookies'
|
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||||
|
|
||||||
export const useGameStore = defineStore('game', {
|
export const useGameStore = defineStore('game', {
|
||||||
state: () => ({
|
state: () => {
|
||||||
notifications: [] as Notification[],
|
return {
|
||||||
assets: [] as Asset[],
|
loginMessage: null as string | null,
|
||||||
token: '' as string | null,
|
notifications: [] as Notification[],
|
||||||
connection: null as Socket | null,
|
assets: [] as Asset[],
|
||||||
user: null as User | null,
|
token: '' as string | null,
|
||||||
character: null as Character | null,
|
connection: null as Socket | null,
|
||||||
isGmPanelOpen: false,
|
user: null as User | null,
|
||||||
isPlayerDraggingCamera: false,
|
character: null as Character | null,
|
||||||
isCameraFollowingCharacter: false,
|
isPlayerDraggingCamera: false,
|
||||||
isChatOpen: false,
|
world: {
|
||||||
isUserPanelOpen: false
|
date: new Date(),
|
||||||
}),
|
isRainEnabled: false,
|
||||||
|
isFogEnabled: false,
|
||||||
|
fogDensity: 0.5
|
||||||
|
} as WorldSettings,
|
||||||
|
gameSettings: {
|
||||||
|
isCameraFollowingCharacter: false
|
||||||
|
},
|
||||||
|
uiSettings: {
|
||||||
|
isChatOpen: false,
|
||||||
|
isUserPanelOpen: false,
|
||||||
|
isGmPanelOpen: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getNotifications: (state: any) => state.notifications,
|
getNotifications: (state: any) => state.notifications,
|
||||||
getAssetByKey: (state) => {
|
getAssetByKey: (state) => {
|
||||||
@ -92,7 +105,7 @@ export const useGameStore = defineStore('game', {
|
|||||||
this.character = character
|
this.character = character
|
||||||
},
|
},
|
||||||
toggleGmPanel() {
|
toggleGmPanel() {
|
||||||
this.isGmPanelOpen = !this.isGmPanelOpen
|
this.uiSettings.isGmPanelOpen = !this.uiSettings.isGmPanelOpen
|
||||||
},
|
},
|
||||||
togglePlayerDraggingCamera() {
|
togglePlayerDraggingCamera() {
|
||||||
this.isPlayerDraggingCamera = !this.isPlayerDraggingCamera
|
this.isPlayerDraggingCamera = !this.isPlayerDraggingCamera
|
||||||
@ -101,16 +114,16 @@ export const useGameStore = defineStore('game', {
|
|||||||
this.isPlayerDraggingCamera = moving
|
this.isPlayerDraggingCamera = moving
|
||||||
},
|
},
|
||||||
toggleCameraFollowingCharacter() {
|
toggleCameraFollowingCharacter() {
|
||||||
this.isCameraFollowingCharacter = !this.isCameraFollowingCharacter
|
this.gameSettings.isCameraFollowingCharacter = !this.gameSettings.isCameraFollowingCharacter
|
||||||
},
|
},
|
||||||
setCameraFollowingCharacter(following: boolean) {
|
setCameraFollowingCharacter(following: boolean) {
|
||||||
this.isCameraFollowingCharacter = following
|
this.gameSettings.isCameraFollowingCharacter = following
|
||||||
},
|
},
|
||||||
toggleChat() {
|
toggleChat() {
|
||||||
this.isChatOpen = !this.isChatOpen
|
this.uiSettings.isChatOpen = !this.uiSettings.isChatOpen
|
||||||
},
|
},
|
||||||
toggleUserPanel() {
|
toggleUserPanel() {
|
||||||
this.isUserPanelOpen = !this.isUserPanelOpen
|
this.uiSettings.isUserPanelOpen = !this.uiSettings.isUserPanelOpen
|
||||||
},
|
},
|
||||||
initConnection() {
|
initConnection() {
|
||||||
this.connection = io(config.server_endpoint, {
|
this.connection = io(config.server_endpoint, {
|
||||||
@ -143,6 +156,7 @@ export const useGameStore = defineStore('game', {
|
|||||||
|
|
||||||
useCookies().remove('token', {
|
useCookies().remove('token', {
|
||||||
// for whole domain
|
// for whole domain
|
||||||
|
// @TODO : #190
|
||||||
domain: window.location.hostname.split('.').slice(-2).join('.')
|
domain: window.location.hostname.split('.').slice(-2).join('.')
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -151,10 +165,16 @@ export const useGameStore = defineStore('game', {
|
|||||||
this.token = null
|
this.token = null
|
||||||
this.user = null
|
this.user = null
|
||||||
this.character = null
|
this.character = null
|
||||||
this.isGmPanelOpen = false
|
this.uiSettings.isGmPanelOpen = false
|
||||||
this.isPlayerDraggingCamera = false
|
this.isPlayerDraggingCamera = false
|
||||||
this.isChatOpen = false
|
this.gameSettings.isCameraFollowingCharacter = false
|
||||||
this.isUserPanelOpen = false
|
this.uiSettings.isChatOpen = false
|
||||||
|
this.uiSettings.isUserPanelOpen = false
|
||||||
|
|
||||||
|
this.world.date = new Date()
|
||||||
|
this.world.isRainEnabled = false
|
||||||
|
this.world.isFogEnabled = false
|
||||||
|
this.world.fogDensity = 0.5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import type { Zone, Object, Tile, ZoneObject } from '@/types'
|
import type { Zone, Object, Tile, ZoneObject, ZoneEffects } from '@/types'
|
||||||
|
|
||||||
type TeleportSettings = {
|
export type TeleportSettings = {
|
||||||
toZoneId: number
|
toZoneId: number
|
||||||
toPositionX: number
|
toPositionX: number
|
||||||
toPositionY: number
|
toPositionY: number
|
||||||
@ -10,37 +10,40 @@ type TeleportSettings = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useZoneEditorStore = defineStore('zoneEditor', {
|
export const useZoneEditorStore = defineStore('zoneEditor', {
|
||||||
state: () => ({
|
state: () => {
|
||||||
active: false,
|
return {
|
||||||
zone: null as Zone | null,
|
active: false,
|
||||||
tool: 'move',
|
zone: null as Zone | null,
|
||||||
drawMode: 'tile',
|
tool: 'move',
|
||||||
eraserMode: 'tile',
|
drawMode: 'tile',
|
||||||
zoneList: [] as Zone[],
|
eraserMode: 'tile',
|
||||||
tileList: [] as Tile[],
|
zoneList: [] as Zone[],
|
||||||
objectList: [] as Object[],
|
tileList: [] as Tile[],
|
||||||
selectedTile: null as Tile | null,
|
objectList: [] as Object[],
|
||||||
selectedObject: null as Object | null,
|
selectedTile: null as Tile | null,
|
||||||
selectedZoneObject: null as ZoneObject | null,
|
selectedObject: null as Object | null,
|
||||||
objectDepth: 0,
|
selectedZoneObject: null as ZoneObject | null,
|
||||||
isTileListModalShown: false,
|
objectDepth: 0,
|
||||||
isObjectListModalShown: false,
|
isTileListModalShown: false,
|
||||||
isZoneListModalShown: false,
|
isObjectListModalShown: false,
|
||||||
isCreateZoneModalShown: false,
|
isZoneListModalShown: false,
|
||||||
isSettingsModalShown: false,
|
isCreateZoneModalShown: false,
|
||||||
zoneSettings: {
|
isSettingsModalShown: false,
|
||||||
name: '',
|
zoneSettings: {
|
||||||
width: 0,
|
name: '',
|
||||||
height: 0,
|
width: 0,
|
||||||
pvp: false
|
height: 0,
|
||||||
},
|
pvp: false,
|
||||||
teleportSettings: {
|
effects: [] as ZoneEffects[]
|
||||||
toZoneId: 0,
|
},
|
||||||
toPositionX: 0,
|
teleportSettings: {
|
||||||
toPositionY: 0,
|
toZoneId: 0,
|
||||||
toRotation: 0
|
toPositionX: 0,
|
||||||
|
toPositionY: 0,
|
||||||
|
toRotation: 0
|
||||||
|
} as TeleportSettings
|
||||||
}
|
}
|
||||||
}),
|
},
|
||||||
actions: {
|
actions: {
|
||||||
toggleActive() {
|
toggleActive() {
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
@ -64,6 +67,10 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
|
|||||||
if (!this.zone) return
|
if (!this.zone) return
|
||||||
this.zone.pvp = pvp
|
this.zone.pvp = pvp
|
||||||
},
|
},
|
||||||
|
setZoneEffects(zoneEffects: ZoneEffects) {
|
||||||
|
if (!this.zone) return
|
||||||
|
this.zone.zoneEffects = zoneEffects
|
||||||
|
},
|
||||||
setTool(tool: string) {
|
setTool(tool: string) {
|
||||||
this.tool = tool
|
this.tool = tool
|
||||||
},
|
},
|
||||||
|
@ -2,11 +2,13 @@ import { defineStore } from 'pinia'
|
|||||||
import type { ExtendedCharacter, Zone } from '@/types'
|
import type { ExtendedCharacter, Zone } from '@/types'
|
||||||
|
|
||||||
export const useZoneStore = defineStore('zone', {
|
export const useZoneStore = defineStore('zone', {
|
||||||
state: () => ({
|
state: () => {
|
||||||
zone: null as Zone | null,
|
return {
|
||||||
characters: [] as ExtendedCharacter[],
|
zone: null as Zone | null,
|
||||||
characterLoaded: false
|
characters: [] as ExtendedCharacter[],
|
||||||
}),
|
characterLoaded: false
|
||||||
|
}
|
||||||
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getCharacterById: (state) => {
|
getCharacterById: (state) => {
|
||||||
return (id: number) => state.characters.find((char) => char.id === id)
|
return (id: number) => state.characters.find((char) => char.id === id)
|
||||||
|
17
src/types.ts
@ -53,6 +53,7 @@ export type Zone = {
|
|||||||
height: number
|
height: number
|
||||||
tiles: any | null
|
tiles: any | null
|
||||||
pvp: boolean
|
pvp: boolean
|
||||||
|
zoneEffects: ZoneEffects
|
||||||
zoneEventTiles: ZoneEventTile[]
|
zoneEventTiles: ZoneEventTile[]
|
||||||
zoneObjects: ZoneObject[]
|
zoneObjects: ZoneObject[]
|
||||||
characters: Character[]
|
characters: Character[]
|
||||||
@ -61,6 +62,14 @@ export type Zone = {
|
|||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ZoneEffects = {
|
||||||
|
id: string
|
||||||
|
zoneId: number
|
||||||
|
zone: Zone
|
||||||
|
effect: string
|
||||||
|
strength: number
|
||||||
|
}
|
||||||
|
|
||||||
export type ZoneObject = {
|
export type ZoneObject = {
|
||||||
id: string
|
id: string
|
||||||
zoneId: number
|
zoneId: number
|
||||||
@ -68,6 +77,7 @@ export type ZoneObject = {
|
|||||||
objectId: string
|
objectId: string
|
||||||
object: Object
|
object: Object
|
||||||
depth: number
|
depth: number
|
||||||
|
isRotated: boolean
|
||||||
positionX: number
|
positionX: number
|
||||||
positionY: number
|
positionY: number
|
||||||
}
|
}
|
||||||
@ -205,3 +215,10 @@ export type ChatMessage = {
|
|||||||
character: Character
|
character: Character
|
||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WorldSettings = {
|
||||||
|
date: Date
|
||||||
|
isRainEnabled: boolean
|
||||||
|
isFogEnabled: boolean
|
||||||
|
fogDensity: number
|
||||||
|
}
|
@ -4,9 +4,12 @@ export default {
|
|||||||
theme: {
|
theme: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
'titles': ['Poppins', 'serif'],
|
'titles': ['Poppins', 'serif'],
|
||||||
'default': ['Inter', 'serif']
|
'default': ['Quicksand', 'serif'],
|
||||||
|
'ui': ['Upheaval', 'serif'],
|
||||||
},
|
},
|
||||||
backgroundSize: {
|
backgroundSize: {
|
||||||
|
'cover': 'cover',
|
||||||
|
'contain': 'contain',
|
||||||
'30px': '30px'
|
'30px': '30px'
|
||||||
},
|
},
|
||||||
screens: {
|
screens: {
|
||||||
@ -41,46 +44,60 @@ export default {
|
|||||||
dropShadow: {
|
dropShadow: {
|
||||||
'default': '0 3px 6px rgb(0, 0, 0)',
|
'default': '0 3px 6px rgb(0, 0, 0)',
|
||||||
'text': '1px 1px 5px rgba(0, 0, 0, 0.25)',
|
'text': '1px 1px 5px rgba(0, 0, 0, 0.25)',
|
||||||
|
'pixel': '2px 2px 0px rgb(77, 77, 77)',
|
||||||
|
'pixel-black': '2px 2px 0px rgb(0, 0, 0)',
|
||||||
'20': '0 3px 6px rgba(0, 0, 0, 0.2)'
|
'20': '0 3px 6px rgba(0, 0, 0, 0.2)'
|
||||||
},
|
},
|
||||||
boxShadow: {
|
boxShadow: {
|
||||||
'character': '0 4px 30px rgba(0, 0, 0, 0.1)',
|
'character': '0 4px 30px rgba(0, 0, 0, 0.1)',
|
||||||
|
'inner': 'inset 0 0 12px 8px rgb(0, 0, 0)',
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
red: {
|
cyan: {
|
||||||
DEFAULT: '#d50000',
|
DEFAULT: '#0D6d69',
|
||||||
50: '#d50000',
|
50: '#f3faf8',
|
||||||
100: '#b30000'
|
100: '#D7F0EC',
|
||||||
|
200: '#b1dfd9',
|
||||||
|
300: '#80c8c1',
|
||||||
|
600: '#0D6D69',
|
||||||
|
800: '#244b4c',
|
||||||
|
900: '#204040',
|
||||||
|
950: '#0f2324'
|
||||||
},
|
},
|
||||||
bordeaux: {
|
red: {
|
||||||
DEFAULT: '#800020',
|
DEFAULT: '#e15970',
|
||||||
50: '#cc0033',
|
100: '#ffefef',
|
||||||
100: '#800020',
|
200: '#e15970',
|
||||||
200: '#4c0000'
|
300: '#b73f54',
|
||||||
|
400: '#332426'
|
||||||
},
|
},
|
||||||
blue: {
|
blue: {
|
||||||
DEFAULT: '#00c2ff'
|
DEFAULT: '#4F5FF0',
|
||||||
|
100: '#4F5FF0',
|
||||||
|
200: '#2E378A'
|
||||||
},
|
},
|
||||||
green: {
|
green: {
|
||||||
DEFAULT: '#09ad19'
|
DEFAULT: '#05E300',
|
||||||
|
100: '#05E300',
|
||||||
|
200: '#027D00'
|
||||||
},
|
},
|
||||||
purple: {
|
purple: {
|
||||||
DEFAULT: '#9841e6'
|
DEFAULT: '#9841e6'
|
||||||
},
|
},
|
||||||
cyan: {
|
|
||||||
DEFAULT: '#368f8b',
|
|
||||||
50: '#00b3b3',
|
|
||||||
100: '#368f8b',
|
|
||||||
200: '#376362'
|
|
||||||
},
|
|
||||||
gray: {
|
gray: {
|
||||||
DEFAULT: '#7f7f7f',
|
DEFAULT: '#262626',
|
||||||
50: '#d3d3d3',
|
50: '#fcfcfd',
|
||||||
100: '#7f7f7f',
|
100: '#f7f7f8',
|
||||||
200: '#696969',
|
150: '#f1f1f3',
|
||||||
300: '#313638',
|
175: '#e4e4e7',
|
||||||
500: '#778899',
|
200: '#999999',
|
||||||
600: '#B1B2B5'
|
300: '#808080',
|
||||||
|
400: '#666666',
|
||||||
|
500: '#4d4d4d',
|
||||||
|
600: '#333333',
|
||||||
|
700: '#262626',
|
||||||
|
800: '#1a1a1a',
|
||||||
|
900: '#0d0d0d'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|