Compare commits

..

119 Commits

Author SHA1 Message Date
c1360899e1 #263 - Updated character movement to pure ts (WIP) 2025-01-02 23:34:53 +01:00
63758e67b3 Removed redundant field 2025-01-01 23:01:58 +01:00
b51aa29bd8 Updated types 2025-01-01 20:59:38 +01:00
f9bfbdf735 Renamed property for better consistency 2025-01-01 20:46:02 +01:00
2abce7a7e7 Sorted imports 2025-01-01 19:05:24 +01:00
6ec9f8a7bc Loading char. texture works again 2025-01-01 18:16:31 +01:00
8191a039c9 Walking works again but needs to be improved 2025-01-01 17:50:07 +01:00
540425ca44 Minor change 2025-01-01 04:48:57 +01:00
1c2e642fe3 Typo 2025-01-01 02:59:26 +01:00
8355c83dc8 Renamed class 2024-12-31 16:15:28 +01:00
5fcb336835 Moved file 2024-12-30 17:44:56 +01:00
90bdf43b64 Small change 2024-12-30 02:47:49 +01:00
e9dfcf7870 Minor changes 2024-12-29 02:36:04 +01:00
d0c08c25fd #286 - Added global class for fully absolute-centering elements 2024-12-29 02:21:21 +01:00
7bb7af9476 #295 + #296 - Changed login boxes and char select styling 2024-12-29 02:10:18 +01:00
e4186a1bf5 WIP zone loading 2024-12-29 00:40:02 +01:00
8c664d7774 Started working on improved connect method 2024-12-28 23:48:52 +01:00
744df2e2dc Re-enabled vue dev tools, moved type into types.ts, added temp. logging 2024-12-28 17:27:00 +01:00
b4f9b11143 Removed console log 2024-12-27 19:04:33 +01:00
18b07d2f46 Several fixes, improvements, refactor for authentication 2024-12-27 19:00:53 +01:00
9d0f810ab3 Double field fix 2024-12-27 00:54:31 +01:00
cf3f17dfef Disabled vue dev tools for testing purposes 2024-12-27 00:54:23 +01:00
6be1134c8c Minor improvement 2024-12-27 00:49:57 +01:00
6dad7bc9dd http improvements, fixed link 2024-12-27 00:48:36 +01:00
231f19a30f Added characterChest component for chest equipment, moved some files 2024-12-27 00:27:54 +01:00
9c105d6df6 Returned data update 2024-12-26 23:55:47 +01:00
179ceb0ca0 Cleaned up assets, added default border values to main.scss 2024-12-26 20:03:42 +01:00
680661f07c #258 - Put update effects in the timeout corner until zoneEffects is ready
Reverted latest changes due to zoneEffects needing to fully overwrite
2024-12-25 00:40:56 +01:00
c54d2a2da8 Merge branch 'main' of ssh://gitea.directonline.io:29417/sylvan-quest/client 2024-12-24 00:54:27 +01:00
85f0fca2ae Added copy sprite button, changed asset manager layout, updated packages 2024-12-24 00:54:20 +01:00
420e63b724 #258 - Made it so zoneEffects only overrides defined effects instead of all 2024-12-23 23:23:16 +01:00
5d9b4fd19a #187 - Enter to focus chat when not focused 2024-12-22 20:56:06 +01:00
b3d68ef562 Merge branch 'main' of ssh://gitea.directonline.io:29417/sylvan-quest/client 2024-12-22 20:08:49 +01:00
baae737d6b CRUD components for items 2024-12-22 20:08:45 +01:00
03f8b327c5 #258 - Fixed zone effects when set in settings 2024-12-22 20:06:51 +01:00
b9a1ce5ab5 Adjusted sorting 2024-12-22 02:36:14 +01:00
1b650bd733 #16: Show updates made to character in real time 2024-12-22 00:13:15 +01:00
b867250580 Updated name 2024-12-21 22:10:37 +01:00
2c7a1e27be Fixes for origin being string, styling bug hair select and wrong label tags 2024-12-21 17:48:20 +01:00
0e455f8ffc Use originX and Y for hair 2024-12-21 03:00:09 +01:00
8005bc1318 Small fix 2024-12-21 02:29:48 +01:00
11e978121f Renamed frame speed > frame rate 2024-12-21 02:27:47 +01:00
727ca99b73 #262 : Use frameRate is value is set in sprite settings 2024-12-21 02:20:03 +01:00
97080d7380 Better anim. timing 2024-12-21 02:09:18 +01:00
1a3a53a229 Timing for animations 2024-12-20 21:29:33 +01:00
a926de8466 Hair anim. adjustment 2024-12-20 20:36:13 +01:00
5eabb39ec8 Improved hair animation 2024-12-20 01:28:48 +01:00
03313cb092 #259 - Changed bg options for modals, restyled GM Panel 2024-12-15 15:24:10 +01:00
d58cfa668d More finetuning of hair positioning 2024-12-15 14:40:47 +01:00
e3e40dd083 updated packages 2024-12-13 00:47:44 +01:00
facdd2d1b4 Minor styling changes 2024-12-13 00:47:36 +01:00
7d6bd39f29 GmPanel is full screen by default now 2024-12-12 01:12:33 +01:00
608932300f Improved proof of concept hair customisation & sprite anchor points 2024-12-12 00:42:56 +01:00
b5c1c92b04 Worked on character customisation 2024-12-10 02:22:03 +01:00
c68b129da8 #260 - Fix Characterprofile start position 2024-12-08 19:58:43 +01:00
963c593a1f #260 - Add min height when no character is selected 2024-12-08 19:45:11 +01:00
a299e22f88 #260 - added responsive styling character select 2024-12-08 19:29:07 +01:00
2007bfd7c5 Altered menu a bit 2024-12-08 00:53:07 +01:00
4cae045d0d Effect improvement 2024-12-08 00:35:00 +01:00
1fa8b8f06e You can now zoom in/out with key combination (shift + arrow uo/down)
my 5 EUR Action gaming mouse doesnt let me scroll on MacOS 💩
2024-12-07 23:24:11 +01:00
4095184b27 updated packages 2024-12-07 22:50:36 +01:00
857d56a878 Overriding zone effects now works 2024-12-07 22:50:27 +01:00
8087f754b0 Merge branch 'feature/#245-character-hair' of ssh://gitea.directonline.io:29417/sylvan-quest/client into feature/#245-character-hair 2024-12-05 09:56:17 +01:00
1479d96162 npm update, proof of concept code for character hair 2024-12-05 09:55:20 +01:00
606e220a9f Slightly adjusted styling radio hair color buttons 2024-11-27 20:39:09 +01:00
6988565484 Removed redundant code 2024-11-25 00:42:07 +01:00
fbc4a3dcdb Better typescript 2024-11-25 00:41:12 +01:00
924d5bdd13 Bug fixes and improvements to character select screen 2024-11-25 00:40:21 +01:00
25a2fd24f3 Prep hair color radio btns, adjust styling hairstyles to make it tab friendly 2024-11-24 20:46:29 +01:00
64f5ac45dd Removed console.log, renamed hars to characterHairs 2024-11-24 20:28:50 +01:00
937ce939d1 Preselect hairstyle that's set on character
Removed unused watch
2024-11-24 20:19:45 +01:00
7d89364104 Fix styling conditions for radio select
WIP
2024-11-24 17:15:57 +01:00
f7b8c235d8 Renamed hair > characterHair, split character logic (chat bubble, healthbar etc) into separate components for better DX 2024-11-24 15:13:11 +01:00
89d83efca4 Mess 2024-11-23 22:55:43 +01:00
ab97e27f27 Moved game components into a new folder, working proof of concept hair customisation 2024-11-23 16:47:41 +01:00
ee3e1b55cb Huehue wups 2024-11-23 15:43:57 +01:00
5e109e2a39 Removed ID from radio 2024-11-23 15:39:47 +01:00
a8e50c993a Commented out color swatch, added justify-center 2024-11-23 15:38:39 +01:00
ba8af589a7 Merge remote-tracking branch 'origin/feature/#245-character-hair' into feature/#250
# Conflicts:
#	src/components/screens/Characters.vue
2024-11-23 15:34:42 +01:00
301340327a Added components to manage hair types 2024-11-23 15:29:49 +01:00
1e4c58c79e npm update 2024-11-23 15:29:38 +01:00
f87cd063ee Center elements in characters screen 2024-11-23 15:29:28 +01:00
9593298389 #250 - Changed focus styling, adjusted where needed 2024-11-23 00:27:11 +01:00
ad4651844d Updated TS types 2024-11-21 03:00:09 +01:00
3748c459f8 Merge remote-tracking branch 'origin/main' into feature/#248 2024-11-20 21:19:22 +01:00
50ea3ecdab npm update 2024-11-20 21:19:05 +01:00
8910390f7b #248 - Unfocus chat input when clicking outside 2024-11-19 22:24:34 +01:00
d820490b2b Added update logic for character types, added isEnabledForCharCreation field 2024-11-17 17:52:24 +01:00
2c96caee4f Text white when item is active in asset manager 2024-11-17 17:00:07 +01:00
84939a7d32 Disabled texture in GM utility modals , fixed btn padding for zone delete button 2024-11-17 00:46:05 +01:00
1e3fc2b0f8 Added disableBgTexture option to modals 2024-11-17 00:39:28 +01:00
7c8b5f3e82 npm update 2024-11-16 01:21:43 +01:00
570d315bf5 Small improvements 2024-11-14 23:46:13 +01:00
7871b34c60 Don't open GM panel if focus is on any input or textarea 2024-11-14 22:28:10 +01:00
85d64f23eb #238: Remove hash from URL with JS instead of full redirect 2024-11-14 22:20:46 +01:00
bdb6dd0d54 #161: Store chats into database 2024-11-14 20:42:52 +01:00
faf887163a Added resize event to update to particle window size when resizing 2024-11-13 21:25:54 +01:00
dd5baa530d Removed console.log(), fixed small bug 2024-11-13 19:38:51 +01:00
d9947e29cf Prevent loading anims more than once 2024-11-13 15:42:21 +01:00
1888521762 Fixed modal fullscreen icons, made types compatible with server changes made for #174, npm update, npm run format, minor improvements 2024-11-13 13:22:20 +01:00
48fef2313b Renamed and moved assets for clarity, removed unused svg's 2024-11-08 22:03:10 +01:00
0a99d2c430 Finished base layout of character select/create 2024-11-08 21:22:50 +01:00
ed6f592606 WIP character select 2024-11-06 23:05:37 +01:00
46ebfaec01 npm update 2024-11-05 23:18:10 +01:00
1384f50406 npm run format 2024-11-05 23:16:47 +01:00
d71f4e7b59 #192: Update light and other effects based on server date / weather state 2024-11-05 23:07:05 +01:00
58929290ab #184: Listen for weather updates from client 2024-11-05 22:46:21 +01:00
63146106c0 Moved clock pos. 2024-11-05 22:02:46 +01:00
7c5602f204 #197: Added background image loader 2024-11-05 22:01:28 +01:00
e711e124ce Map editor tiles improvement 2024-11-05 21:31:28 +01:00
e1b39c42ec Several map editor improvements 2024-11-05 21:28:12 +01:00
d81c889426 Removed GM tools, added event listener for shift + G to open GM panel 2024-11-05 20:53:39 +01:00
afb0edacf6 #184: Added clock component 2024-11-05 20:42:52 +01:00
6d7d568746 Moved login partial components 2024-11-05 20:36:00 +01:00
8df5b6eb76 #239: Add loading indicator to password reset submit button for better UX 2024-11-05 20:21:52 +01:00
270d12821a Renamed component, inform user when password reset mail has been sent, added comment for #238 2024-11-05 01:02:27 +01:00
9c244e980c Cleaned component 2024-11-05 00:35:26 +01:00
25ba54c8ac Moved ResetPassword component to correct dir 2024-11-05 00:33:28 +01:00
9c4bef864b Updated default value 2024-11-05 00:01:36 +01:00
139 changed files with 3205 additions and 2013 deletions

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

