Compare commits
102 Commits
feature/ne
...
feature/#2
Author | SHA1 | Date | |
---|---|---|---|
c1360899e1 | |||
63758e67b3 | |||
b51aa29bd8 | |||
f9bfbdf735 | |||
2abce7a7e7 | |||
6ec9f8a7bc | |||
8191a039c9 | |||
540425ca44 | |||
1c2e642fe3 | |||
8355c83dc8 | |||
5fcb336835 | |||
90bdf43b64 | |||
e9dfcf7870 | |||
d0c08c25fd | |||
7bb7af9476 | |||
e4186a1bf5 | |||
8c664d7774 | |||
744df2e2dc | |||
b4f9b11143 | |||
18b07d2f46 | |||
9d0f810ab3 | |||
cf3f17dfef | |||
6be1134c8c | |||
6dad7bc9dd | |||
231f19a30f | |||
9c105d6df6 | |||
179ceb0ca0 | |||
680661f07c | |||
c54d2a2da8 | |||
85f0fca2ae | |||
420e63b724 | |||
5d9b4fd19a | |||
b3d68ef562 | |||
baae737d6b | |||
03f8b327c5 | |||
b9a1ce5ab5 | |||
1b650bd733 | |||
b867250580 | |||
2c7a1e27be | |||
0e455f8ffc | |||
8005bc1318 | |||
11e978121f | |||
727ca99b73 | |||
97080d7380 | |||
1a3a53a229 | |||
a926de8466 | |||
5eabb39ec8 | |||
03313cb092 | |||
d58cfa668d | |||
e3e40dd083 | |||
facdd2d1b4 | |||
7d6bd39f29 | |||
608932300f | |||
b5c1c92b04 | |||
c68b129da8 | |||
963c593a1f | |||
a299e22f88 | |||
2007bfd7c5 | |||
4cae045d0d | |||
1fa8b8f06e | |||
4095184b27 | |||
857d56a878 | |||
8087f754b0 | |||
1479d96162 | |||
606e220a9f | |||
6988565484 | |||
fbc4a3dcdb | |||
924d5bdd13 | |||
25a2fd24f3 | |||
64f5ac45dd | |||
937ce939d1 | |||
7d89364104 | |||
f7b8c235d8 | |||
89d83efca4 | |||
ab97e27f27 | |||
ee3e1b55cb | |||
5e109e2a39 | |||
a8e50c993a | |||
ba8af589a7 | |||
301340327a | |||
1e4c58c79e | |||
f87cd063ee | |||
9593298389 | |||
ad4651844d | |||
3748c459f8 | |||
50ea3ecdab | |||
8910390f7b | |||
d820490b2b | |||
2c96caee4f | |||
84939a7d32 | |||
1e3fc2b0f8 | |||
7c8b5f3e82 | |||
570d315bf5 | |||
7871b34c60 | |||
85d64f23eb | |||
bdb6dd0d54 | |||
faf887163a | |||
dd5baa530d | |||
d9947e29cf | |||
1888521762 | |||
48fef2313b | |||
0a99d2c430 |
@ -1,4 +1,4 @@
|
||||
VITE_NAME=Sylvan Quest
|
||||
VITE_NAME=Noxious
|
||||
VITE_DEVELOPMENT=true
|
||||
VITE_SERVER_ENDPOINT=http://localhost:4000
|
||||
VITE_TILE_SIZE_X=64
|
||||
|
@ -4,5 +4,8 @@
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"printWidth": 300,
|
||||
"trailingComma": "none"
|
||||
"trailingComma": "none",
|
||||
"plugins": ["@ianvs/prettier-plugin-sort-imports"],
|
||||
"importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy", "classProperties"],
|
||||
"importOrderCaseSensitive": false
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>Sylvan Quest - Play</title>
|
||||
<title>Noxious - Play</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
1434
package-lock.json
generated
@ -27,6 +27,7 @@
|
||||
"zod": "^3.22.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
|
||||
"@rushstack/eslint-patch": "^1.10.3",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
@ -37,7 +38,6 @@
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"easystarjs": "^0.4.4",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"jsdom": "^24.1.1",
|
||||
|
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 162 KiB |
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 16L15 12M15 12L11 8M15 12H3M4.51555 17C6.13007 19.412 8.87958 21 12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C8.87958 3 6.13007 4.58803 4.51555 7" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 453 B |
Before Width: | Height: | Size: 597 KiB After Width: | Height: | Size: 597 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 600 KiB After Width: | Height: | Size: 600 KiB |
Before Width: | Height: | Size: 600 KiB After Width: | Height: | Size: 600 KiB |
Before Width: | Height: | Size: 599 KiB After Width: | Height: | Size: 599 KiB |
Before Width: | Height: | Size: 915 B After Width: | Height: | Size: 918 B |
Before Width: | Height: | Size: 768 B After Width: | Height: | Size: 768 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 375 B After Width: | Height: | Size: 375 B |
Before Width: | Height: | Size: 652 B After Width: | Height: | Size: 652 B |
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 346 B |
@ -1 +0,0 @@
|
||||
<svg width="48" xmlns="http://www.w3.org/2000/svg" height="48" id="screenshot-e9346f42-72c8-800c-8004-507b356b7f18" viewBox="0 0 48 48" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-e9346f42-72c8-800c-8004-507b356b7f18" width="1em" height="1em" rx="0" ry="0" style="fill: rgb(0, 0, 0);"><g id="shape-e9346f42-72c8-800c-8004-507b356b7f1b"><defs style="fill: rgb(0, 0, 0);"/></g><g id="shape-e9346f42-72c8-800c-8004-507b356b7f1c"><defs><mask width="1.2" height="1.2" x="-0.1" id="render-3-ipSEnterKey0" data-old-y="-0.1" data-old-width="1.2" data-old-x="-0.1" y="-0.1" data-old-height="1.2"><g class="svg-mask-wrapper" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"><path fill="#fff" stroke="#fff" d="M44 44V4H24v16H4v24z"/><path stroke="#000" d="m21 28l-4 4l4 4"/><path stroke="#000" d="M34 23v9H17"/></g></g></mask></defs><g class="fills" id="fills-e9346f42-72c8-800c-8004-507b356b7f1c"><path d="M0.000,0.000L48.000,0.000L48.000,48.000L0.000,48.000ZZ" mask="url(#render-3-ipSEnterKey0)" style="fill: #696969; fill-opacity: 1;"/></g></g></g></svg>
|
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 2.1 KiB |
3
public/assets/icons/triangle-icon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="13" height="16" viewBox="0 0 13 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.00044 7.42339L12.0005 1.07252C12.3193 0.888385 12.7271 0.997653 12.9111 1.31652C12.9697 1.41785 13.0005 1.53285 13.0005 1.64985L13.0005 14.3516C13.0005 14.7198 12.702 15.0182 12.3338 15.0182C12.2167 15.0182 12.1018 14.9874 12.0005 14.9289L1.00044 8.57805C0.681573 8.39399 0.572326 7.98625 0.756419 7.66739C0.814932 7.56605 0.899092 7.48185 1.00044 7.42339Z" fill="#4D4D4D"/>
|
||||
</svg>
|
After Width: | Height: | Size: 490 B |
3
public/assets/icons/x-button-gray.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.5859 10.0001L0.792969 2.20718L2.20718 0.792969L10.0001 8.58582L17.793 0.792969L19.2072 2.20718L11.4143 10.0001L19.2072 17.7929L17.793 19.2072L10.0001 11.4143L2.20718 19.2072L0.792969 17.7929L8.5859 10.0001Z" fill="#999999"/>
|
||||
</svg>
|
After Width: | Height: | Size: 340 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 274 B |
@ -1 +0,0 @@
|
||||
<svg width="170" xmlns="http://www.w3.org/2000/svg" height="275" id="screenshot-c7008730-586b-8052-8004-79a2f5807f5c" viewBox="0 0 170 275" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-c7008730-586b-8052-8004-79a2f5807f5c"><defs><linearGradient id="fill-color-gradient-render-1-0" x1="0.4238862507773219" y1="0.26897966364321624" x2="0.7513711107948178" y2="0.9146156628521559" gradientTransform=""><stop offset="0" stop-color="#ffffff" stop-opacity="0.4"/><stop offset="1" stop-color="#ffffff" stop-opacity="0"/></linearGradient><pattern patternUnits="userSpaceOnUse" x="0" y="0" width="170" height="275" patternTransform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" id="fill-0-render-1"><g><rect width="170" height="275" style="fill: url("#fill-color-gradient-render-1-0");"/></g></pattern></defs><g class="fills" id="fills-c7008730-586b-8052-8004-79a2f5807f5c"><path d="M170.000,20.000L170.000,255.000C170.000,266.038,161.038,275.000,150.000,275.000L20.000,275.000C8.962,275.000,0.000,266.038,0.000,255.000L0.000,20.000C0.000,8.962,8.962,0.000,20.000,0.000L150.000,0.000C150.000,0.000,150.000,0.000,150.000,0.000C150.000,11.038,158.962,20.000,170.000,20.000Z" fill="url(#fill-0-render-1)"/></g><g id="strokes-c7008730-586b-8052-8004-79a2f5807f5c" class="strokes"><g class="inner-stroke-shape"><defs><clipPath id="inner-stroke-render-1-c7008730-586b-8052-8004-79a2f5807f5c-0"><use href="#stroke-shape-render-1-c7008730-586b-8052-8004-79a2f5807f5c-0"/></clipPath><path d="M170.000,20.000L170.000,255.000C170.000,266.038,161.038,275.000,150.000,275.000L20.000,275.000C8.962,275.000,0.000,266.038,0.000,255.000L0.000,20.000C0.000,8.962,8.962,0.000,20.000,0.000L150.000,0.000C150.000,0.000,150.000,0.000,150.000,0.000C150.000,11.038,158.962,20.000,170.000,20.000Z" id="stroke-shape-render-1-c7008730-586b-8052-8004-79a2f5807f5c-0" style="fill: none; stroke-width: 4; stroke: rgb(255, 255, 255); stroke-opacity: 1;"/></defs><use href="#stroke-shape-render-1-c7008730-586b-8052-8004-79a2f5807f5c-0" clip-path="url('#inner-stroke-render-1-c7008730-586b-8052-8004-79a2f5807f5c-0')"/></g></g></g></svg>
|
Before Width: | Height: | Size: 2.1 KiB |
@ -1 +0,0 @@
|
||||
<svg width="290" xmlns="http://www.w3.org/2000/svg" height="87" id="screenshot-e9942e24-155b-8096-8004-7eaff9882cd6" viewBox="0 0 290 87" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-e9942e24-155b-8096-8004-7eaff9882cd6"><g class="fills" id="fills-e9942e24-155b-8096-8004-7eaff9882cd6"><path d="M3.485,8.722C7.089,3.457,13.144,0.000,20.000,0.000L270.000,0.000C281.038,0.000,290.000,8.962,290.000,20.000L290.000,67.000C290.000,78.038,281.038,87.000,270.000,87.000L20.000,87.000C13.166,87.000,7.128,83.565,3.520,78.329C21.157,76.589,35.000,61.648,35.000,43.500C35.000,25.390,21.216,10.475,3.485,8.722ZM0.000,67.000L0.000,20.000"/></g><g id="strokes-e9942e24-155b-8096-8004-7eaff9882cd6" class="strokes"><g class="inner-stroke-shape"><defs><clipPath id="inner-stroke-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0"><use href="#stroke-shape-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0"/></clipPath><path d="M3.485,8.722C7.089,3.457,13.144,0.000,20.000,0.000L270.000,0.000C281.038,0.000,290.000,8.962,290.000,20.000L290.000,67.000C290.000,78.038,281.038,87.000,270.000,87.000L20.000,87.000C13.166,87.000,7.128,83.565,3.520,78.329C21.157,76.589,35.000,61.648,35.000,43.500C35.000,25.390,21.216,10.475,3.485,8.722ZM0.000,67.000L0.000,20.000" id="stroke-shape-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0" style="fill: none; stroke-width: 6; stroke: rgb(77 77 77); stroke-opacity: 1;"/></defs><use href="#stroke-shape-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0" clip-path="url('#inner-stroke-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0')"/></g></g></g></svg>
|
Before Width: | Height: | Size: 1.6 KiB |
@ -1 +0,0 @@
|
||||
<svg width="290" xmlns="http://www.w3.org/2000/svg" height="87" id="screenshot-e9942e24-155b-8096-8004-7eaff9882cd6" viewBox="0 0 290 87" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-e9942e24-155b-8096-8004-7eaff9882cd6"><g class="fills" id="fills-e9942e24-155b-8096-8004-7eaff9882cd6"><path d="M3.485,8.722C7.089,3.457,13.144,0.000,20.000,0.000L270.000,0.000C281.038,0.000,290.000,8.962,290.000,20.000L290.000,67.000C290.000,78.038,281.038,87.000,270.000,87.000L20.000,87.000C13.166,87.000,7.128,83.565,3.520,78.329C21.157,76.589,35.000,61.648,35.000,43.500C35.000,25.390,21.216,10.475,3.485,8.722ZM0.000,67.000L0.000,20.000" style="fill: rgb(127, 127, 127); fill-opacity: 0.7;"/></g><g id="strokes-e9942e24-155b-8096-8004-7eaff9882cd6" class="strokes"><g class="inner-stroke-shape"><defs><clipPath id="inner-stroke-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0"><use href="#stroke-shape-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0"/></clipPath><path d="M3.485,8.722C7.089,3.457,13.144,0.000,20.000,0.000L270.000,0.000C281.038,0.000,290.000,8.962,290.000,20.000L290.000,67.000C290.000,78.038,281.038,87.000,270.000,87.000L20.000,87.000C13.166,87.000,7.128,83.565,3.520,78.329C21.157,76.589,35.000,61.648,35.000,43.500C35.000,25.390,21.216,10.475,3.485,8.722ZM0.000,67.000L0.000,20.000" id="stroke-shape-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0" style="fill: none; stroke-width: 6; stroke: rgb(255, 255, 255); stroke-opacity: 1;"/></defs><use href="#stroke-shape-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0" clip-path="url('#inner-stroke-render-1-e9942e24-155b-8096-8004-7eaff9882cd6-0')"/></g></g></g></svg>
|
Before Width: | Height: | Size: 1.6 KiB |
@ -1 +0,0 @@
|
||||
<svg width="1508.086" xmlns="http://www.w3.org/2000/svg" height="1511.251" id="screenshot-0d120e2a-8725-8061-8004-79728483f7ea" viewBox="-201.784 -208.012 1508.086 1511.251" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-0d120e2a-8725-8061-8004-79728483f7ea" width="800px" height="800px" rx="0" ry="0" style="opacity: 0.3; fill: rgb(0, 0, 0);"><g id="shape-0d120e2a-8725-8061-8004-79728484b3fe"><g class="fills" id="fills-0d120e2a-8725-8061-8004-79728484b3fe"><path d="M1190.359,745.690L1043.367,575.945L1133.504,630.603C1099.180,585.722,1047.978,532.622,975.722,469.519L780.783,401.898L896.468,404.538C851.234,371.382,797.068,339.090,738.130,311.073L601.350,337.338L689.349,289.039C627.425,263.088,562.143,241.922,497.713,229.160C430.172,215.674,363.453,211.534,303.512,221.641L314.753,151.382C271.664,177.012,239.130,209.992,214.226,251.017L204.390,177.710C166.181,212.950,148.095,250.172,143.131,301.343C69.092,307.974,-2.300,327.925,-73.861,347.005L-63.628,384.898C361.675,238.903,753.109,407.667,987.467,615.054L960.305,643.506C749.259,458.743,490.332,358.712,193.406,380.541C209.110,415.226,228.858,447.126,251.288,474.785L371.998,430.671L277.417,504.449C294.635,521.771,312.591,536.670,331.111,548.765L396.081,470.998L358.366,564.253C377.625,573.924,397.183,580.217,416.674,582.837C534.164,599.232,652.310,618.566,782.785,703.535L773.618,601.955L831.163,737.792C852.261,754.489,876.370,771.191,897.168,782.701L861.169,684.190L960.409,811.519C976.589,817.512,992.991,822.477,1008.953,826.486C1066.083,840.287,1120.015,842.594,1157.256,819.002C1175.975,807.393,1189.205,786.963,1190.791,762.853C1191.080,756.933,1190.907,750.762,1190.359,745.690ZZ" style="fill: rgb(13 109 105);"/></g></g></g></svg>
|
Before Width: | Height: | Size: 1.7 KiB |
BIN
public/assets/tlogo.png
Normal file
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 470 KiB After Width: | Height: | Size: 470 KiB |
Before Width: | Height: | Size: 471 KiB After Width: | Height: | Size: 471 KiB |
Before Width: | Height: | Size: 598 KiB After Width: | Height: | Size: 598 KiB |
25
public/assets/ui-elements/character-select-ui-shape.svg
Normal file
@ -0,0 +1,25 @@
|
||||
<svg width="190" height="202" viewBox="0 0 190 202" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 8H8V0C7.40741 4.14815 4.14815 7.40741 0 8Z" fill="#1A1A1A"/>
|
||||
<path d="M7.55 0V0C7.55 4.16975 4.16975 7.55 0 7.55V7.55" stroke="#4D4D4D"/>
|
||||
<mask id="path-3-inside-1_632_705" fill="white">
|
||||
<path d="M0 8H8V194H0V8Z"/>
|
||||
</mask>
|
||||
<path d="M0 8H8V194H0V8Z" fill="#1A1A1A"/>
|
||||
<path d="M1 194V8H-1V194H1Z" fill="#4D4D4D" mask="url(#path-3-inside-1_632_705)"/>
|
||||
<path d="M0 194H8V202C7.40741 197.852 4.14815 194.593 0 194Z" fill="#1A1A1A"/>
|
||||
<path d="M7.55 202V202C7.55 197.83 4.16975 194.45 0 194.45V194.45" stroke="#4D4D4D"/>
|
||||
<mask id="path-7-inside-2_632_705" fill="white">
|
||||
<path d="M8 0H182V202H8V0Z"/>
|
||||
</mask>
|
||||
<path d="M8 0H182V202H8V0Z" fill="#1A1A1A"/>
|
||||
<path d="M8 1H182V-1H8V1ZM182 201H8V203H182V201Z" fill="#4D4D4D" mask="url(#path-7-inside-2_632_705)"/>
|
||||
<path d="M190 8H182V0C182.593 4.14815 185.852 7.40741 190 8Z" fill="#1A1A1A"/>
|
||||
<path d="M182.45 0V0C182.45 4.16975 185.83 7.55 190 7.55V7.55" stroke="#4D4D4D"/>
|
||||
<mask id="path-11-inside-3_632_705" fill="white">
|
||||
<path d="M190 8H182V194H190V8Z"/>
|
||||
</mask>
|
||||
<path d="M190 8H182V194H190V8Z" fill="#1A1A1A"/>
|
||||
<path d="M189 194V8H191V194H189Z" fill="#4D4D4D" mask="url(#path-11-inside-3_632_705)"/>
|
||||
<path d="M190 194H182V202C182.593 197.852 185.852 194.593 190 194Z" fill="#1A1A1A"/>
|
||||
<path d="M182.45 202V202C182.45 197.83 185.83 194.45 190 194.45V194.45" stroke="#4D4D4D"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 470 KiB After Width: | Height: | Size: 470 KiB |
23
public/assets/ui-elements/login-ui-box-inner.svg
Normal file
After Width: | Height: | Size: 302 KiB |
23
public/assets/ui-elements/login-ui-box-outer.svg
Normal file
After Width: | Height: | Size: 400 KiB |
@ -1,18 +0,0 @@
|
||||
<svg width="190" height="202" viewBox="0 0 190 202" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_i_598_514)">
|
||||
<path d="M0 3.60002C0 1.61179 1.61177 0 3.6 0H186.4C188.388 0 190 1.61177 190 3.6V193.658C190 195.646 188.388 197.258 186.4 197.258H184.894C183.584 197.258 182.523 198.32 182.523 199.629C182.523 200.938 181.461 202 180.152 202H9.84847C8.53901 202 7.47748 200.938 7.47748 199.629C7.47748 198.32 6.41596 197.258 5.1065 197.258H3.6C1.61178 197.258 0 195.646 0 193.658V3.60002Z" fill="#181818"/>
|
||||
</g>
|
||||
<path d="M0.3 3.60002C0.3 1.77747 1.77746 0.3 3.6 0.3H186.4C188.223 0.3 189.7 1.77746 189.7 3.6V193.658C189.7 195.481 188.223 196.958 186.4 196.958H184.894C183.418 196.958 182.223 198.154 182.223 199.629C182.223 200.773 181.295 201.7 180.152 201.7H9.84847C8.7047 201.7 7.77748 200.773 7.77748 199.629C7.77748 198.154 6.58164 196.958 5.1065 196.958H3.6C1.77746 196.958 0.3 195.481 0.3 193.658V3.60002Z" stroke="#454442" stroke-width="0.6"/>
|
||||
<defs>
|
||||
<filter id="filter0_i_598_514" x="0" y="0" width="190" height="204.4" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="2.4"/>
|
||||
<feGaussianBlur stdDeviation="2.34"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.13 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_598_514"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 301 KiB |
Before Width: | Height: | Size: 400 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 2.1 MiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 1.1 MiB |
14
src/App.vue
@ -6,15 +6,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import Notifications from '@/components/utilities/Notifications.vue'
|
||||
import BackgroundImageLoader from '@/components/utilities/BackgroundImageLoader.vue'
|
||||
import GmPanel from '@/components/gameMaster/GmPanel.vue'
|
||||
import Login from '@/components/screens/Login.vue'
|
||||
import Characters from '@/components/screens/Characters.vue'
|
||||
import Game from '@/components/screens/Game.vue'
|
||||
import Login from '@/components/screens/Login.vue'
|
||||
import ZoneEditor from '@/components/screens/ZoneEditor.vue'
|
||||
import BackgroundImageLoader from '@/components/utilities/BackgroundImageLoader.vue'
|
||||
import Notifications from '@/components/utilities/Notifications.vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { computed, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
@ -49,8 +49,8 @@ addEventListener('click', (event) => {
|
||||
addEventListener('keydown', (event) => {
|
||||
if (gameStore.character?.role !== 'gm') return // Only allow toggling the gm panel if the character is a gm
|
||||
|
||||
// Check if no input is active
|
||||
if (event.repeat || event.isComposing || event.defaultPrevented) return
|
||||
// Check if no input is active or focus is on an input
|
||||
if (event.repeat || event.isComposing || event.defaultPrevented || document.activeElement?.tagName.toUpperCase() === 'INPUT' || document.activeElement?.tagName.toUpperCase() === 'TEXTAREA') return
|
||||
|
||||
if (event.key === 'G') {
|
||||
gameStore.toggleGmPanel()
|
||||
|
@ -1,8 +1,8 @@
|
||||
import config from '@/config'
|
||||
import config from '@/application/config'
|
||||
import type { AssetDataT } from '@/application/types'
|
||||
import Dexie from 'dexie'
|
||||
import type { AssetDataT } from '@/types'
|
||||
|
||||
export class AssetStorage {
|
||||
export class Assets {
|
||||
private db: Dexie
|
||||
|
||||
constructor() {
|
||||
@ -25,7 +25,19 @@ export class AssetStorage {
|
||||
const blob = await response.blob()
|
||||
|
||||
// Store the asset in the database
|
||||
await this.db.table('assets').put({ key: asset.key, data: blob, group: asset.group, updatedAt: asset.updatedAt, frameCount: asset.frameCount, frameWidth: asset.frameWidth, frameHeight: asset.frameHeight })
|
||||
await this.db.table('assets').put({
|
||||
key: asset.key,
|
||||
data: blob,
|
||||
group: asset.group,
|
||||
updatedAt: asset.updatedAt,
|
||||
originX: asset.originX,
|
||||
originY: asset.originY,
|
||||
isAnimated: asset.isAnimated,
|
||||
frameRate: asset.frameRate,
|
||||
frameWidth: asset.frameWidth,
|
||||
frameHeight: asset.frameHeight,
|
||||
frameCount: asset.frameCount
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`Failed to add asset ${asset.key}:`, error)
|
||||
}
|
@ -1,22 +1,33 @@
|
||||
export type UUID = `${string}-${string}-${string}-${string}-${string}`
|
||||
|
||||
export type Notification = {
|
||||
id?: string
|
||||
title?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export type HttpResponse<T> = {
|
||||
success: boolean
|
||||
message?: string
|
||||
data?: T
|
||||
}
|
||||
|
||||
export type AssetDataT = {
|
||||
key: string
|
||||
data: string
|
||||
group: 'tiles' | 'objects' | 'sprites' | 'sprite_animations' | 'sound' | 'music' | 'ui' | 'font' | 'other'
|
||||
updatedAt: Date
|
||||
originX?: number
|
||||
originY?: number
|
||||
isAnimated?: boolean
|
||||
frameCount?: number
|
||||
frameRate?: number
|
||||
frameWidth?: number
|
||||
frameHeight?: number
|
||||
frameCount?: number
|
||||
}
|
||||
|
||||
export type Tile = {
|
||||
id: string
|
||||
id: UUID
|
||||
name: string
|
||||
tags: any | null
|
||||
createdAt: Date
|
||||
@ -24,13 +35,13 @@ export type Tile = {
|
||||
}
|
||||
|
||||
export type Object = {
|
||||
id: string
|
||||
id: UUID
|
||||
name: string
|
||||
tags: any | null
|
||||
originX: number
|
||||
originY: number
|
||||
isAnimated: boolean
|
||||
frameSpeed: number
|
||||
frameRate: number
|
||||
frameWidth: number
|
||||
frameHeight: number
|
||||
createdAt: Date
|
||||
@ -39,17 +50,23 @@ export type Object = {
|
||||
}
|
||||
|
||||
export type Item = {
|
||||
id: string
|
||||
id: UUID
|
||||
name: string
|
||||
description: string | null
|
||||
itemType: ItemType
|
||||
stackable: boolean
|
||||
rarity: ItemRarity
|
||||
spriteId: UUID | null
|
||||
sprite?: Sprite
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
characters: CharacterItem[]
|
||||
}
|
||||
|
||||
export type ItemType = 'WEAPON' | 'HELMET' | 'CHEST' | 'LEGS' | 'BOOTS' | 'GLOVES' | 'RING' | 'NECKLACE'
|
||||
export type ItemRarity = 'COMMON' | 'UNCOMMON' | 'RARE' | 'EPIC' | 'LEGENDARY'
|
||||
|
||||
export type Zone = {
|
||||
id: number
|
||||
id: UUID
|
||||
name: string
|
||||
width: number
|
||||
height: number
|
||||
@ -65,18 +82,18 @@ export type Zone = {
|
||||
}
|
||||
|
||||
export type ZoneEffect = {
|
||||
id: string
|
||||
zoneId: number
|
||||
id: UUID
|
||||
zoneId: UUID
|
||||
zone: Zone
|
||||
effect: string
|
||||
strength: number
|
||||
}
|
||||
|
||||
export type ZoneObject = {
|
||||
id: string
|
||||
zoneId: number
|
||||
id: UUID
|
||||
zoneId: UUID
|
||||
zone: Zone
|
||||
objectId: string
|
||||
objectId: UUID
|
||||
object: Object
|
||||
depth: number
|
||||
isRotated: boolean
|
||||
@ -92,8 +109,8 @@ export enum ZoneEventTileType {
|
||||
}
|
||||
|
||||
export type ZoneEventTile = {
|
||||
id: string
|
||||
zoneId: number
|
||||
id: UUID
|
||||
zoneId: UUID
|
||||
zone: Zone
|
||||
type: ZoneEventTileType
|
||||
positionX: number
|
||||
@ -102,10 +119,10 @@ export type ZoneEventTile = {
|
||||
}
|
||||
|
||||
export type ZoneEventTileTeleport = {
|
||||
id: string
|
||||
zoneEventTileId: string
|
||||
id: UUID
|
||||
zoneEventTileId: UUID
|
||||
zoneEventTile: ZoneEventTile
|
||||
toZoneId: number
|
||||
toZoneId: UUID
|
||||
toZone: Zone
|
||||
toPositionX: number
|
||||
toPositionY: number
|
||||
@ -113,7 +130,7 @@ export type ZoneEventTileTeleport = {
|
||||
}
|
||||
|
||||
export type User = {
|
||||
id: number
|
||||
id: UUID
|
||||
username: string
|
||||
password: string
|
||||
characters: Character[]
|
||||
@ -133,10 +150,11 @@ export enum CharacterRace {
|
||||
}
|
||||
|
||||
export type CharacterType = {
|
||||
id: number
|
||||
id: UUID
|
||||
name: string
|
||||
gender: CharacterGender
|
||||
race: CharacterRace
|
||||
isSelectable: boolean
|
||||
characters: Character[]
|
||||
spriteId?: string
|
||||
sprite?: Sprite
|
||||
@ -144,9 +162,17 @@ export type CharacterType = {
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
export type CharacterHair = {
|
||||
id: UUID
|
||||
name: string
|
||||
sprite: Sprite
|
||||
gender: CharacterGender
|
||||
isSelectable: boolean
|
||||
}
|
||||
|
||||
export type Character = {
|
||||
id: number
|
||||
userId: number
|
||||
id: UUID
|
||||
userId: UUID
|
||||
user: User
|
||||
name: string
|
||||
hitpoints: number
|
||||
@ -158,29 +184,49 @@ export type Character = {
|
||||
positionX: number
|
||||
positionY: number
|
||||
rotation: number
|
||||
zoneId: number
|
||||
characterTypeId: UUID | null
|
||||
characterType: CharacterType | null | string
|
||||
characterHairId: UUID | null
|
||||
characterHair: CharacterHair | null
|
||||
zoneId: UUID
|
||||
zone: Zone
|
||||
characterTypeId: number | null
|
||||
characterType: CharacterType | null
|
||||
chats: Chat[]
|
||||
items: CharacterItem[]
|
||||
equipment: CharacterEquipment[]
|
||||
}
|
||||
|
||||
export type ExtendedCharacter = Character & {
|
||||
export type ZoneCharacter = {
|
||||
character: Character
|
||||
isMoving?: boolean
|
||||
}
|
||||
|
||||
export type CharacterItem = {
|
||||
id: number
|
||||
characterId: number
|
||||
id: UUID
|
||||
characterId: UUID
|
||||
character: Character
|
||||
itemId: string
|
||||
itemId: UUID
|
||||
item: Item
|
||||
quantity: number
|
||||
}
|
||||
|
||||
export type CharacterEquipment = {
|
||||
id: UUID
|
||||
slot: CharacterEquipmentSlotType
|
||||
characterItemId: UUID
|
||||
characterItem: CharacterItem
|
||||
}
|
||||
|
||||
export enum CharacterEquipmentSlotType {
|
||||
HEAD = 'HEAD',
|
||||
BODY = 'BODY',
|
||||
ARMS = 'ARMS',
|
||||
LEGS = 'LEGS',
|
||||
NECK = 'NECK',
|
||||
RING = 'RING'
|
||||
}
|
||||
|
||||
export type Sprite = {
|
||||
id: string
|
||||
id: UUID
|
||||
name: string
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
@ -189,8 +235,7 @@ export type Sprite = {
|
||||
}
|
||||
|
||||
export type SpriteAction = {
|
||||
id: string
|
||||
spriteId: string
|
||||
id: UUID
|
||||
sprite: Sprite
|
||||
action: string
|
||||
sprites: string[]
|
||||
@ -200,24 +245,19 @@ export type SpriteAction = {
|
||||
isLooping: boolean
|
||||
frameWidth: number
|
||||
frameHeight: number
|
||||
frameSpeed: number
|
||||
frameRate: number
|
||||
}
|
||||
|
||||
export type Chat = {
|
||||
id: number
|
||||
characterId: number
|
||||
id: UUID
|
||||
characterId: UUID
|
||||
character: Character
|
||||
zoneId: number
|
||||
zoneId: UUID
|
||||
zone: Zone
|
||||
message: string
|
||||
createdAt: Date
|
||||
}
|
||||
|
||||
export type ChatMessage = {
|
||||
character: Character
|
||||
message: string
|
||||
}
|
||||
|
||||
export type WorldSettings = {
|
||||
date: Date
|
||||
isRainEnabled: boolean
|
||||
@ -231,3 +271,8 @@ export type WeatherState = {
|
||||
isFogEnabled: boolean
|
||||
fogDensity: number
|
||||
}
|
||||
|
||||
export type zoneLoadData = {
|
||||
zone: Zone
|
||||
characters: ZoneCharacter[]
|
||||
}
|
@ -23,6 +23,14 @@ body {
|
||||
|
||||
// Disable pinch zoom
|
||||
touch-action: pan-x pan-y;
|
||||
|
||||
// Add custom focus outline
|
||||
*:focus-visible {
|
||||
@apply outline-gray-300;
|
||||
@apply outline;
|
||||
@apply outline-offset-2;
|
||||
@apply rounded-sm;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
@ -45,7 +53,7 @@ label {
|
||||
|
||||
button,
|
||||
a {
|
||||
@apply font-medium drop-shadow-20;
|
||||
@apply font-medium;
|
||||
}
|
||||
|
||||
button,
|
||||
@ -58,13 +66,20 @@ input {
|
||||
appearance: textfield;
|
||||
}
|
||||
&[type='number']::-webkit-inner-spin-button,
|
||||
&[type='number']::-webkit-outer-spin-button {
|
||||
&[type='number']::-webkit-outer-spin-button,
|
||||
&[type='radio'] {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
.input-field {
|
||||
@apply px-4 py-2.5 text-base leading-5 focus-visible:outline-none bg-gray border border-solid border-gray-500 rounded text-gray-300;
|
||||
@apply px-4 py-2.5 text-base leading-5 bg-gray border border-solid border-gray-500 rounded text-gray-300;
|
||||
&:focus-visible {
|
||||
@apply outline-none border-cyan rounded bg-gray-900;
|
||||
}
|
||||
&::placeholder {
|
||||
@apply focus-visible:text-gray-300/50;
|
||||
}
|
||||
&.inactive {
|
||||
@apply bg-gray-600/50 hover:cursor-not-allowed;
|
||||
&::placeholder {
|
||||
@ -99,11 +114,11 @@ button {
|
||||
}
|
||||
|
||||
&.btn-red {
|
||||
@apply bg-red text-gray-50 text-base leading-5 rounded py-2.5;
|
||||
@apply bg-red-300 text-gray-50 text-base leading-5 rounded py-2.5;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
@apply bg-red-300;
|
||||
@apply bg-red-400;
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,8 +126,9 @@ button {
|
||||
@apply text-gray-50 border-2 border-solid border-gray-500 text-base leading-5 rounded py-2.5;
|
||||
|
||||
&.active,
|
||||
&.selected,
|
||||
&:hover {
|
||||
@apply bg-gray-700 border-gray-700;
|
||||
@apply bg-gray border-gray;
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,12 +139,28 @@ button {
|
||||
&.eye-open {
|
||||
@apply bg-[url('/assets/icons/eye-closed.svg')] w-5 h-4 right-2.5;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
@apply bg-gray-500 border-gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
.character {
|
||||
&.active {
|
||||
@apply pr-px border-r-0;
|
||||
.character.active {
|
||||
@apply bg-gray bg-none;
|
||||
}
|
||||
|
||||
.hair-deselect:has(:checked) {
|
||||
img {
|
||||
@apply brightness-200;
|
||||
}
|
||||
}
|
||||
|
||||
.default-border {
|
||||
@apply border border-solid border-gray-500;
|
||||
}
|
||||
|
||||
.center-element {
|
||||
@apply absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2;
|
||||
}
|
||||
|
||||
.text-pixel {
|
||||
@ -139,6 +171,15 @@ button {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
&::-webkit-scrollbar {
|
||||
@apply block w-0.5 bg-gray-300 rounded-sm;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply bg-gray-175;
|
||||
}
|
||||
}
|
||||
|
||||
canvas {
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -webkit-crisp-edges;
|
||||
|
@ -3,31 +3,34 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Scene } from 'phavuer'
|
||||
import { useZoneStore } from '@/stores/zoneStore'
|
||||
import type { WeatherState } from '@/application/types'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onBeforeUnmount, ref, watch } from 'vue'
|
||||
import type { WeatherState } from '@/types'
|
||||
import { useZoneStore } from '@/stores/zoneStore'
|
||||
import { Scene } from 'phavuer'
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
// Constants
|
||||
const SUNRISE_HOUR = 6
|
||||
const SUNSET_HOUR = 20
|
||||
const DAY_STRENGTH = 100
|
||||
const NIGHT_STRENGTH = 30
|
||||
const LIGHT_CONFIG = {
|
||||
SUNRISE_HOUR: 6,
|
||||
SUNSET_HOUR: 20,
|
||||
DAY_STRENGTH: 100,
|
||||
NIGHT_STRENGTH: 30
|
||||
}
|
||||
|
||||
// Stores
|
||||
// Stores and refs
|
||||
const gameStore = useGameStore()
|
||||
const zoneStore = useZoneStore()
|
||||
|
||||
// Scene ref
|
||||
const sceneRef = ref<Phaser.Scene | null>(null)
|
||||
const zoneEffectsReady = ref(false)
|
||||
|
||||
// Effect refs
|
||||
const lightEffect = ref<Phaser.GameObjects.Graphics | null>(null)
|
||||
const rainEmitter = ref<Phaser.GameObjects.Particles.ParticleEmitter | null>(null)
|
||||
const fogSprite = ref<Phaser.GameObjects.Sprite | null>(null)
|
||||
// Effect objects
|
||||
const effects = {
|
||||
light: ref<Phaser.GameObjects.Graphics | null>(null),
|
||||
rain: ref<Phaser.GameObjects.Particles.ParticleEmitter | null>(null),
|
||||
fog: ref<Phaser.GameObjects.Sprite | null>(null)
|
||||
}
|
||||
|
||||
// State refs
|
||||
// Weather state
|
||||
const weatherState = ref<WeatherState>({
|
||||
isRainEnabled: false,
|
||||
rainPercentage: 0,
|
||||
@ -35,36 +38,25 @@ const weatherState = ref<WeatherState>({
|
||||
fogDensity: 0
|
||||
})
|
||||
|
||||
// Scene lifecycle methods
|
||||
const preloadScene = async (scene: Phaser.Scene) => {
|
||||
// Scene setup
|
||||
const preloadScene = (scene: Phaser.Scene) => {
|
||||
scene.load.image('raindrop', 'assets/raindrop.png')
|
||||
scene.load.image('fog', 'assets/fog.png')
|
||||
}
|
||||
|
||||
const createScene = async (scene: Phaser.Scene) => {
|
||||
const createScene = (scene: Phaser.Scene) => {
|
||||
sceneRef.value = scene
|
||||
setupEffects(scene)
|
||||
initializeEffects(scene)
|
||||
setupSocketListeners()
|
||||
}
|
||||
|
||||
const updateScene = () => {
|
||||
updateEffects()
|
||||
}
|
||||
const initializeEffects = (scene: Phaser.Scene) => {
|
||||
// Light
|
||||
effects.light.value = scene.add.graphics().setDepth(1000)
|
||||
|
||||
// Effect setup
|
||||
const setupEffects = (scene: Phaser.Scene) => {
|
||||
createLightEffect(scene)
|
||||
createRainEffect(scene)
|
||||
createFogEffect(scene)
|
||||
}
|
||||
|
||||
const createLightEffect = (scene: Phaser.Scene) => {
|
||||
lightEffect.value = scene.add.graphics()
|
||||
lightEffect.value.setDepth(1000)
|
||||
}
|
||||
|
||||
const createRainEffect = (scene: Phaser.Scene) => {
|
||||
rainEmitter.value = scene.add.particles(0, 0, 'raindrop', {
|
||||
// Rain
|
||||
effects.rain.value = scene.add
|
||||
.particles(0, 0, 'raindrop', {
|
||||
x: { min: 0, max: window.innerWidth },
|
||||
y: -50,
|
||||
quantity: 5,
|
||||
@ -74,131 +66,113 @@ const createRainEffect = (scene: Phaser.Scene) => {
|
||||
alpha: { start: 0.5, end: 0 },
|
||||
blendMode: 'ADD'
|
||||
})
|
||||
rainEmitter.value.setDepth(900)
|
||||
rainEmitter.value.stop()
|
||||
.setDepth(900)
|
||||
effects.rain.value.stop()
|
||||
|
||||
// Fog
|
||||
effects.fog.value = scene.add
|
||||
.sprite(window.innerWidth / 2, window.innerHeight / 2, 'fog')
|
||||
.setScale(2)
|
||||
.setAlpha(0)
|
||||
.setDepth(950)
|
||||
}
|
||||
|
||||
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)
|
||||
// Effect updates
|
||||
const updateScene = () => {
|
||||
const timeBasedLight = calculateLightStrength(gameStore.world.date)
|
||||
const zoneEffects = zoneStore.zone?.zoneEffects?.reduce(
|
||||
(acc, curr) => ({
|
||||
...acc,
|
||||
[curr.effect]: curr.strength
|
||||
}),
|
||||
{}
|
||||
) as { [key: string]: number }
|
||||
|
||||
// Only update effects once zoneEffects are loaded
|
||||
if (!zoneEffectsReady.value) {
|
||||
if (zoneEffects && Object.keys(zoneEffects).length) {
|
||||
zoneEffectsReady.value = true
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const finalEffects =
|
||||
zoneEffects && Object.keys(zoneEffects).length
|
||||
? zoneEffects
|
||||
: {
|
||||
light: timeBasedLight,
|
||||
rain: weatherState.value.isRainEnabled ? weatherState.value.rainPercentage : 0,
|
||||
fog: weatherState.value.isFogEnabled ? weatherState.value.fogDensity * 100 : 0
|
||||
}
|
||||
|
||||
applyEffects(finalEffects)
|
||||
}
|
||||
|
||||
const applyEffects = (effectValues: any) => {
|
||||
if (effects.light.value) {
|
||||
const darkness = 1 - (effectValues.light ?? 100) / 100
|
||||
effects.light.value.clear().fillStyle(0x000000, darkness).fillRect(0, 0, window.innerWidth, window.innerHeight)
|
||||
}
|
||||
|
||||
if (effects.rain.value) {
|
||||
const strength = effectValues.rain ?? 0
|
||||
strength > 0 ? effects.rain.value.start().setQuantity(Math.floor((strength / 100) * 10)) : effects.rain.value.stop()
|
||||
}
|
||||
|
||||
if (effects.fog.value) {
|
||||
effects.fog.value.setAlpha((effectValues.fog ?? 0) / 100)
|
||||
}
|
||||
}
|
||||
|
||||
// Lighting calculations
|
||||
const calculateLightStrength = (time: Date): number => {
|
||||
const hour = time.getHours()
|
||||
const minute = time.getMinutes()
|
||||
|
||||
let strength = DAY_STRENGTH
|
||||
if (hour >= LIGHT_CONFIG.SUNSET_HOUR || hour < LIGHT_CONFIG.SUNRISE_HOUR) return LIGHT_CONFIG.NIGHT_STRENGTH
|
||||
|
||||
// Night time (10 PM - 6 AM)
|
||||
if (hour >= SUNSET_HOUR || hour < SUNRISE_HOUR) {
|
||||
strength = NIGHT_STRENGTH
|
||||
}
|
||||
// Full daylight (7 AM - 7 PM)
|
||||
else if (hour > SUNRISE_HOUR && hour < SUNSET_HOUR - 2) {
|
||||
strength = DAY_STRENGTH
|
||||
}
|
||||
// Sunrise transition (6 AM - 7 AM)
|
||||
else if (hour === SUNRISE_HOUR) {
|
||||
strength = NIGHT_STRENGTH + ((DAY_STRENGTH - NIGHT_STRENGTH) * minute) / 60
|
||||
}
|
||||
// Sunset transition (8 PM - 10 PM)
|
||||
else if (hour >= SUNSET_HOUR - 2 && hour < SUNSET_HOUR) {
|
||||
const totalMinutes = (hour - (SUNSET_HOUR - 2)) * 60 + minute
|
||||
const transitionProgress = totalMinutes / 120 // 2 hours = 120 minutes
|
||||
strength = DAY_STRENGTH - (DAY_STRENGTH - NIGHT_STRENGTH) * transitionProgress
|
||||
if (hour > LIGHT_CONFIG.SUNRISE_HOUR && hour < LIGHT_CONFIG.SUNSET_HOUR - 2) return LIGHT_CONFIG.DAY_STRENGTH
|
||||
|
||||
if (hour === LIGHT_CONFIG.SUNRISE_HOUR) return LIGHT_CONFIG.NIGHT_STRENGTH + ((LIGHT_CONFIG.DAY_STRENGTH - LIGHT_CONFIG.NIGHT_STRENGTH) * minute) / 60
|
||||
|
||||
const totalMinutes = (hour - (LIGHT_CONFIG.SUNSET_HOUR - 2)) * 60 + minute
|
||||
return LIGHT_CONFIG.DAY_STRENGTH - (LIGHT_CONFIG.DAY_STRENGTH - LIGHT_CONFIG.NIGHT_STRENGTH) * (totalMinutes / 120)
|
||||
}
|
||||
|
||||
return strength
|
||||
}
|
||||
|
||||
// Effect updates
|
||||
const updateEffects = () => {
|
||||
const effects = zoneStore.zone?.zoneEffects || []
|
||||
|
||||
if (effects.length > 0) {
|
||||
updateZoneEffects(effects)
|
||||
} else {
|
||||
// Make sure we're getting the current time
|
||||
const lightStrength = calculateLightStrength(gameStore.world.date)
|
||||
updateLightEffect(lightStrength)
|
||||
updateWeatherEffects()
|
||||
}
|
||||
}
|
||||
|
||||
const updateZoneEffects = (effects: any[]) => {
|
||||
// Always update light based on time when zone effects are present
|
||||
updateLightEffect(calculateLightStrength(gameStore.world.date))
|
||||
|
||||
effects.forEach((effect) => {
|
||||
switch (effect.effect) {
|
||||
case 'rain':
|
||||
updateRainEffect(effect.strength)
|
||||
break
|
||||
case 'fog':
|
||||
updateFogEffect(effect.strength)
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateWeatherEffects = () => {
|
||||
updateRainEffect(weatherState.value.isRainEnabled ? weatherState.value.rainPercentage : 0)
|
||||
updateFogEffect(weatherState.value.isFogEnabled ? weatherState.value.fogDensity * 100 : 0)
|
||||
}
|
||||
|
||||
const updateLightEffect = (strength: number) => {
|
||||
if (!lightEffect.value) return
|
||||
const darkness = 1 - strength / 100
|
||||
lightEffect.value.clear()
|
||||
lightEffect.value.fillStyle(0x000000, darkness)
|
||||
lightEffect.value.fillRect(0, 0, window.innerWidth, window.innerHeight)
|
||||
}
|
||||
|
||||
const updateRainEffect = (strength: number) => {
|
||||
if (!rainEmitter.value) return
|
||||
if (strength > 0) {
|
||||
rainEmitter.value.start()
|
||||
rainEmitter.value.setQuantity(Math.floor((strength / 100) * 10))
|
||||
} else {
|
||||
rainEmitter.value.stop()
|
||||
}
|
||||
}
|
||||
|
||||
const updateFogEffect = (strength: number) => {
|
||||
if (!fogSprite.value) return
|
||||
fogSprite.value.setAlpha(strength / 100)
|
||||
}
|
||||
|
||||
// Socket handlers
|
||||
// Socket and window handlers
|
||||
const setupSocketListeners = () => {
|
||||
// Initial weather state
|
||||
gameStore.connection?.emit('weather', (response: WeatherState) => {
|
||||
if (zoneStore.zone?.zoneEffects) return
|
||||
weatherState.value = response
|
||||
updateEffects()
|
||||
updateScene()
|
||||
})
|
||||
|
||||
// Weather updates
|
||||
gameStore.connection?.on('weather', (data: WeatherState) => {
|
||||
gameStore.connection!.on('weather', (data: WeatherState) => {
|
||||
weatherState.value = data
|
||||
updateEffects()
|
||||
updateScene()
|
||||
})
|
||||
|
||||
// Time updates
|
||||
gameStore.connection?.on('date', () => {
|
||||
if (zoneStore.zone?.zoneEffects) return
|
||||
updateEffects()
|
||||
})
|
||||
gameStore.connection!.on('date', updateScene)
|
||||
}
|
||||
|
||||
// Watchers
|
||||
watch(() => zoneStore.zone?.zoneEffects, updateEffects, { deep: true })
|
||||
const handleResize = () => {
|
||||
if (effects.rain.value) effects.rain.value.updateConfig({ x: { min: 0, max: window.innerWidth } })
|
||||
if (effects.fog.value) effects.fog.value.setPosition(window.innerWidth / 2, window.innerHeight / 2)
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
watch(
|
||||
() => zoneStore.zone,
|
||||
() => {
|
||||
zoneEffectsReady.value = false
|
||||
updateScene()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
onMounted(() => window.addEventListener('resize', handleResize))
|
||||
|
||||
// Cleanup
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
if (sceneRef.value) sceneRef.value.scene.remove('effects')
|
||||
gameStore.connection?.off('weather')
|
||||
})
|
||||
|
192
src/components/game/character/Character.vue
Normal file
@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<ChatBubble :zoneCharacter="props.zoneCharacter" :currentX="currentX" :currentY="currentY" />
|
||||
<Healthbar :zoneCharacter="props.zoneCharacter" :currentX="currentX" :currentY="currentY" />
|
||||
<Container ref="charContainer" :depth="isometricDepth" :x="currentX" :y="currentY">
|
||||
<CharacterHair :zoneCharacter="props.zoneCharacter" :currentX="currentX" :currentY="currentY" />
|
||||
<!-- <CharacterChest :zoneCharacter="props.zoneCharacter" :currentX="currentX" :currentY="currentY" />-->
|
||||
<Sprite ref="charSprite" :origin-y="1" :flipX="isFlippedX" />
|
||||
</Container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import config from '@/application/config'
|
||||
import { type Sprite as SpriteT, type ZoneCharacter } from '@/application/types'
|
||||
import CharacterHair from '@/components/game/character/partials/CharacterHair.vue'
|
||||
import ChatBubble from '@/components/game/character/partials/ChatBubble.vue'
|
||||
import Healthbar from '@/components/game/character/partials/Healthbar.vue'
|
||||
import { loadSpriteTextures } from '@/composables/gameComposable'
|
||||
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneStore } from '@/stores/zoneStore'
|
||||
import { Container, refObj, Sprite, useScene } from 'phavuer'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
// import CharacterChest from '@/components/game/character/partials/CharacterChest.vue'
|
||||
|
||||
enum Direction {
|
||||
POSITIVE,
|
||||
NEGATIVE,
|
||||
UNCHANGED
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
tilemap: Phaser.Tilemaps.Tilemap
|
||||
zoneCharacter: ZoneCharacter
|
||||
}>()
|
||||
|
||||
const charContainer = refObj<Phaser.GameObjects.Container>()
|
||||
const charSprite = refObj<Phaser.GameObjects.Sprite>()
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const zoneStore = useZoneStore()
|
||||
const scene = useScene()
|
||||
|
||||
const currentX = ref(0)
|
||||
const currentY = ref(0)
|
||||
const isometricDepth = ref(1)
|
||||
const isInitialPosition = ref(true)
|
||||
const isMoving = ref(false)
|
||||
let animationFrame: number | null = null
|
||||
const moveSpeed = 5.7
|
||||
|
||||
const updateIsometricDepth = (x: number, y: number) => {
|
||||
isometricDepth.value = calculateIsometricDepth(x, y, 28, 94, true)
|
||||
}
|
||||
|
||||
const stopMovement = () => {
|
||||
isMoving.value = false
|
||||
if (animationFrame) {
|
||||
cancelAnimationFrame(animationFrame)
|
||||
animationFrame = null
|
||||
}
|
||||
}
|
||||
|
||||
const updatePosition = (x: number, y: number, direction: Direction) => {
|
||||
const targetX = tileToWorldX(props.tilemap, x, y)
|
||||
const targetY = tileToWorldY(props.tilemap, x, y)
|
||||
|
||||
if (isInitialPosition.value) {
|
||||
currentX.value = targetX
|
||||
currentY.value = targetY
|
||||
isInitialPosition.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (isMoving.value) {
|
||||
stopMovement()
|
||||
}
|
||||
|
||||
const distance = Math.sqrt(Math.pow(targetX - currentX.value, 2) + Math.pow(targetY - currentY.value, 2))
|
||||
|
||||
isMoving.value = true
|
||||
const startTime = performance.now()
|
||||
const startX = currentX.value
|
||||
const startY = currentY.value
|
||||
const duration = distance * moveSpeed
|
||||
|
||||
if (direction === Direction.POSITIVE) {
|
||||
updateIsometricDepth(x, y)
|
||||
}
|
||||
|
||||
const animate = (currentTime: number) => {
|
||||
if (!isMoving.value) return
|
||||
|
||||
const elapsed = currentTime - startTime
|
||||
const progress = Math.min(elapsed / duration, 1)
|
||||
|
||||
currentX.value = startX + (targetX - startX) * progress
|
||||
currentY.value = startY + (targetY - startY) * progress
|
||||
|
||||
if (progress < 1) {
|
||||
animationFrame = requestAnimationFrame(animate)
|
||||
} else {
|
||||
isMoving.value = false
|
||||
if (direction === Direction.NEGATIVE) {
|
||||
updateIsometricDepth(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animationFrame = requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
const calcDirection = (oldX: number, oldY: number, newX: number, newY: number): Direction => {
|
||||
if (newY < oldY || newX < oldX) return Direction.NEGATIVE
|
||||
if (newX > oldX || newY > oldY) return Direction.POSITIVE
|
||||
return Direction.UNCHANGED
|
||||
}
|
||||
|
||||
const isFlippedX = computed(() => [6, 4].includes(props.zoneCharacter.character.rotation ?? 0))
|
||||
|
||||
const charTexture = computed(() => {
|
||||
const { rotation, characterType } = props.zoneCharacter.character
|
||||
const spriteId = characterType?.sprite ?? 'idle_right_down'
|
||||
const action = props.zoneCharacter.isMoving ? 'walk' : 'idle'
|
||||
const direction = [0, 6].includes(rotation) ? 'left_up' : 'right_down'
|
||||
|
||||
return `${spriteId}-${action}_${direction}`
|
||||
})
|
||||
|
||||
const updateSprite = () => {
|
||||
if (props.zoneCharacter.isMoving) {
|
||||
charSprite.value!.anims.play(charTexture.value, true)
|
||||
return
|
||||
}
|
||||
|
||||
charSprite.value!.anims.stop()
|
||||
charSprite.value!.setFrame(0)
|
||||
charSprite.value!.setTexture(charTexture.value)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => ({
|
||||
x: props.zoneCharacter.character.positionX,
|
||||
y: props.zoneCharacter.character.positionY,
|
||||
isMoving: props.zoneCharacter.isMoving,
|
||||
rotation: props.zoneCharacter.character.rotation
|
||||
}),
|
||||
(newValues, oldValues) => {
|
||||
if (!newValues) return
|
||||
|
||||
if (!oldValues || newValues.x !== oldValues.x || newValues.y !== oldValues.y) {
|
||||
const direction = !oldValues ? Direction.POSITIVE : calcDirection(oldValues.x, oldValues.y, newValues.x, newValues.y)
|
||||
updatePosition(newValues.x, newValues.y, direction)
|
||||
}
|
||||
|
||||
// Handle animation updates
|
||||
if (newValues.isMoving !== oldValues?.isMoving || newValues.rotation !== oldValues?.rotation) {
|
||||
updateSprite()
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(() => props.zoneCharacter, updateSprite)
|
||||
|
||||
loadSpriteTextures(scene, props.zoneCharacter.character.characterType?.sprite as string)
|
||||
.then(() => {
|
||||
charSprite.value!.setTexture(charTexture.value)
|
||||
charSprite.value!.setFlipX(isFlippedX.value)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error loading texture:', error)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
charContainer.value!.setName(props.zoneCharacter.character!.name)
|
||||
|
||||
if (props.zoneCharacter.character.id === gameStore.character!.id) {
|
||||
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()
|
||||
}
|
||||
|
||||
updatePosition(props.zoneCharacter.character.positionX, props.zoneCharacter.character.positionY, props.zoneCharacter.character.rotation)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
stopMovement()
|
||||
})
|
||||
</script>
|
51
src/components/game/character/partials/CharacterChest.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<Image v-bind="imageProps" v-if="gameStore.getLoadedAsset(texture)" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Sprite as SpriteT, ZoneCharacter } from '@/application/types'
|
||||
import { loadSpriteTextures } from '@/composables/gameComposable'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { Image, useScene } from 'phavuer'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
zoneCharacter: ZoneCharacter
|
||||
currentX: number
|
||||
currentY: number
|
||||
}>()
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const scene = useScene()
|
||||
|
||||
const texture = computed(() => {
|
||||
const { rotation, characterHair } = props.zoneCharacter.character
|
||||
const spriteId = characterHair?.sprite?.id
|
||||
const direction = [0, 6].includes(rotation) ? 'back' : 'front'
|
||||
|
||||
return `${spriteId}-${direction}`
|
||||
})
|
||||
|
||||
const isFlippedX = computed(() => [6, 4].includes(props.zoneCharacter.character.rotation ?? 0))
|
||||
|
||||
const imageProps = computed(() => {
|
||||
// Get the current sprite action based on direction
|
||||
const direction = [0, 6].includes(props.zoneCharacter.character.rotation ?? 0) ? 'back' : 'front'
|
||||
const spriteAction = props.zoneCharacter.character.characterHair?.sprite?.spriteActions?.find((spriteAction) => spriteAction.action === direction)
|
||||
|
||||
return {
|
||||
depth: 1,
|
||||
originX: Number(spriteAction?.originX) ?? 0,
|
||||
originY: Number(spriteAction?.originY) ?? 0,
|
||||
flipX: isFlippedX.value,
|
||||
texture: texture.value
|
||||
// y: props.zoneCharacter.isMoving ? Math.floor(Date.now() / 250) % 2 : 0
|
||||
}
|
||||
})
|
||||
|
||||
loadSpriteTextures(scene, props.zoneCharacter.character.characterHair?.sprite as SpriteT)
|
||||
.then(() => {})
|
||||
.catch((error) => {
|
||||
console.error('Error loading texture:', error)
|
||||
})
|
||||
</script>
|
51
src/components/game/character/partials/CharacterHair.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<Image v-bind="imageProps" v-if="gameStore.getLoadedAsset(texture)" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Sprite as SpriteT, ZoneCharacter } from '@/application/types'
|
||||
import { loadSpriteTextures } from '@/composables/gameComposable'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { Image, useScene } from 'phavuer'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
zoneCharacter: ZoneCharacter
|
||||
currentX: number
|
||||
currentY: number
|
||||
}>()
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const scene = useScene()
|
||||
|
||||
const texture = computed(() => {
|
||||
const { rotation, characterHair } = props.zoneCharacter.character
|
||||
const spriteId = characterHair?.sprite?.id
|
||||
const direction = [0, 6].includes(rotation) ? 'back' : 'front'
|
||||
|
||||
return `${spriteId}-${direction}`
|
||||
})
|
||||
|
||||
const isFlippedX = computed(() => [6, 4].includes(props.zoneCharacter.character.rotation ?? 0))
|
||||
|
||||
const imageProps = computed(() => {
|
||||
// Get the current sprite action based on direction
|
||||
const direction = [0, 6].includes(props.zoneCharacter.character.rotation ?? 0) ? 'back' : 'front'
|
||||
const spriteAction = props.zoneCharacter.character.characterHair?.sprite?.spriteActions?.find((spriteAction) => spriteAction.action === direction)
|
||||
|
||||
return {
|
||||
depth: 1,
|
||||
originX: Number(spriteAction?.originX) ?? 0,
|
||||
originY: Number(spriteAction?.originY) ?? 0,
|
||||
flipX: isFlippedX.value,
|
||||
texture: texture.value,
|
||||
y: props.zoneCharacter.isMoving ? Math.floor(Date.now() / 250) % 2 : 0
|
||||
}
|
||||
})
|
||||
|
||||
loadSpriteTextures(scene, props.zoneCharacter.character.characterHair?.sprite as SpriteT)
|
||||
.then(() => {})
|
||||
.catch((error) => {
|
||||
console.error('Error loading texture:', error)
|
||||
})
|
||||
</script>
|
46
src/components/game/character/partials/ChatBubble.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<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="20" />
|
||||
<Text @create="createChatText" :style="{ fontSize: 13, fontFamily: 'Arial', color: '#000' }" />
|
||||
</Container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ZoneCharacter } from '@/application/types'
|
||||
import { Container, refObj, RoundRectangle, Text, useGame } from 'phavuer'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
zoneCharacter: ZoneCharacter
|
||||
currentX: number
|
||||
currentY: number
|
||||
}>()
|
||||
|
||||
const game = useGame()
|
||||
const charChatContainer = refObj<Phaser.GameObjects.Container>()
|
||||
|
||||
const createChatBubble = (container: Phaser.GameObjects.Container) => {
|
||||
container.setName(`${props.zoneCharacter.character.name}_chatBubble`)
|
||||
}
|
||||
|
||||
const createChatText = (text: Phaser.GameObjects.Text) => {
|
||||
text.setName(`${props.zoneCharacter.character.name}_chatText`)
|
||||
text.setFontSize(13)
|
||||
text.setFontFamily('Arial')
|
||||
text.setOrigin(0.5, 10.9)
|
||||
|
||||
// Fix text alignment on Windows and Android
|
||||
if (game.device.os.windows || game.device.os.android) {
|
||||
text.setOrigin(0.5, 9.75)
|
||||
|
||||
if (game.device.browser.firefox) {
|
||||
text.setOrigin(0.5, 10.9)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
charChatContainer.value!.setName(`${props.zoneCharacter.character!.name}_chatContainer`)
|
||||
charChatContainer.value!.setVisible(false)
|
||||
})
|
||||
</script>
|
35
src/components/game/character/partials/Healthbar.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<Container :depth="999" :x="currentX" :y="currentY">
|
||||
<Text @create="createNicknameText" :text="props.zoneCharacter.character.name" />
|
||||
<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" />
|
||||
</Container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ZoneCharacter } from '@/application/types'
|
||||
import { Container, RoundRectangle, Text, useGame } from 'phavuer'
|
||||
|
||||
const props = defineProps<{
|
||||
zoneCharacter: ZoneCharacter
|
||||
currentX: number
|
||||
currentY: number
|
||||
}>()
|
||||
|
||||
const game = useGame()
|
||||
|
||||
const createNicknameText = (text: Phaser.GameObjects.Text) => {
|
||||
text.setFontSize(13)
|
||||
text.setFontFamily('Arial')
|
||||
text.setOrigin(0.5, 9)
|
||||
|
||||
// Fix text alignment on Windows and Android
|
||||
if (game.device.os.windows || game.device.os.android) {
|
||||
text.setOrigin(0.5, 8)
|
||||
|
||||
if (game.device.browser.firefox) {
|
||||
text.setOrigin(0.5, 9)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
14
src/components/game/zone/Characters.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<Character v-for="item in zoneStore.characters" :key="item.character.id" :tilemap="tilemap" :zoneCharacter="item" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Character from '@/components/game/character/Character.vue'
|
||||
import { useZoneStore } from '@/stores/zoneStore'
|
||||
|
||||
const zoneStore = useZoneStore()
|
||||
|
||||
const props = defineProps<{
|
||||
tilemap: Phaser.Tilemaps.Tilemap
|
||||
}>()
|
||||
</script>
|
50
src/components/game/zone/Zone.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<ZoneTiles :key="zoneStore.zone?.id ?? 0" @tileMap:create="tileMap = $event" />
|
||||
<ZoneObjects v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />
|
||||
<Characters v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ZoneCharacter, zoneLoadData } from '@/application/types'
|
||||
import Characters from '@/components/game/zone/Characters.vue'
|
||||
import ZoneObjects from '@/components/game/zone/ZoneObjects.vue'
|
||||
import ZoneTiles from '@/components/game/zone/ZoneTiles.vue'
|
||||
import { loadZoneTilesIntoScene } from '@/composables/zoneComposable'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneStore } from '@/stores/zoneStore'
|
||||
import { useScene } from 'phavuer'
|
||||
import { onUnmounted, ref } from 'vue'
|
||||
|
||||
const scene = useScene()
|
||||
const gameStore = useGameStore()
|
||||
const zoneStore = useZoneStore()
|
||||
|
||||
const tileMap = ref(null as Phaser.Tilemaps.Tilemap | null)
|
||||
|
||||
onUnmounted(() => {
|
||||
zoneStore.reset()
|
||||
gameStore.connection!.off('zone:character:teleport')
|
||||
gameStore.connection!.off('zone:character:join')
|
||||
gameStore.connection!.off('zone:character:leave')
|
||||
gameStore.connection!.off('zone:character:move')
|
||||
})
|
||||
|
||||
// Event listeners
|
||||
gameStore.connection!.on('zone:character:teleport', async (data: zoneLoadData) => {
|
||||
await loadZoneTilesIntoScene(data.zone.id, scene)
|
||||
zoneStore.setZone(data.zone)
|
||||
zoneStore.setCharacters(data.characters)
|
||||
})
|
||||
|
||||
gameStore.connection!.on('zone:character:join', async (data: ZoneCharacter) => {
|
||||
zoneStore.addCharacter(data)
|
||||
})
|
||||
|
||||
gameStore.connection!.on('zone:character:leave', (characterId: number) => {
|
||||
zoneStore.removeCharacter(characterId)
|
||||
})
|
||||
|
||||
gameStore.connection!.on('zone:character:move', (data: { characterId: number; positionX: number; positionY: number; rotation: number; isMoving: boolean }) => {
|
||||
zoneStore.updateCharacterPosition(data)
|
||||
})
|
||||
</script>
|
@ -3,8 +3,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ZoneObject from '@/components/game/zone/partials/ZoneObject.vue'
|
||||
import { useZoneStore } from '@/stores/zoneStore'
|
||||
import ZoneObject from '@/components/zone/partials/ZoneObject.vue'
|
||||
|
||||
const zoneStore = useZoneStore()
|
||||
|
@ -3,13 +3,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/config'
|
||||
import { useScene } from 'phavuer'
|
||||
import { useZoneStore } from '@/stores/zoneStore'
|
||||
import { onBeforeUnmount } from 'vue'
|
||||
import { FlattenZoneArray, setLayerTiles } from '@/composables/zoneComposable'
|
||||
import config from '@/application/config'
|
||||
import { unduplicateArray } from '@/application/utilities'
|
||||
import Controls from '@/components/utilities/Controls.vue'
|
||||
import { unduplicateArray } from '@/utilities'
|
||||
import { FlattenZoneArray, setLayerTiles } from '@/composables/zoneComposable'
|
||||
import { useZoneStore } from '@/stores/zoneStore'
|
||||
import { useScene } from 'phavuer'
|
||||
import { onBeforeUnmount } from 'vue'
|
||||
|
||||
const emit = defineEmits(['tileMap:create'])
|
||||
|
@ -3,12 +3,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { Image, useScene } from 'phavuer'
|
||||
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
||||
import type { AssetDataT, ZoneObject } from '@/application/types'
|
||||
import { loadTexture } from '@/composables/gameComposable'
|
||||
import type { AssetDataT, ZoneObject } from '@/types'
|
||||
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { Image, useScene } from 'phavuer'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
tilemap: Phaser.Tilemaps.Tilemap
|
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<Modal :isModalOpen="gameStore.uiSettings.isGmPanelOpen" @modal:close="() => gameStore.toggleGmPanel()" :modal-width="1000" :modal-height="650" :can-full-screen="true">
|
||||
<Modal :isModalOpen="gameStore.uiSettings.isGmPanelOpen" @modal:close="() => gameStore.toggleGmPanel()" :modal-width="1000" :modal-height="650" :is-full-screen="true" :bg-style="'dark'">
|
||||
<template #modalHeader>
|
||||
<div class="flex gap-1.5 flex-wrap">
|
||||
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">General</button>
|
||||
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">Users</button>
|
||||
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">Chats</button>
|
||||
<button @mousedown.stop class="btn-cyan active py-1.5 px-4 min-w-24">Asset manager</button>
|
||||
<button class="btn-cyan py-1.5 px-4 min-w-24" type="button" @click="() => zoneEditorStore.toggleActive()">Zone manager</button>
|
||||
<button class="btn-cyan py-1.5 px-4 min-w-24" type="button" @click="() => zoneEditorStore.toggleActive()">Map editor</button>
|
||||
</div>
|
||||
</template>
|
||||
<template #modalBody>
|
||||
@ -18,11 +18,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import AssetManager from '@/components/gameMaster/assetManager/AssetManager.vue'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
|
@ -1,85 +1,79 @@
|
||||
<template>
|
||||
<div class="flex h-full w-full relative">
|
||||
<div class="w-2/12 flex flex-col relative overflow-auto">
|
||||
<div class="flex gap-4 h-[calc(100%_-_32px)] w-[calc(100%_-_32px)] relative m-4">
|
||||
<div class="w-2/12 flex flex-col relative overflow-auto rounded-md default-border bg-gray p-2.5">
|
||||
<!-- Asset Categories -->
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'tiles' }" @click="() => (selectedCategory = 'tiles')">
|
||||
<span :class="{ 'text-white': selectedCategory === 'tiles' }">Tiles</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
<a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group" :class="{ 'bg-cyan': selectedCategory === 'tiles' }" @click="() => (selectedCategory = 'tiles')">
|
||||
<span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'tiles' }">Tiles</span>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'objects' }" @click="() => (selectedCategory = 'objects')">
|
||||
<span :class="{ 'text-white': selectedCategory === 'objects' }">Objects</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
<a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group" :class="{ 'bg-cyan': selectedCategory === 'objects' }" @click="() => (selectedCategory = 'objects')">
|
||||
<span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'objects' }">Objects</span>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'sprites' }" @click="() => (selectedCategory = 'sprites')">
|
||||
<span :class="{ 'text-white': selectedCategory === 'sprites' }">Sprites</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
<a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group" :class="{ 'bg-cyan': selectedCategory === 'sprites' }" @click="() => (selectedCategory = 'sprites')">
|
||||
<span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'sprites' }">Sprites</span>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer">
|
||||
<span>Items</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
<a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group" :class="{ 'bg-cyan': selectedCategory === 'items' }" @click="() => (selectedCategory = 'items')">
|
||||
<span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'items' }">Items</span>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer">
|
||||
<span>NPC's</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
<a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group">
|
||||
<span class="group-hover:text-white">NPC's</span>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'shops' }" @click="() => (selectedCategory = 'shops')">
|
||||
<span :class="{ 'text-white': selectedCategory === 'shops' }">Shops</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
<a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group" :class="{ 'bg-cyan/80': selectedCategory === 'shops' }" @click="() => (selectedCategory = 'shops')">
|
||||
<span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'shops' }">Shops</span>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'characterTypes' }" @click="() => (selectedCategory = 'characterTypes')">
|
||||
<span :class="{ 'text-white': selectedCategory === 'characterTypes' }">Character types</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
<a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group" :class="{ 'bg-cyan/80': selectedCategory === 'characterTypes' }" @click="() => (selectedCategory = 'characterTypes')">
|
||||
<span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'characterTypes' }">Character types</span>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'characterHair' }" @click="() => (selectedCategory = 'characterHair')">
|
||||
<span :class="{ 'text-white': selectedCategory === 'characterHair' }">Character hair</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
<a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group" :class="{ 'bg-cyan/80': selectedCategory === 'characterHair' }" @click="() => (selectedCategory = 'characterHair')">
|
||||
<span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'characterHair' }">Character hair</span>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer">
|
||||
<span>Mounts</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
<a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group">
|
||||
<span class="group-hover:text-white">Mounts</span>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer">
|
||||
<span>Pets</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
<a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group">
|
||||
<span class="group-hover:text-white">Pets</span>
|
||||
</a>
|
||||
<a class="relative p-2.5 hover:cursor-pointer">
|
||||
<span>Emoticons</span>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
<a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group">
|
||||
<span class="group-hover:text-white">Emoticons</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="absolute w-px bg-gray-500 h-full top-0 left-1/6"></div>
|
||||
|
||||
<!-- Assets list -->
|
||||
<div class="overflow-auto h-full w-4/12 flex flex-col relative">
|
||||
<TileList v-if="selectedCategory === 'tiles'" />
|
||||
<ObjectList v-if="selectedCategory === 'objects'" />
|
||||
<SpriteList v-if="selectedCategory === 'sprites'" />
|
||||
<ItemList v-if="selectedCategory === 'items'" />
|
||||
<CharacterTypeList v-if="selectedCategory === 'characterTypes'" />
|
||||
<CharacterHairList v-if="selectedCategory === 'characterHair'" />
|
||||
</div>
|
||||
|
||||
<div class="absolute w-px bg-gray-500 h-full top-0 left-1/2"></div>
|
||||
|
||||
<!-- Asset details -->
|
||||
<div class="flex w-1/2 after:hidden flex-col relative overflow-auto">
|
||||
<div class="flex w-7/12 after:hidden flex-col relative overflow-auto">
|
||||
<TileDetails v-if="selectedCategory === 'tiles' && assetManagerStore.selectedTile" />
|
||||
<ObjectDetails v-if="selectedCategory === 'objects' && assetManagerStore.selectedObject" />
|
||||
<SpriteDetails v-if="selectedCategory === 'sprites' && assetManagerStore.selectedSprite" />
|
||||
<ItemDetails v-if="selectedCategory === 'items' && assetManagerStore.selectedItem" />
|
||||
<CharacterTypeDetails v-if="selectedCategory === 'characterTypes' && assetManagerStore.selectedCharacterType" />
|
||||
<CharacterHairDetails v-if="selectedCategory === 'characterHair' && assetManagerStore.selectedCharacterHair" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import TileList from '@/components/gameMaster/assetManager/partials/tile/TileList.vue'
|
||||
import TileDetails from '@/components/gameMaster/assetManager/partials/tile/TileDetails.vue'
|
||||
import ObjectList from '@/components/gameMaster/assetManager/partials/object/ObjectList.vue'
|
||||
import ObjectDetails from '@/components/gameMaster/assetManager/partials/object/ObjectDetails.vue'
|
||||
import SpriteList from '@/components/gameMaster/assetManager/partials/sprite/SpriteList.vue'
|
||||
import SpriteDetails from '@/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue'
|
||||
import CharacterTypeList from '@/components/gameMaster/assetManager/partials/characterType/CharacterTypeList.vue'
|
||||
import CharacterHairDetails from '@/components/gameMaster/assetManager/partials/characterHair/CharacterHairDetails.vue'
|
||||
import CharacterHairList from '@/components/gameMaster/assetManager/partials/characterHair/CharacterHairList.vue'
|
||||
import CharacterTypeDetails from '@/components/gameMaster/assetManager/partials/characterType/CharacterTypeDetails.vue'
|
||||
import CharacterTypeList from '@/components/gameMaster/assetManager/partials/characterType/CharacterTypeList.vue'
|
||||
import ItemDetails from '@/components/gameMaster/assetManager/partials/item/itemDetails.vue'
|
||||
import ItemList from '@/components/gameMaster/assetManager/partials/item/itemList.vue'
|
||||
import ObjectDetails from '@/components/gameMaster/assetManager/partials/object/ObjectDetails.vue'
|
||||
import ObjectList from '@/components/gameMaster/assetManager/partials/object/ObjectList.vue'
|
||||
import SpriteDetails from '@/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue'
|
||||
import SpriteList from '@/components/gameMaster/assetManager/partials/sprite/SpriteList.vue'
|
||||
import TileDetails from '@/components/gameMaster/assetManager/partials/tile/TileDetails.vue'
|
||||
import TileList from '@/components/gameMaster/assetManager/partials/tile/TileList.vue'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
const selectedCategory = ref('tiles')
|
||||
|
@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div class="h-full overflow-auto">
|
||||
<div class="p-2.5 block rounded-md default-border bg-gray">
|
||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveCharacterHair">
|
||||
<div class="form-field-full">
|
||||
<label for="name">Name</label>
|
||||
<input v-model="characterName" class="input-field" type="text" name="name" placeholder="Character Type Name" />
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="gender">Gender</label>
|
||||
<select v-model="characterGender" class="input-field" name="gender">
|
||||
<option v-for="gender in genderOptions" :key="gender" :value="gender">{{ gender }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="isSelectable">Is selectable</label>
|
||||
<select v-model="characterIsSelectable" class="input-field" name="isSelectable">
|
||||
<option :value="false">No</option>
|
||||
<option :value="true">Yes</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="spriteId">Sprite</label>
|
||||
<select v-model="characterSpriteId" class="input-field" name="spriteId">
|
||||
<option disabled selected value="">Select sprite</option>
|
||||
<option v-for="sprite in assetManagerStore.spriteList" :key="sprite.id" :value="sprite.id">{{ sprite.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button>
|
||||
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="removeCharacterHair">Remove</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CharacterGender, CharacterHair, Sprite } from '@/application/types'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
||||
const selectedCharacterHair = computed(() => assetManagerStore.selectedCharacterHair)
|
||||
|
||||
const characterName = ref('')
|
||||
const characterGender = ref<CharacterGender>('MALE' as CharacterGender.MALE)
|
||||
const characterIsSelectable = ref<boolean>(false)
|
||||
const characterSpriteId = ref<string | null | undefined>(null)
|
||||
|
||||
const genderOptions: CharacterGender[] = ['MALE' as CharacterGender.MALE, 'FEMALE' as CharacterGender.FEMALE]
|
||||
|
||||
if (!selectedCharacterHair.value) {
|
||||
console.error('No character hair selected')
|
||||
}
|
||||
|
||||
if (selectedCharacterHair.value) {
|
||||
characterName.value = selectedCharacterHair.value.name
|
||||
characterGender.value = selectedCharacterHair.value.gender
|
||||
characterIsSelectable.value = selectedCharacterHair.value.isSelectable
|
||||
characterSpriteId.value = selectedCharacterHair.value.spriteId
|
||||
}
|
||||
|
||||
function removeCharacterHair() {
|
||||
if (!selectedCharacterHair.value) return
|
||||
|
||||
gameStore.connection?.emit('gm:characterHair:remove', { id: selectedCharacterHair.value.id }, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to remove character hair')
|
||||
return
|
||||
}
|
||||
refreshCharacterHairList()
|
||||
})
|
||||
}
|
||||
|
||||
function refreshCharacterHairList(unsetSelectedCharacterHair = true) {
|
||||
gameStore.connection?.emit('gm:characterHair:list', {}, (response: CharacterHair[]) => {
|
||||
assetManagerStore.setCharacterHairList(response)
|
||||
|
||||
if (unsetSelectedCharacterHair) {
|
||||
assetManagerStore.setSelectedCharacterHair(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function saveCharacterHair() {
|
||||
const characterHairData = {
|
||||
id: selectedCharacterHair.value!.id,
|
||||
name: characterName.value,
|
||||
gender: characterGender.value,
|
||||
isSelectable: characterIsSelectable.value,
|
||||
spriteId: characterSpriteId.value
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:characterHair:update', characterHairData, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to save character type')
|
||||
return
|
||||
}
|
||||
refreshCharacterHairList(false)
|
||||
})
|
||||
}
|
||||
|
||||
watch(selectedCharacterHair, (characterHair: CharacterHair | null) => {
|
||||
if (!characterHair) return
|
||||
characterName.value = characterHair.name
|
||||
characterGender.value = characterHair.gender
|
||||
characterIsSelectable.value = characterHair.isSelectable
|
||||
characterSpriteId.value = characterHair.spriteId
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (!selectedCharacterHair.value) return
|
||||
|
||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
assetManagerStore.setSelectedCharacterHair(null)
|
||||
})
|
||||
</script>
|
@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div class="relative mb-5 flex items-center gap-x-2.5">
|
||||
<input v-model="searchQuery" class="input-field flex-grow" placeholder="Search..." @input="handleSearch" />
|
||||
<label for="create-character" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2.5 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<button class="p-0 h-5" id="create-character" @click="createNewCharacterHair">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="white">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
<div v-bind="containerProps" class="overflow-y-auto relative p-2.5 rounded-md default-border bg-gray" @scroll="onScroll">
|
||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||
<a
|
||||
v-for="{ data: characterHair } in list"
|
||||
:key="characterHair.id"
|
||||
class="relative p-2.5 cursor-pointer block rounded hover:bg-cyan group"
|
||||
:class="{ 'bg-cyan': assetManagerStore.selectedCharacterHair?.id === characterHair.id }"
|
||||
@click="assetManagerStore.setSelectedCharacterHair(characterHair as CharacterHair)"
|
||||
>
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span class="group-hover:text-white" :class="{ 'text-white': assetManagerStore.selectedCharacterHair?.id === characterHair.id }">{{ characterHair.name }}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="absolute w-12 h-12 bottom-2.5 right-2.5">
|
||||
<button class="fixed min-w-[unset] w-12 h-12 rounded-md bg-cyan p-0 hover:bg-cyan-800" v-show="hasScrolled" @click="toTop">
|
||||
<img class="invert w-8 h-8 center-element rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CharacterHair } from '@/application/types'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
const hasScrolled = ref(false)
|
||||
const elementToScroll = ref()
|
||||
|
||||
const handleSearch = () => {
|
||||
// Trigger a re-render of the virtual list
|
||||
virtualList.value?.scrollTo(0)
|
||||
}
|
||||
|
||||
const createNewCharacterHair = () => {
|
||||
gameStore.connection?.emit('gm:characterHair:create', {}, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to create new character type')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:characterHair:list', {}, (response: CharacterHair[]) => {
|
||||
assetManagerStore.setCharacterHairList(response)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const filteredCharacterHairs = computed(() => {
|
||||
if (!searchQuery.value) {
|
||||
return assetManagerStore.characterHairList
|
||||
}
|
||||
return assetManagerStore.characterHairList.filter((character) => character.name.toLowerCase().includes(searchQuery.value.toLowerCase()))
|
||||
})
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(filteredCharacterHairs, {
|
||||
itemHeight: 48
|
||||
})
|
||||
|
||||
const virtualList = ref({ scrollTo })
|
||||
|
||||
const onScroll = () => {
|
||||
let scrollTop = elementToScroll.value.style.marginTop.replace('px', '')
|
||||
|
||||
if (scrollTop > 80) {
|
||||
hasScrolled.value = true
|
||||
} else if (scrollTop <= 80) {
|
||||
hasScrolled.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function toTop() {
|
||||
virtualList.value?.scrollTo(0)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit('gm:characterHair:list', {}, (response: CharacterHair[]) => {
|
||||
assetManagerStore.setCharacterHairList(response)
|
||||
})
|
||||
})
|
||||
</script>
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="h-full overflow-auto">
|
||||
<div class="m-2.5 p-2.5 block">
|
||||
<div class="p-2.5 block rounded-md default-border bg-gray">
|
||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveCharacterType">
|
||||
<div class="form-field-full">
|
||||
<label for="name">Name</label>
|
||||
@ -19,8 +19,18 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="spriteId">Sprite ID</label>
|
||||
<input v-model="characterSpriteId" class="input-field" type="text" name="spriteId" placeholder="Sprite ID" />
|
||||
<label for="isSelectable">Is selectable</label>
|
||||
<select v-model="characterIsSelectable" class="input-field" name="isSelectable">
|
||||
<option :value="false">No</option>
|
||||
<option :value="true">Yes</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="spriteId">Sprite</label>
|
||||
<select v-model="characterSpriteId" class="input-field" name="spriteId">
|
||||
<option disabled selected value="">Select sprite</option>
|
||||
<option v-for="sprite in assetManagerStore.spriteList" :key="sprite.id" :value="sprite.id">{{ sprite.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button>
|
||||
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="removeCharacterType">Remove</button>
|
||||
@ -30,10 +40,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CharacterType, CharacterGender, CharacterRace } from '@/types'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import type { CharacterGender, CharacterRace, CharacterType, Sprite } from '@/application/types'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
@ -43,7 +53,8 @@ const selectedCharacterType = computed(() => assetManagerStore.selectedCharacter
|
||||
const characterName = ref('')
|
||||
const characterGender = ref<CharacterGender>('MALE' as CharacterGender.MALE)
|
||||
const characterRace = ref<CharacterRace>('HUMAN' as CharacterRace.HUMAN)
|
||||
const characterSpriteId = ref('')
|
||||
const characterIsSelectable = ref<boolean>(false)
|
||||
const characterSpriteId = ref<string | null | undefined>(null)
|
||||
|
||||
const genderOptions: CharacterGender[] = ['MALE' as CharacterGender.MALE, 'FEMALE' as CharacterGender.FEMALE]
|
||||
const raceOptions: CharacterRace[] = ['HUMAN' as CharacterRace.HUMAN, 'ELF' as CharacterRace.ELF, 'DWARF' as CharacterRace.DWARF, 'ORC' as CharacterRace.ORC, 'GOBLIN' as CharacterRace.GOBLIN]
|
||||
@ -56,6 +67,7 @@ if (selectedCharacterType.value) {
|
||||
characterName.value = selectedCharacterType.value.name
|
||||
characterGender.value = selectedCharacterType.value.gender
|
||||
characterRace.value = selectedCharacterType.value.race
|
||||
characterIsSelectable.value = selectedCharacterType.value.isSelectable
|
||||
characterSpriteId.value = selectedCharacterType.value.spriteId
|
||||
}
|
||||
|
||||
@ -83,10 +95,11 @@ function refreshCharacterTypeList(unsetSelectedCharacterType = true) {
|
||||
|
||||
function saveCharacterType() {
|
||||
const characterTypeData = {
|
||||
id: selectedCharacterType.value?.id,
|
||||
id: selectedCharacterType.value!.id,
|
||||
name: characterName.value,
|
||||
gender: characterGender.value,
|
||||
race: characterRace.value,
|
||||
isSelectable: characterIsSelectable.value,
|
||||
spriteId: characterSpriteId.value
|
||||
}
|
||||
|
||||
@ -104,11 +117,16 @@ watch(selectedCharacterType, (characterType: CharacterType | null) => {
|
||||
characterName.value = characterType.name
|
||||
characterGender.value = characterType.gender
|
||||
characterRace.value = characterType.race
|
||||
characterIsSelectable.value = characterType.isSelectable
|
||||
characterSpriteId.value = characterType.spriteId
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (!selectedCharacterType.value) return
|
||||
|
||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
@ -1,36 +1,42 @@
|
||||
<template>
|
||||
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
||||
<div class="relative mb-5 flex items-center gap-x-2.5">
|
||||
<input v-model="searchQuery" class="input-field flex-grow" placeholder="Search..." @input="handleSearch" />
|
||||
<label for="create-character" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<label for="create-character" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2.5 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<button class="p-0 h-5" id="create-character" @click="createNewCharacterType">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="white">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</button>
|
||||
</label>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</div>
|
||||
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
||||
<div v-bind="containerProps" class="overflow-y-auto relative p-2.5 rounded-md default-border bg-gray" @scroll="onScroll">
|
||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||
<a v-for="{ data: characterType } in list" :key="characterType.id" class="relative p-2.5 cursor-pointer block" :class="{ 'bg-cyan/80': assetManagerStore.selectedCharacterType?.id === characterType.id }" @click="assetManagerStore.setSelectedCharacterType(characterType as CharacterType)">
|
||||
<a
|
||||
v-for="{ data: characterType } in list"
|
||||
:key="characterType.id"
|
||||
class="relative p-2.5 cursor-pointer block rounded hover:bg-cyan group"
|
||||
:class="{ 'bg-cyan': assetManagerStore.selectedCharacterType?.id === characterType.id }"
|
||||
@click="assetManagerStore.setSelectedCharacterType(characterType as CharacterType)"
|
||||
>
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span>{{ characterType.name }}</span>
|
||||
<span class="group-hover:text-white" :class="{ 'text-white': assetManagerStore.selectedCharacterType?.id === characterType.id }">{{ characterType.name }}</span>
|
||||
</div>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
</div>
|
||||
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan p-0 hover:bg-cyan-800" v-show="hasScrolled" @click="toTop">
|
||||
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||
<div class="absolute w-12 h-12 bottom-2.5 right-2.5">
|
||||
<button class="fixed min-w-[unset] w-12 h-12 rounded-md bg-cyan p-0 hover:bg-cyan-800" v-show="hasScrolled" @click="toTop">
|
||||
<img class="invert w-8 h-8 center-element rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import type { CharacterType } from '@/application/types'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import type { CharacterType } from '@/types'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div class="h-full overflow-auto">
|
||||
<div class="p-2.5 block rounded-md default-border bg-gray">
|
||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveItem">
|
||||
<div class="form-field-full">
|
||||
<label for="name">Name</label>
|
||||
<input v-model="itemName" class="input-field" type="text" name="name" placeholder="Item Name" />
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="description">Description</label>
|
||||
<input v-model="itemDescription" class="input-field" type="text" name="description" placeholder="Item Description" />
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="itemType">Type</label>
|
||||
<select v-model="itemType" class="input-field" name="itemType">
|
||||
<option v-for="type in itemTypeOptions" :key="type" :value="type">{{ type }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="rarity">Rarity</label>
|
||||
<select v-model="itemRarity" class="input-field" name="rarity">
|
||||
<option v-for="rarity in rarityOptions" :key="rarity" :value="rarity">{{ rarity }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="stackable">Stackable</label>
|
||||
<select v-model="itemStackable" class="input-field" name="stackable">
|
||||
<option :value="false">No</option>
|
||||
<option :value="true">Yes</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="spriteId">Sprite</label>
|
||||
<select v-model="itemSpriteId" class="input-field" name="spriteId">
|
||||
<option disabled selected value="">Select sprite</option>
|
||||
<option v-for="sprite in assetManagerStore.spriteList" :key="sprite.id" :value="sprite.id">{{ sprite.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button>
|
||||
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="removeItem">Remove</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Item, ItemRarity, ItemType } from '@/application/types'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
||||
const selectedItem = computed(() => assetManagerStore.selectedItem)
|
||||
|
||||
const itemName = ref('')
|
||||
const itemDescription = ref('')
|
||||
const itemType = ref<ItemType>('WEAPON' as ItemType)
|
||||
const itemRarity = ref<ItemRarity>('COMMON' as ItemRarity)
|
||||
const itemStackable = ref<boolean>(false)
|
||||
const itemSpriteId = ref<string | null | undefined>(null)
|
||||
|
||||
const itemTypeOptions: ItemType[] = ['WEAPON', 'HELMET', 'CHEST', 'LEGS', 'BOOTS', 'GLOVES', 'RING', 'NECKLACE']
|
||||
const rarityOptions: ItemRarity[] = ['COMMON', 'UNCOMMON', 'RARE', 'EPIC', 'LEGENDARY']
|
||||
|
||||
if (!selectedItem.value) {
|
||||
console.error('No item selected')
|
||||
}
|
||||
|
||||
if (selectedItem.value) {
|
||||
itemName.value = selectedItem.value.name
|
||||
itemDescription.value = selectedItem.value.description || ''
|
||||
itemType.value = selectedItem.value.itemType
|
||||
itemRarity.value = selectedItem.value.rarity
|
||||
itemStackable.value = selectedItem.value.stackable
|
||||
itemSpriteId.value = selectedItem.value.spriteId
|
||||
}
|
||||
|
||||
function removeItem() {
|
||||
if (!selectedItem.value) return
|
||||
|
||||
gameStore.connection?.emit('gm:item:remove', { id: selectedItem.value.id }, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to remove item')
|
||||
return
|
||||
}
|
||||
refreshItemList()
|
||||
})
|
||||
}
|
||||
|
||||
function refreshItemList(unsetSelectedItem = true) {
|
||||
gameStore.connection?.emit('gm:item:list', {}, (response: Item[]) => {
|
||||
assetManagerStore.setItemList(response)
|
||||
|
||||
if (unsetSelectedItem) {
|
||||
assetManagerStore.setSelectedItem(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function saveItem() {
|
||||
const itemData = {
|
||||
id: selectedItem.value!.id,
|
||||
name: itemName.value,
|
||||
description: itemDescription.value,
|
||||
itemType: itemType.value,
|
||||
rarity: itemRarity.value,
|
||||
stackable: itemStackable.value,
|
||||
spriteId: itemSpriteId.value
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:item:update', itemData, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to save item')
|
||||
return
|
||||
}
|
||||
refreshItemList(false)
|
||||
})
|
||||
}
|
||||
|
||||
watch(selectedItem, (item: Item | null) => {
|
||||
if (!item) return
|
||||
itemName.value = item.name
|
||||
itemDescription.value = item.description || ''
|
||||
itemType.value = item.itemType
|
||||
itemRarity.value = item.rarity
|
||||
itemStackable.value = item.stackable
|
||||
itemSpriteId.value = item.spriteId
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (!selectedItem.value) return
|
||||
|
||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
assetManagerStore.setSelectedItem(null)
|
||||
})
|
||||
</script>
|
@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="relative mb-5 flex items-center gap-x-2.5">
|
||||
<input v-model="searchQuery" class="input-field flex-grow" placeholder="Search..." @input="handleSearch" />
|
||||
<label for="create-item" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2.5 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<button class="p-0 h-5" id="create-item" @click="createNewItem">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="white">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
<div v-bind="containerProps" class="overflow-y-auto relative p-2.5 rounded-md default-border bg-gray" @scroll="onScroll">
|
||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||
<a v-for="{ data: item } in list" :key="item.id" class="relative p-2.5 cursor-pointer block rounded hover:bg-cyan group" :class="{ 'bg-cyan': assetManagerStore.selectedItem?.id === item.id }" @click="assetManagerStore.setSelectedItem(item as Item)">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span class="group-hover:text-white" :class="{ 'text-white': assetManagerStore.selectedItem?.id === item.id }">
|
||||
{{ item.name }}
|
||||
<small class="text-gray-400">({{ item.itemType }})</small>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="absolute w-12 h-12 bottom-2.5 right-2.5">
|
||||
<button class="fixed min-w-[unset] w-12 h-12 rounded-md bg-cyan p-0 hover:bg-cyan-800" v-show="hasScrolled" @click="toTop">
|
||||
<img class="invert w-8 h-8 center-element rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Item } from '@/application/types'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
const hasScrolled = ref(false)
|
||||
const elementToScroll = ref()
|
||||
|
||||
const handleSearch = () => {
|
||||
virtualList.value?.scrollTo(0)
|
||||
}
|
||||
|
||||
const createNewItem = () => {
|
||||
gameStore.connection?.emit('gm:item:create', {}, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to create new item')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:item:list', {}, (response: Item[]) => {
|
||||
assetManagerStore.setItemList(response)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const filteredItems = computed(() => {
|
||||
if (!searchQuery.value) {
|
||||
return assetManagerStore.itemList
|
||||
}
|
||||
return assetManagerStore.itemList.filter((item) => item.name.toLowerCase().includes(searchQuery.value.toLowerCase()) || item.itemType.toLowerCase().includes(searchQuery.value.toLowerCase()))
|
||||
})
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(filteredItems, {
|
||||
itemHeight: 48
|
||||
})
|
||||
|
||||
const virtualList = ref({ scrollTo })
|
||||
|
||||
const onScroll = () => {
|
||||
let scrollTop = elementToScroll.value.style.marginTop.replace('px', '')
|
||||
|
||||
if (scrollTop > 80) {
|
||||
hasScrolled.value = true
|
||||
} else if (scrollTop <= 80) {
|
||||
hasScrolled.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function toTop() {
|
||||
virtualList.value?.scrollTo(0)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit('gm:item:list', {}, (response: Item[]) => {
|
||||
assetManagerStore.setItemList(response)
|
||||
})
|
||||
})
|
||||
</script>
|
@ -1,12 +1,9 @@
|
||||
<template>
|
||||
<div class="h-full overflow-auto">
|
||||
<div class="relative p-2.5 flex flex-col items-center justify-between h-72">
|
||||
<div class="filler"></div>
|
||||
<div class="relative p-2.5 flex flex-col items-center justify-center h-72 rounded-md default-border bg-gray">
|
||||
<img class="max-h-56" :src="`${config.server_endpoint}/assets/objects/${selectedObject?.id}.png`" :alt="'Object ' + selectedObject?.id" />
|
||||
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="removeObject">Remove</button>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</div>
|
||||
<div class="m-2.5 p-2.5 block">
|
||||
<div class="mt-5 block">
|
||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveObject">
|
||||
<div class="form-field-full">
|
||||
<label for="name">Name</label>
|
||||
@ -21,19 +18,19 @@
|
||||
<input v-model="objectOriginY" class="input-field" type="number" step="any" name="origin-y" placeholder="Origin Y" />
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="origin-x">Tags</label>
|
||||
<label for="tags">Tags</label>
|
||||
<ChipsInput v-model="objectTags" @update:modelValue="objectTags = $event" />
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="origin-x">Is animated</label>
|
||||
<label for="is-animated">Is animated</label>
|
||||
<select v-model="objectIsAnimated" class="input-field" name="is-animated">
|
||||
<option :value="false">No</option>
|
||||
<option :value="true">Yes</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<label for="frame-speed">Frame speed</label>
|
||||
<input v-model="objectFrameSpeed" class="input-field" type="number" step="any" name="frame-speed" placeholder="Frame speed" />
|
||||
<label for="frame-speed">Frame rate</label>
|
||||
<input v-model="objectFrameRate" class="input-field" type="number" step="any" name="frame-speed" placeholder="Frame rate" />
|
||||
</div>
|
||||
<div class="form-field-half">
|
||||
<label for="frame-width">Frame width</label>
|
||||
@ -43,20 +40,23 @@
|
||||
<label for="frame-height">Frame height</label>
|
||||
<input v-model="objectFrameHeight" class="input-field" type="number" step="any" name="frame-height" placeholder="Frame height" />
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button>
|
||||
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="removeObject">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Object } from '@/types'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import config from '@/config'
|
||||
import config from '@/application/config'
|
||||
import type { Object } from '@/application/types'
|
||||
import ChipsInput from '@/components/forms/ChipsInput.vue'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
@ -69,7 +69,7 @@ const objectTags = ref<string[]>([])
|
||||
const objectOriginX = ref(0)
|
||||
const objectOriginY = ref(0)
|
||||
const objectIsAnimated = ref(false)
|
||||
const objectFrameSpeed = ref(0)
|
||||
const objectFrameRate = ref(0)
|
||||
const objectFrameWidth = ref(0)
|
||||
const objectFrameHeight = ref(0)
|
||||
|
||||
@ -83,7 +83,7 @@ if (selectedObject.value) {
|
||||
objectOriginX.value = selectedObject.value.originX
|
||||
objectOriginY.value = selectedObject.value.originY
|
||||
objectIsAnimated.value = selectedObject.value.isAnimated
|
||||
objectFrameSpeed.value = selectedObject.value.frameSpeed
|
||||
objectFrameRate.value = selectedObject.value.frameRate
|
||||
objectFrameWidth.value = selectedObject.value.frameWidth
|
||||
objectFrameHeight.value = selectedObject.value.frameHeight
|
||||
}
|
||||
@ -127,7 +127,7 @@ function saveObject() {
|
||||
originX: objectOriginX.value,
|
||||
originY: objectOriginY.value,
|
||||
isAnimated: objectIsAnimated.value,
|
||||
frameSpeed: objectFrameSpeed.value,
|
||||
frameRate: objectFrameRate.value,
|
||||
frameWidth: objectFrameWidth.value,
|
||||
frameHeight: objectFrameHeight.value
|
||||
},
|
||||
@ -148,7 +148,7 @@ watch(selectedObject, (object: Object | null) => {
|
||||
objectOriginX.value = object.originX
|
||||
objectOriginY.value = object.originY
|
||||
objectIsAnimated.value = object.isAnimated
|
||||
objectFrameSpeed.value = object.frameSpeed
|
||||
objectFrameRate.value = object.frameRate
|
||||
objectFrameWidth.value = object.frameWidth
|
||||
objectFrameHeight.value = object.frameHeight
|
||||
})
|
||||
|
@ -1,39 +1,39 @@
|
||||
<template>
|
||||
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
||||
<div class="relative mb-5 flex items-center gap-x-2.5">
|
||||
<input v-model="searchQuery" class="input-field flex-grow" placeholder="Search..." @input="handleSearch" />
|
||||
<label for="upload-asset" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<label for="upload-asset" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2.5 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<input class="hidden" id="upload-asset" ref="objectUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</label>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</div>
|
||||
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
||||
<div v-bind="containerProps" class="overflow-y-auto relative p-2.5 rounded-md default-border bg-gray" @scroll="onScroll">
|
||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||
<a v-for="{ data: object } in list" :key="object.id" class="relative p-2.5 cursor-pointer block" :class="{ 'bg-cyan/80': assetManagerStore.selectedObject?.id === object.id }" @click="assetManagerStore.setSelectedObject(object as Object)">
|
||||
<a v-for="{ data: object } in list" :key="object.id" class="relative p-2.5 cursor-pointer block rounded hover:bg-cyan group" :class="{ 'bg-cyan': assetManagerStore.selectedObject?.id === object.id }" @click="assetManagerStore.setSelectedObject(object as Object)">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<div class="h-7 w-16 max-w-16 flex justify-center">
|
||||
<img class="h-7" :src="`${config.server_endpoint}/assets/objects/${object.id}.png`" alt="Object" />
|
||||
</div>
|
||||
<span>{{ object.name }}</span>
|
||||
<span :class="{ 'text-white': assetManagerStore.selectedObject?.id === object.id }">{{ object.name }}</span>
|
||||
</div>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
</div>
|
||||
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan p-0 hover:bg-cyan-800" v-show="hasScrolled" @click="toTop">
|
||||
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||
<div class="absolute w-12 h-12 bottom-2.5 right-2.5">
|
||||
<button class="fixed min-w-[unset] w-12 h-12 rounded-md bg-cyan p-0 hover:bg-cyan-800" v-show="hasScrolled" @click="toTop">
|
||||
<img class="invert w-8 h-8 center-element rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/config'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import config from '@/application/config'
|
||||
import type { Object } from '@/application/types'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import type { Object } from '@/types'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const objectUploadField = ref(null)
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="h-full overflow-auto">
|
||||
<div class="relative p-4 flex flex-col">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="relative flex flex-col">
|
||||
<div class="flex flex-wrap gap-2 p-2.5 rounded-md default-border bg-gray">
|
||||
<div class="w-full flex flex-col">
|
||||
<label class="mb-1.5 font-titles" for="name">Name</label>
|
||||
<input v-model="spriteName" class="input-field" type="text" name="name" placeholder="New sprite" />
|
||||
@ -10,7 +10,11 @@
|
||||
<div class="w-full flex gap-2 mt-2 pb-4 relative">
|
||||
<button class="btn-cyan px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="saveSprite">Save</button>
|
||||
<button class="btn-red px-4 py-2 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="deleteSprite">Delete</button>
|
||||
<div class="w-[calc(100%_+_32px)] absolute left-[-15px] bottom-0 h-px bg-gray-500"></div>
|
||||
<button class="btn bg-indigo-500 hover:bg-indigo-600 rounded text-white px-4 py-2 flex-1 sm:flex-none" type="button" @click.prevent="copySprite">
|
||||
<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="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -51,8 +55,8 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field-full" v-if="action.isAnimated">
|
||||
<label for="frame-speed">Frame speed</label>
|
||||
<input v-model.number="action.frameSpeed" class="input-field" type="number" step="any" name="frame-speed" placeholder="Frame speed" />
|
||||
<label for="frame-speed">Frame rate</label>
|
||||
<input v-model.number="action.frameRate" class="input-field" type="number" step="any" name="frame-speed" placeholder="Frame rate" />
|
||||
</div>
|
||||
<div class="form-field-full">
|
||||
<SpriteActionsInput v-model="action.sprites" />
|
||||
@ -65,13 +69,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Sprite, SpriteAction } from '@/types'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import type { Sprite, SpriteAction } from '@/application/types'
|
||||
import { uuidv4 } from '@/application/utilities'
|
||||
import SpriteActionsInput from '@/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue'
|
||||
import Accordion from '@/components/utilities/Accordion.vue'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import Accordion from '@/components/utilities/Accordion.vue'
|
||||
import SpriteActionsInput from '@/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue'
|
||||
import { uuidv4 } from '@/utilities'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
@ -87,7 +91,7 @@ if (!selectedSprite.value) {
|
||||
|
||||
if (selectedSprite.value) {
|
||||
spriteName.value = selectedSprite.value.name
|
||||
spriteActions.value = selectedSprite.value.spriteActions
|
||||
spriteActions.value = sortSpriteActions(selectedSprite.value.spriteActions)
|
||||
}
|
||||
|
||||
function deleteSprite() {
|
||||
@ -100,6 +104,16 @@ function deleteSprite() {
|
||||
})
|
||||
}
|
||||
|
||||
function copySprite() {
|
||||
gameStore.connection?.emit('gm:sprite:copy', { id: selectedSprite.value?.id }, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to copy sprite')
|
||||
return
|
||||
}
|
||||
refreshSpriteList(false)
|
||||
})
|
||||
}
|
||||
|
||||
function refreshSpriteList(unsetSelectedSprite = true) {
|
||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
@ -128,7 +142,7 @@ function saveSprite() {
|
||||
originY: action.originY,
|
||||
isAnimated: action.isAnimated,
|
||||
isLooping: action.isLooping,
|
||||
frameSpeed: action.frameSpeed,
|
||||
frameRate: action.frameRate,
|
||||
frameWidth: action.frameWidth,
|
||||
frameHeight: action.frameHeight
|
||||
}
|
||||
@ -148,7 +162,7 @@ function addNewImage() {
|
||||
if (!selectedSprite.value) return
|
||||
|
||||
const newImage: SpriteAction = {
|
||||
id: uuidv4(), // Temporary ID, should be replaced by server-generated ID
|
||||
id: uuidv4(),
|
||||
spriteId: selectedSprite.value.id,
|
||||
sprite: selectedSprite.value,
|
||||
action: 'new_action',
|
||||
@ -157,7 +171,7 @@ function addNewImage() {
|
||||
originY: 0,
|
||||
isAnimated: false,
|
||||
isLooping: false,
|
||||
frameSpeed: 0,
|
||||
frameRate: 0,
|
||||
frameWidth: 0,
|
||||
frameHeight: 0
|
||||
}
|
||||
@ -166,13 +180,17 @@ function addNewImage() {
|
||||
spriteActions.value = []
|
||||
}
|
||||
|
||||
spriteActions.value.push(newImage)
|
||||
spriteActions.value = sortSpriteActions([...spriteActions.value, newImage])
|
||||
}
|
||||
|
||||
function sortSpriteActions(actions: SpriteAction[]): SpriteAction[] {
|
||||
return [...actions].sort((a, b) => a.action.localeCompare(b.action))
|
||||
}
|
||||
|
||||
watch(selectedSprite, (sprite: Sprite | null) => {
|
||||
if (!sprite) return
|
||||
spriteName.value = sprite.name
|
||||
spriteActions.value = sprite.spriteActions
|
||||
spriteActions.value = sortSpriteActions(sprite.spriteActions)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -1,35 +1,35 @@
|
||||
<template>
|
||||
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
||||
<div class="relative mb-5 flex items-center gap-x-2.5">
|
||||
<input v-model="searchQuery" class="input-field flex-grow" placeholder="Search..." @input="handleSearch" />
|
||||
<button @click.prevent="newButtonClickHandler" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<button @click.prevent="newButtonClickHandler" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2.5 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</div>
|
||||
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
||||
<div v-bind="containerProps" class="overflow-y-auto relative p-2.5 rounded-md default-border bg-gray" @scroll="onScroll">
|
||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||
<a v-for="{ data: sprite } in list" :key="sprite.id" class="relative p-2.5 cursor-pointer block" :class="{ 'bg-cyan/80': assetManagerStore.selectedSprite?.id === sprite.id }" @click="assetManagerStore.setSelectedSprite(sprite as Sprite)">
|
||||
<a v-for="{ data: sprite } in list" :key="sprite.id" class="relative p-2.5 cursor-pointer block rounded hover:bg-cyan group" :class="{ 'bg-cyan': assetManagerStore.selectedSprite?.id === sprite.id }" @click="assetManagerStore.setSelectedSprite(sprite as Sprite)">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span>{{ sprite.name }}</span>
|
||||
<span :class="{ 'text-white': assetManagerStore.selectedSprite?.id === sprite.id }">{{ sprite.name }}</span>
|
||||
</div>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
</div>
|
||||
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan p-0 hover:bg-cyan-800" v-show="hasScrolled" @click="toTop">
|
||||
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||
<div class="absolute w-12 h-12 bottom-2.5 right-2.5">
|
||||
<button class="fixed min-w-[unset] w-12 h-12 rounded-md bg-cyan p-0 hover:bg-cyan-800" v-show="hasScrolled" @click="toTop">
|
||||
<img class="invert w-8 h-8 center-element rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/config'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import config from '@/application/config'
|
||||
import type { Sprite } from '@/application/types'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
import type { Sprite } from '@/types'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
@ -1,14 +1,21 @@
|
||||
<template>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<div v-for="(image, index) in modelValue" :key="index" class="h-20 w-20 p-4 bg-gray-50 bg-opacity-50 rounded text-center relative group cursor-move" draggable="true" @dragstart="dragStart($event, index)" @dragover.prevent @dragenter.prevent @drop="drop($event, index)">
|
||||
<div v-for="(image, index) in modelValue" :key="index" class="h-20 w-20 p-4 bg-gray-300 bg-opacity-50 rounded text-center relative group cursor-move" draggable="true" @dragstart="dragStart($event, index)" @dragover.prevent @dragenter.prevent @drop="drop($event, index)">
|
||||
<img :src="image" class="max-w-full max-h-full object-contain pointer-events-none" alt="Uploaded image" />
|
||||
<button @click.stop="deleteImage(index)" class="absolute top-1 right-1 bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity" aria-label="Delete image">
|
||||
<div class="absolute top-1 left-1 flex-row space-y-1">
|
||||
<button @click.stop="deleteImage(index)" class="bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity" aria-label="Delete image">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity" aria-label="Scope image">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="h-20 w-20 p-4 bg-gray-100 bg-opacity-50 rounded justify-center items-center flex hover:cursor-pointer" @click="triggerFileInput" @drop.prevent="onDrop" @dragover.prevent>
|
||||
</div>
|
||||
<div class="h-20 w-20 p-4 bg-gray-200 bg-opacity-50 rounded justify-center items-center flex hover:cursor-pointer" @click="triggerFileInput" @drop.prevent="onDrop" @dragover.prevent>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 invert" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
|
@ -1,12 +1,9 @@
|
||||
<template>
|
||||
<div class="h-full overflow-auto">
|
||||
<div class="relative p-2.5 flex flex-col items-center justify-between h-72">
|
||||
<div class="filler"></div>
|
||||
<div class="relative p-2.5 flex flex-col items-center justify-center h-72 rounded-md default-border bg-gray">
|
||||
<img class="max-h-72" :src="`${config.server_endpoint}/assets/tiles/${selectedTile?.id}.png`" :alt="'Tile ' + selectedTile?.id" />
|
||||
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="deleteTile">Delete</button>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</div>
|
||||
<div class="m-2.5 p-2.5 block">
|
||||
<div class="mt-5 block">
|
||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveTile">
|
||||
<div class="form-field-full">
|
||||
<label for="name">Name</label>
|
||||
@ -16,20 +13,23 @@
|
||||
<label for="origin-x">Tags</label>
|
||||
<ChipsInput v-model="tileTags" @update:modelValue="tileTags = $event" />
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button>
|
||||
<button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="deleteTile">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Tile } from '@/types'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import config from '@/config'
|
||||
import config from '@/application/config'
|
||||
import type { Tile } from '@/application/types'
|
||||
import ChipsInput from '@/components/forms/ChipsInput.vue'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const assetManagerStore = useAssetManagerStore()
|
||||
|
@ -1,39 +1,39 @@
|
||||
<template>
|
||||
<div class="relative p-2.5 flex items-center gap-x-2.5">
|
||||
<div class="relative mb-5 flex items-center gap-x-2.5">
|
||||
<input v-model="searchQuery" class="input-field flex-grow" placeholder="Search..." @input="handleSearch" />
|
||||
<label for="upload-asset" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<label for="upload-asset" class="bg-cyan text-white border border-solid border-white/25 rounded p-2.5 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
|
||||
<input class="hidden" id="upload-asset" ref="tileUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</label>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</div>
|
||||
<div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
|
||||
<div v-bind="containerProps" class="overflow-y-auto relative p-2.5 rounded-md default-border bg-gray" @scroll="onScroll">
|
||||
<div v-bind="wrapperProps" ref="elementToScroll">
|
||||
<a v-for="{ data: tile } in list" :key="tile.id" class="relative p-2.5 cursor-pointer block" :class="{ 'bg-cyan/80': assetManagerStore.selectedTile?.id === tile.id }" @click="assetManagerStore.setSelectedTile(tile)">
|
||||
<a v-for="{ data: tile } in list" :key="tile.id" class="relative p-2.5 cursor-pointer block rounded hover:bg-cyan group" :class="{ 'bg-cyan': assetManagerStore.selectedTile?.id === tile.id }" @click="assetManagerStore.setSelectedTile(tile)">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<div class="h-7 w-16 max-w-16 flex justify-center">
|
||||
<img class="h-7" :src="`${config.server_endpoint}/assets/tiles/${tile.id}.png`" alt="Tile" />
|
||||
</div>
|
||||
<span>{{ tile.name }}</span>
|
||||
<span class="group-hover:text-white" :class="{ 'text-white': assetManagerStore.selectedTile?.id === tile.id }">{{ tile.name }}</span>
|
||||
</div>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
</a>
|
||||
</div>
|
||||
<button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan p-0 hover:bg-cyan-800" v-show="hasScrolled" @click="toTop">
|
||||
<img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||
<div class="absolute w-12 h-12 bottom-2.5 right-2.5">
|
||||
<button class="fixed min-w-[unset] w-12 h-12 rounded-md bg-cyan p-0 hover:bg-cyan-800" v-show="hasScrolled" @click="toTop">
|
||||
<img class="invert w-8 h-8 center-element rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/config'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import config from '@/application/config'
|
||||
import type { Tile } from '@/application/types'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import type { Tile } from '@/types'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const tileUploadField = ref(null)
|
||||
|
@ -14,24 +14,23 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onUnmounted, ref } from 'vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { type Zone } from '@/types'
|
||||
import { type Zone } from '@/application/types'
|
||||
import ObjectList from '@/components/gameMaster/zoneEditor/partials/ObjectList.vue'
|
||||
import TeleportModal from '@/components/gameMaster/zoneEditor/partials/TeleportModal.vue'
|
||||
import TileList from '@/components/gameMaster/zoneEditor/partials/TileList.vue'
|
||||
// Components
|
||||
import Toolbar from '@/components/gameMaster/zoneEditor/partials/Toolbar.vue'
|
||||
import TileList from '@/components/gameMaster/zoneEditor/partials/TileList.vue'
|
||||
import ObjectList from '@/components/gameMaster/zoneEditor/partials/ObjectList.vue'
|
||||
import ZoneSettings from '@/components/gameMaster/zoneEditor/partials/ZoneSettings.vue'
|
||||
import ZoneList from '@/components/gameMaster/zoneEditor/partials/ZoneList.vue'
|
||||
import TeleportModal from '@/components/gameMaster/zoneEditor/partials/TeleportModal.vue'
|
||||
import ZoneTiles from '@/components/gameMaster/zoneEditor/zonePartials/ZoneTiles.vue'
|
||||
import ZoneObjects from '@/components/gameMaster/zoneEditor/zonePartials/ZoneObjects.vue'
|
||||
import ZoneSettings from '@/components/gameMaster/zoneEditor/partials/ZoneSettings.vue'
|
||||
import ZoneEventTiles from '@/components/gameMaster/zoneEditor/zonePartials/ZoneEventTiles.vue'
|
||||
import ZoneObjects from '@/components/gameMaster/zoneEditor/zonePartials/ZoneObjects.vue'
|
||||
import ZoneTiles from '@/components/gameMaster/zoneEditor/zonePartials/ZoneTiles.vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { onUnmounted, ref } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
console.log(zoneEditorStore.zone)
|
||||
|
||||
const tileMap = ref(null as Phaser.Tilemaps.Tilemap | null)
|
||||
|
||||
@ -64,7 +63,6 @@ function save() {
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:zone_editor:zone:update', data, (response: Zone) => {
|
||||
console.log(response.updatedAt)
|
||||
zoneEditorStore.setZone(response)
|
||||
})
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Modal :isModalOpen="true" @modal:close="() => zoneEditorStore.toggleCreateZoneModal()" :modal-width="300" :modal-height="400" :is-resizable="false">
|
||||
<Modal :isModalOpen="true" @modal:close="() => zoneEditorStore.toggleCreateZoneModal()" :modal-width="300" :modal-height="420" :is-resizable="false" :bg-style="'none'">
|
||||
<template #modalHeader>
|
||||
<h3 class="m-0 font-medium shrink-0 text-white">Create new zone</h3>
|
||||
</template>
|
||||
@ -36,11 +36,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { Zone } from '@/application/types'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import type { Zone } from '@/types'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<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)" :bg-style="'none'">
|
||||
<template #modalHeader>
|
||||
<h3 class="text-lg text-white">Objects</h3>
|
||||
</template>
|
||||
@ -41,12 +41,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/config'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import config from '@/application/config'
|
||||
import type { Object, ZoneObject } from '@/application/types'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import type { Object, ZoneObject } from '@/types'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const isModalOpen = ref(false)
|
||||
|
@ -11,7 +11,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ZoneObject } from '@/types'
|
||||
import type { ZoneObject } from '@/application/types'
|
||||
|
||||
const props = defineProps<{
|
||||
zoneObject: ZoneObject
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Modal :is-modal-open="showTeleportModal" @modal:close="() => zoneEditorStore.setTool('move')" :modal-width="300" :modal-height="350" :is-resizable="false">
|
||||
<Modal :is-modal-open="showTeleportModal" @modal:close="() => zoneEditorStore.setTool('move')" :modal-width="300" :modal-height="350" :is-resizable="false" :bg-style="'none'">
|
||||
<template #modalHeader>
|
||||
<h3 class="m-0 font-medium shrink-0 text-white">Teleport settings</h3>
|
||||
</template>
|
||||
@ -39,11 +39,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import type { Zone } from '@/application/types'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import type { Zone } from '@/types'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const showTeleportModal = computed(() => zoneEditorStore.tool === 'pencil' && zoneEditorStore.drawMode === 'teleport')
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<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)" :bg-style="'none'">
|
||||
<template #modalHeader>
|
||||
<h3 class="text-lg text-white">Tiles</h3>
|
||||
</template>
|
||||
@ -81,12 +81,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/config'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import config from '@/application/config'
|
||||
import type { Tile } from '@/application/types'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import type { Tile } from '@/types'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const isModalOpen = ref(false)
|
||||
|
@ -82,9 +82,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<CreateZone v-if="zoneEditorStore.isCreateZoneModalShown" />
|
||||
<Modal :is-modal-open="zoneEditorStore.isZoneListModalShown" @modal:close="() => zoneEditorStore.toggleZoneListModal()" :is-resizable="false" :modal-width="300" :modal-height="360">
|
||||
<Modal :is-modal-open="zoneEditorStore.isZoneListModalShown" @modal:close="() => zoneEditorStore.toggleZoneListModal()" :is-resizable="false" :modal-width="300" :modal-height="360" :bg-style="'none'">
|
||||
<template #modalHeader>
|
||||
<h3 class="text-lg text-white">Zones</h3>
|
||||
</template>
|
||||
@ -15,7 +15,7 @@
|
||||
<div class="flex gap-3 items-center w-full" @click="() => loadZone(zone.id)">
|
||||
<span>{{ zone.name }}</span>
|
||||
<span class="ml-auto gap-1 flex">
|
||||
<button class="btn-red w-11 h-11 z-50" @click.stop="() => deleteZone(zone.id)">X</button>
|
||||
<button class="btn-red w-7 h-7 z-50 flex items-center justify-center" @click.stop="() => deleteZone(zone.id)">x</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
|
||||
@ -26,12 +26,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import type { Zone } from '@/types'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import type { Zone } from '@/application/types'
|
||||
import CreateZone from '@/components/gameMaster/zoneEditor/partials/CreateZone.vue'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Modal :is-modal-open="zoneEditorStore.isSettingsModalShown" @modal:close="() => zoneEditorStore.toggleSettingsModal()" :modal-width="600" :modal-height="350">
|
||||
<Modal :is-modal-open="zoneEditorStore.isSettingsModalShown" @modal:close="() => zoneEditorStore.toggleSettingsModal()" :modal-width="600" :modal-height="430" :bg-style="'none'">
|
||||
<template #modalHeader>
|
||||
<h3 class="m-0 font-medium shrink-0 text-white">Zone settings</h3>
|
||||
</template>
|
||||
@ -47,9 +47,9 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
const screen = ref('settings')
|
||||
|
@ -3,11 +3,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type ZoneEventTile, ZoneEventTileType } from '@/types'
|
||||
import { ZoneEventTileType, type ZoneEventTile } from '@/application/types'
|
||||
import { uuidv4 } from '@/application/utilities'
|
||||
import { getTile, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { Image, useScene } from 'phavuer'
|
||||
import { getTile, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
||||
import { uuidv4 } from '@/utilities'
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const scene = useScene()
|
||||
|
@ -3,12 +3,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Image, useScene } from 'phavuer'
|
||||
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
||||
import type { AssetDataT, ZoneObject } from '@/application/types'
|
||||
import { loadTexture } from '@/composables/gameComposable'
|
||||
import type { AssetDataT, ZoneObject } from '@/types'
|
||||
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/zoneComposable'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { Image, useScene } from 'phavuer'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
tilemap: Phaser.Tilemaps.Tilemap
|
||||
|
@ -4,14 +4,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { uuidv4 } from '@/utilities'
|
||||
import { getTile } from '@/composables/zoneComposable'
|
||||
import { useScene } from 'phavuer'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import type { ZoneObject as ZoneObjectT } from '@/application/types'
|
||||
import { uuidv4 } from '@/application/utilities'
|
||||
import SelectedZoneObject from '@/components/gameMaster/zoneEditor/partials/SelectedZoneObject.vue'
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import ZoneObject from '@/components/gameMaster/zoneEditor/zonePartials/ZoneObject.vue'
|
||||
import type { ZoneObject as ZoneObjectT } from '@/types'
|
||||
import { getTile } from '@/composables/zoneComposable'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { useScene } from 'phavuer'
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
const scene = useScene()
|
||||
const zoneEditorStore = useZoneEditorStore()
|
||||
@ -56,7 +56,7 @@ function pencil(pointer: Phaser.Input.Pointer) {
|
||||
id: uuidv4(),
|
||||
zoneId: zoneEditorStore.zone.id,
|
||||
zone: zoneEditorStore.zone,
|
||||
objectId: zoneEditorStore.selectedObject,
|
||||
objectId: zoneEditorStore.selectedObject.id,
|
||||
object: zoneEditorStore.selectedObject,
|
||||
depth: 0,
|
||||
isRotated: false,
|
||||
@ -65,7 +65,7 @@ function pencil(pointer: Phaser.Input.Pointer) {
|
||||
}
|
||||
|
||||
// Add new object to zoneObjects
|
||||
zoneEditorStore.zone.zoneObjects = zoneEditorStore.zone.zoneObjects.concat(newObject as ZoneObject)
|
||||
zoneEditorStore.zone.zoneObjects = zoneEditorStore.zone.zoneObjects.concat(newObject as ZoneObjectT)
|
||||
}
|
||||
|
||||
function eraser(pointer: Phaser.Input.Pointer) {
|
||||
@ -134,7 +134,7 @@ function moveZoneObject(id: string) {
|
||||
// Check if zone is set
|
||||
if (!zoneEditorStore.zone) return
|
||||
|
||||
movingZoneObject.value = zoneEditorStore.zone.zoneObjects.find((object) => object.id === id) as ZoneObject
|
||||
movingZoneObject.value = zoneEditorStore.zone.zoneObjects.find((object) => object.id === id) as ZoneObjectT
|
||||
|
||||
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
||||
if (!movingZoneObject.value) return
|
||||
|
@ -3,14 +3,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/config'
|
||||
import { useScene } from 'phavuer'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { onMounted, onUnmounted, watch } from 'vue'
|
||||
import { createTileArray, getTile, placeTile, setLayerTiles } from '@/composables/zoneComposable'
|
||||
import config from '@/application/config'
|
||||
import type { AssetDataT } from '@/application/types'
|
||||
import Controls from '@/components/utilities/Controls.vue'
|
||||
import { createTileArray, getTile, placeTile, setLayerTiles } from '@/composables/zoneComposable'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import type { AssetDataT } from '@/types'
|
||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||
import { useScene } from 'phavuer'
|
||||
import { onMounted, onUnmounted, watch } from 'vue'
|
||||
|
||||
const emit = defineEmits(['tileMap:create'])
|
||||
|
||||
|