View File

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

View File

Before

Width:  |  Height:  |  Size: 597 KiB

After

Width:  |  Height:  |  Size: 597 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 600 KiB

After

Width:  |  Height:  |  Size: 600 KiB

View File

Before

Width:  |  Height:  |  Size: 600 KiB

After

Width:  |  Height:  |  Size: 600 KiB

View File

Before

Width:  |  Height:  |  Size: 599 KiB

After

Width:  |  Height:  |  Size: 599 KiB

View File

@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_598_541)">
<path d="M10.0327 5.69111L12.3905 3.33333H9.33333V2H14.6667V7.33333H13.3333V4.27614L10.9755 6.63392C11.6183 7.47513 12 8.52633 12 9.66667C12 12.4281 9.7614 14.6667 7 14.6667C4.23857 14.6667 2 12.4281 2 9.66667C2 6.90527 4.23857 4.66667 7 4.66667C8.14033 4.66667 9.19153 5.04843 10.0327 5.69111ZM7 13.3333C9.02507 13.3333 10.6667 11.6917 10.6667 9.66667C10.6667 7.6416 9.02507 6 7 6C4.97495 6 3.33333 7.6416 3.33333 9.66667C3.33333 11.6917 4.97495 13.3333 7 13.3333Z" fill="#999999"/>
</g>
<defs>
<clipPath id="clip0_598_541">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 729 B

View File

Before

Width:  |  Height:  |  Size: 915 B

After

Width:  |  Height:  |  Size: 918 B

View File

Before

Width:  |  Height:  |  Size: 768 B

After

Width:  |  Height:  |  Size: 768 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 375 B

After

Width:  |  Height:  |  Size: 375 B

View File

Before

Width:  |  Height:  |  Size: 652 B

After

Width:  |  Height:  |  Size: 652 B

View File

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 346 B

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View 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

View 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

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

View File

@ -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(&quot;#fill-color-gradient-render-1-0&quot;);"/></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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 470 KiB

After

Width:  |  Height:  |  Size: 470 KiB

View File

Before

Width:  |  Height:  |  Size: 471 KiB

After

Width:  |  Height:  |  Size: 471 KiB

View File

Before

Width:  |  Height:  |  Size: 598 KiB

After

Width:  |  Height:  |  Size: 598 KiB

View 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

View File

Before

Width:  |  Height:  |  Size: 470 KiB

After

Width:  |  Height:  |  Size: 470 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 302 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 400 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 301 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -1,20 +1,20 @@
<template>
<Notifications />
<GmTools v-if="gameStore.character?.role === 'gm'" />
<BackgroundImageLoader />
<GmPanel v-if="gameStore.character?.role === 'gm'" />
<component :is="currentScreen" />
</template>
<script setup lang="ts">
import { useGameStore } from '@/stores/gameStore'
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
import Notifications from '@/components/utilities/Notifications.vue'
import GmTools from '@/components/gameMaster/GmTools.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()
@ -44,4 +44,16 @@ addEventListener('click', (event) => {
const audio = new Audio('/assets/music/click-btn.mp3')
audio.play()
})
// Watch for "G" key press and toggle the gm panel
addEventListener('keydown', (event) => {
if (gameStore.character?.role !== 'gm') return // Only allow toggling the gm panel if the character is a gm
// Check if no input is active or focus is on an input
if (event.repeat || event.isComposing || event.defaultPrevented || document.activeElement?.tagName.toUpperCase() === 'INPUT' || document.activeElement?.tagName.toUpperCase() === 'TEXTAREA') return
if (event.key === 'G') {
gameStore.toggleGmPanel()
}
})
</script>

View File

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

View File

@ -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,27 +245,34 @@ 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
isFogEnabled: boolean
fogDensity: number
}
export type WeatherState = {
isRainEnabled: boolean
rainPercentage: number
isFogEnabled: boolean
fogDensity: number
}
export type zoneLoadData = {
zone: Zone
characters: ZoneCharacter[]
}

Binary file not shown.

View File

@ -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,6 +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 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 {
@ -133,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;

View File

@ -1,44 +1,62 @@
<template>
<Scene name="effects" @preload="preloadScene" @create="createScene" @update="updateScene"> </Scene>
<Scene name="effects" @preload="preloadScene" @create="createScene" @update="updateScene" />
</template>
<script setup lang="ts">
import { Scene } from 'phavuer'
import type { WeatherState } from '@/application/types'
import { useGameStore } from '@/stores/gameStore'
import { useZoneStore } from '@/stores/zoneStore'
import { onBeforeUnmount, ref, watch } from 'vue'
import { Scene } from 'phavuer'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
// Constants
const LIGHT_CONFIG = {
SUNRISE_HOUR: 6,
SUNSET_HOUR: 20,
DAY_STRENGTH: 100,
NIGHT_STRENGTH: 30
}
// Stores and refs
const gameStore = useGameStore()
const zoneStore = useZoneStore()
const sceneRef = ref<Phaser.Scene | null>(null)
const zoneEffectsReady = ref(false)
// Effect-related 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)
}
const preloadScene = async (scene: Phaser.Scene) => {
// Weather state
const weatherState = ref<WeatherState>({
isRainEnabled: false,
rainPercentage: 0,
isFogEnabled: false,
fogDensity: 0
})
// 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
createLightEffect(scene)
createRainEffect(scene)
createFogEffect(scene)
initializeEffects(scene)
setupSocketListeners()
}
const updateScene = () => {
updateEffects()
}
const initializeEffects = (scene: Phaser.Scene) => {
// Light
effects.light.value = scene.add.graphics().setDepth(1000)
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,
@ -48,63 +66,114 @@ 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 }
const updateEffects = () => {
const effects = zoneStore.zone?.zoneEffects || []
effects.forEach((effect) => {
switch (effect.effect) {
case 'light':
updateLightEffect(effect.strength)
break
case 'rain':
updateRainEffect(effect.strength)
break
case 'fog':
updateFogEffect(effect.strength)
break
}
})
}
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))
// Only update effects once zoneEffects are loaded
if (!zoneEffectsReady.value) {
if (zoneEffects && Object.keys(zoneEffects).length) {
zoneEffectsReady.value = true
} else {
rainEmitter.value.stop()
return
}
}
const updateFogEffect = (strength: number) => {
if (!fogSprite.value) return
fogSprite.value.setAlpha(strength / 100)
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
}
watch(() => zoneStore.zone?.zoneEffects, updateEffects, { deep: true })
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)
}
}
const calculateLightStrength = (time: Date): number => {
const hour = time.getHours()
const minute = time.getMinutes()
if (hour >= LIGHT_CONFIG.SUNSET_HOUR || hour < LIGHT_CONFIG.SUNRISE_HOUR) return LIGHT_CONFIG.NIGHT_STRENGTH
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)
}
// Socket and window handlers
const setupSocketListeners = () => {
gameStore.connection?.emit('weather', (response: WeatherState) => {
weatherState.value = response
updateScene()
})
gameStore.connection!.on('weather', (data: WeatherState) => {
weatherState.value = data
updateScene()
})
gameStore.connection!.on('date', updateScene)
}
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))
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
if (sceneRef.value) sceneRef.value.scene.remove('effects')
gameStore.connection?.off('weather')
})
// @TODO : Fix resize issue
</script>

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

@ -1,11 +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()">Map editor</button>
</div>
</template>
<template #modalBody>
@ -17,12 +18,14 @@
</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()
let toggle = ref('asset-manager')
</script>

View File

@ -1,44 +0,0 @@
<template>
<Modal :isModalOpen="true" :closable="false" :is-resizable="false" :modal-width="modalWidth" :modal-height="modalHeight" :modal-position-x="posXY.x" :modal-position-y="posXY.y">
<template #modalHeader>
<h3 class="m-0 font-medium shrink-0 text-white">GM tools</h3>
</template>
<template #modalBody>
<div class="content flex flex-col gap-2.5 m-4 h-20">
<button class="btn-cyan py-1.5 px-4 w-full" type="button" @click="gameStore.toggleGmPanel()">Toggle GM panel</button>
<button class="btn-cyan py-1.5 px-4 w-full" type="button" @click="() => zoneEditorStore.toggleActive()">Zone manager</button>
</div>
</template>
</Modal>
</template>
<script setup lang="ts">
import Modal from '@/components/utilities/Modal.vue'
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
import { useGameStore } from '@/stores/gameStore'
import { onMounted, ref } from 'vue'
const zoneEditorStore = useZoneEditorStore()
const gameStore = useGameStore()
const modalWidth = ref(200)
const modalHeight = ref(170)
let posXY = ref({ x: 0, y: 0 })
onMounted(() => {
window.addEventListener('resize', () => {
posXY.value = customPositionGmPanel(modalWidth.value)
})
})
const customPositionGmPanel = (modalWidth: number) => {
const padding = 25
const width = window.innerWidth
const x = width - (modalWidth + 4) - 25
const y = padding
return { x, y }
}
posXY.value = customPositionGmPanel(modalWidth.value)
</script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,21 +14,20 @@
</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()

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
@ -223,7 +223,7 @@ function selectTile(tile: string) {
}
function isActiveTile(tile: Tile): boolean {
return zoneEditorStore.selectedTile?.id === tile.id
return zoneEditorStore.selectedTile === tile.id
}
onMounted(async () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More