1
0
forked from noxious/client

Compare commits

...

434 Commits

Author SHA1 Message Date
af5e2449b5 npm update 2025-03-27 21:20:11 +01:00
5392093d71 Sprite gen. work 2025-03-24 15:15:16 +01:00
27c775821a Minor improvements 2025-03-21 21:55:12 +01:00
0d5acd48ce npm update, npm run format 2025-03-21 21:52:37 +01:00
fc34a488d9 Bug fix for hot reload map objects upon saving in map editor 2025-03-21 21:50:31 +01:00
0b1e95f80f npm update 2025-03-21 02:03:58 +01:00
9f176aae45 Remove hidden class 2025-03-21 02:02:18 +01:00
4903a83c71 Merge remote-tracking branch 'origin/feature/depth-sort-fix' into feature/sprite-gen 2025-03-21 02:01:23 +01:00
ba3ed8c099 Sprite editor changes 2025-03-21 01:57:37 +01:00
2881d5f251 Fix for object depths and proper depths for rotated objects 2025-03-20 15:17:34 -05:00
32ca61cc50 Updated sprite editor component 2025-03-16 02:51:17 +01:00
6897ad0f1e Renamed file, removed redundant fields 2025-03-16 01:21:31 +01:00
db1766026e npm update, start updating components, removed obselete fields 2025-03-16 01:07:38 +01:00
3f28d85c30 npm update 2025-03-14 00:38:39 +01:00
a85ad94f15 Merge remote-tracking branch 'origin/main' into feature/depth-sort-fix 2025-03-14 00:31:45 +01:00
e6c684e066 Depth editing for map objects 2025-03-12 11:14:12 -05:00
142d991265 npm update 2025-03-11 23:54:06 +01:00
9e5dcc31fa Updated btn position 2025-03-08 01:40:55 +01:00
b5c5837105 npm update 2025-03-08 00:48:31 +01:00
57b503142e npm run format 2025-03-03 22:21:05 +01:00
208b58d05f npm update, moved component and updated it to composition API 2025-03-03 22:09:58 +01:00
87c04b6de5 Merge remote-tracking branch 'origin/main' into feature/depth-sort-fix 2025-03-03 21:53:39 +01:00
84f8db5e10 Split depth map object demo 2025-03-03 14:38:10 -06:00
84b34c4f85 Container based handling of placed objects 2025-02-22 18:00:37 -06:00
2495e14ece Prevent overflowing of hair img 2025-02-21 21:37:07 +01:00
febb924f75 Cleaned, improved character overview 2025-02-21 21:31:06 +01:00
dc2afba82b Create characters WIP 2025-02-21 03:14:46 +01:00
ed0a02a795 Comment gender selection 2025-02-21 02:20:21 +01:00
3670eb8736 Add reset btn and changed icon sizes 2025-02-21 02:10:49 +01:00
d85bf4846b Character hair refactor, enhancements 2025-02-21 02:02:36 +01:00
51e885cfdf #244: Allow nickname changes, fixed listening for notifications 2025-02-19 11:46:05 +01:00
ffc7efb17c Improve hair positioning 2025-02-19 11:21:46 +01:00
a0da0266d3 Revert logic, updated preview 2025-02-19 01:33:38 +01:00
2281c2c5e0 Mini cleanup 2025-02-19 01:07:27 +01:00
0e3a0e3dba Added Tauri config, updated character hair location logic (WIP) 2025-02-19 01:04:47 +01:00
ed992e1c2d Updated characters.vue 2025-02-18 18:27:35 +01:00
65b011982a Check if hair is set before executing logic 2025-02-18 18:02:50 +01:00
489c6c3ba0 #245 : Added color field to character hair 2025-02-18 17:54:31 +01:00
db650449ac Made save async. 2025-02-18 16:39:14 +01:00
2d7d598c94 #366 : Add storage logic to asset manager 2025-02-18 16:37:21 +01:00
7097eb1580 #366 : Add storage logic to asset manager 2025-02-18 16:37:15 +01:00
d51fbc8030 Map list small UI improvement 2025-02-17 19:23:30 +01:00
b5b6d0adcc Clear event tiles on map clear event 2025-02-17 15:22:24 +01:00
4b7b6e4885 Minor improvement for map saving 2025-02-17 14:49:03 +01:00
4042808d4e Socket event enum enhancement 2025-02-17 01:20:16 +01:00
a6d6d894a9 #363 : Moved socket logic into socketManager and removed it from Pinia store 2025-02-17 01:17:02 +01:00
0c61fe77de Paint tool fixes 2025-02-16 21:36:31 +01:00
bfb2bcb939 Map editor teleport enhancements 2025-02-16 21:18:06 +01:00
af5a97f66d Removed debug console log 2025-02-16 19:04:35 +01:00
79fa54b1bb Cache improvement 2025-02-16 19:03:58 +01:00
dbb4cae154 Only update value if is self 2025-02-16 18:21:53 +01:00
9a8220e4e0 Teleport walk fix 2025-02-16 18:16:35 +01:00
bc0db8b32b Receive new location as array instead of object 2025-02-16 17:29:21 +01:00
ad611ef593 Send new location as array instead of object 2025-02-16 17:19:19 +01:00
d819a84a37 npm update 2025-02-16 17:15:28 +01:00
15dc331a43 Improved movement logic 2025-02-16 17:14:24 +01:00
920baaebde Updated interval value 2025-02-16 01:55:04 +01:00
b569888682 Set selectedPlacedObject null when exiting map editor 2025-02-15 20:46:00 +01:00
94eab073e6 Max 2. pivot points 2025-02-15 17:57:30 +01:00
d843b954ab TS 2025-02-15 17:42:01 +01:00
337446497b Simple 2025-02-15 16:51:58 +01:00
d8805dd775 Added pivot point logic 2025-02-15 16:40:17 +01:00
4c040c21d6 npm update 2025-02-15 15:37:05 +01:00
d0af83ec60 N/A 2025-02-14 23:15:35 +01:00
2de34d2034 Package updates 2025-02-14 22:56:33 +01:00
132121c082 ToggleGmPanel > set false 2025-02-14 16:22:21 +01:00
201f628bfa Saving teleports works again 2025-02-14 03:16:22 +01:00
af99d66595 Add if check to quick login 2025-02-14 03:06:49 +01:00
56f30093f6 Teleport fix WIP 2025-02-14 03:04:42 +01:00
8f26a40a0e 11 for fast login 2025-02-14 03:03:57 +01:00
110fd4e608 TS improvements 2025-02-14 02:49:14 +01:00
c1edf31ca0 TS fix 2025-02-14 02:44:14 +01:00
90c0ed3141 Open teleport modal fix 2025-02-14 02:42:01 +01:00
bcf0d2832d Feedback Shilo
the placement of map objects sometimes placed in a tile that the mouse wasnt over. but i didnt try reproducing the issue.
opening map editor doesnt close the gm window (nor show the map editor). not obvious.
side panels like map objects doesnt have a close button. so i was only able to close it by switching to "move" tool.
2025-02-14 02:34:56 +01:00
8bf67ab168 Minor fix and format 2025-02-14 02:23:13 +01:00
f83e2bf8c8 Merge remote-tracking branch 'origin/main' into feature/map-refactor 2025-02-14 02:08:23 +01:00
8b0bf6534e Most comprehensive undo/redo so far 2025-02-13 13:27:15 -06:00
5e243e5201 Fixed object moving when its not supposed to 2025-02-13 12:19:09 -06:00
c82db9813e Remove redundant import 2025-02-13 11:34:32 -06:00
579749f4e0 fuck depth sorting man 2025-02-13 17:08:47 +01:00
ddc26a021b Depth sort fix attempt 2025-02-13 14:50:46 +01:00
2d6b1ff1e0 Merge branch 'feature/map-refactor' of ssh://gitea.directonline.io:29417/noxious/client into feature/map-refactor
# Conflicts:
#	src/components/gameMaster/mapEditor/Map.vue
#	src/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue
#	src/components/gameMaster/mapEditor/partials/Toolbar.vue
#	src/components/screens/MapEditor.vue
2025-02-12 18:38:14 -06:00
16720777c9 Rm unused func 2025-02-12 21:32:34 +01:00
41e7832cbe Minor improvements 2025-02-12 21:11:17 +01:00
e6412d8a65 Temp. fix for finding children in scene, character create bug fix, chat logic improvements, added image compression upon build 2025-02-12 13:44:37 +01:00
faa8e5def9 Remove history commit that was used to pad the start 2025-02-11 21:14:03 -06:00
beed1d6903 Chat fix 2025-02-12 03:19:56 +01:00
2ebcc24390 npm update 2025-02-12 03:17:01 +01:00
2e3ff803f6 Improvement 2025-02-11 23:17:06 +01:00
dd1cc795de Replaced all event names with numbers for less bandwidth usage 2025-02-11 23:13:15 +01:00
59243e0e17 Merge cleanup 2025-02-10 16:21:23 -06:00
87ffc98cce Best undo/redo function across all map editor elements 2025-02-10 16:08:56 -06:00
0c450b24ed Teleport modal restored, and expanded undo/redo to include all placed and erase edits across each map element type (map object advanced actions WIP) 2025-02-10 16:08:15 -06:00
9459639497 Best undo/redo function across all map editor elements 2025-02-10 16:02:38 -06:00
5f2c7a09b1 Slightly improved logic 2025-02-10 18:32:35 +01:00
13e8c1b4dd Show sprite duration 2025-02-10 16:10:36 +01:00
b27a2e8779 Updated move interval 2025-02-10 15:33:09 +01:00
b3c9e3ca3d 75 2025-02-10 15:30:08 +01:00
31a91c3f9f Prod. env improvements 2025-02-10 15:26:47 +01:00
5d4de60f90 Broken import fix 2025-02-10 15:12:34 +01:00
4070bcf048 Removed BackgroundImageLoader component 2025-02-10 15:11:36 +01:00
04203cb9c1 Add opacity to placed map object preview, don't show seconds in clock , updated date format 2025-02-10 14:37:34 +01:00
592d1df9bf Removed conflicting and redundant logic 2025-02-10 13:39:17 +01:00
9413fdbb2f Improved check 2025-02-10 02:12:24 +01:00
34caac562c Minor fixes 2025-02-10 02:05:16 +01:00
52dafb8643 Added character profile images to preload 2025-02-10 01:53:21 +01:00
390187f353 #202 - Enable / disable placed map object preview 2025-02-10 01:51:45 +01:00
cbd111a05b Map eidtor work , new walk sound 2025-02-10 01:46:31 +01:00
5ef11f3157 Add ID instead of object when adding map object 2025-02-09 22:40:06 +01:00
c56c2796c4 format 2025-02-09 22:36:55 +01:00
c228af7bb6 Moved if logic into component for improved performance, inline value updating for select fields 2025-02-09 22:29:24 +01:00
f45a51c230 Clean 2025-02-09 22:09:42 +01:00
790a62c600 Merge remote-tracking branch 'origin/feature/#321' 2025-02-09 21:54:36 +01:00
82a854e647 Replaced walk sound, removed redundant logic, removed emits in favour of using mapEditor directly (less logic), added soundStorage clear to reset cmd 2025-02-09 21:54:21 +01:00
3bcb16fa9c Merge branch 'feature/#321' of ssh://gitea.directonline.io:29417/noxious/client into feature/#321
# Conflicts:
#	src/components/gameMaster/mapEditor/partials/Toolbar.vue
2025-02-09 21:27:17 +01:00
f79ebedc62 Improvement 2025-02-09 21:27:02 +01:00
44b0368276 Adjusted selectedplacedmapobject toolbar, close list when eraser selected 2025-02-09 20:59:03 +01:00
b8b985470f Move SelectedPlacedMapObject toolbar 2025-02-09 20:40:51 +01:00
39e00c6feb Move toolbar over when listpanel is open 2025-02-09 20:03:47 +01:00
6de0bb200d Removed nginx config 2025-02-09 19:58:54 +01:00
2a00e206eb Updated bg img 2025-02-09 18:36:51 +01:00
8f9b19ba8b i hate environment differences 2025-02-09 17:44:26 +01:00
d997a33b86 Added check 2025-02-09 17:43:39 +01:00
9749b02ccf Debug 2025-02-09 17:42:45 +01:00
f83d5eabee Map service fix attempt 2025-02-09 17:41:08 +01:00
a9cedba4e0 Renamed get to getById, map improvement 2025-02-09 17:33:10 +01:00
49dcd92a9e Map bug fix 2025-02-09 17:28:42 +01:00
d010159989 map bug fix 2025-02-09 17:25:48 +01:00
275dd95c69 Cleaned character create event 2025-02-09 17:13:22 +01:00
e3c3d4d420 Removed debug 2025-02-09 16:32:34 +01:00
87e7f14469 format 2025-02-09 15:31:51 +00:00
723aa59142 rm scp 2025-02-09 16:30:43 +01:00
c369719564 Debug 2025-02-09 16:26:30 +01:00
2d8c421ac6 ah 2025-02-09 03:41:10 +01:00
1137c95ff3 Caddyfile 2025-02-09 03:40:24 +01:00
4b56da0fa0 Caddyfile 2025-02-09 03:39:23 +01:00
c21e78c2ec Removed getDomain func. 2025-02-09 03:34:33 +01:00
fcf96a25ae Added Caddyfile 2025-02-09 03:01:05 +01:00
cf9deebc94 Removed docker files 2025-02-09 01:21:08 +01:00
ca307d4de3 Teleport modal restored, and expanded undo/redo to include all placed and erase edits across each map element type (map object advanced actions WIP) 2025-02-08 15:07:21 -06:00
4c4e8ffe02 Better formatting 2025-02-07 20:47:24 +01:00
369522fda3 #321 - Show corresponding list when drawMode is selected & vice versa
Synced with main, cleaned up unused consts
2025-02-07 20:37:11 +01:00
dc7e20842a Merge remote-tracking branch 'origin/main' into feature/#321 2025-02-07 20:08:19 +01:00
75c9d5f349 Login fix 2025-02-07 20:07:54 +01:00
b35794d6d3 Merge remote-tracking branch 'origin/main' into feature/#321 2025-02-07 19:59:58 +01:00
6ba4c1b843 Updated Dockerfile, renamed config key 2025-02-07 01:14:57 +01:00
6a52546a08 Field step attr. improvement 2025-02-07 00:28:28 +01:00
8133bd02df Merge remote-tracking branch 'origin/main' into feature/#321 2025-02-07 00:26:57 +01:00
e720a1098e Z-index fix for selectedPlacedMapObject 2025-02-07 00:26:47 +01:00
48d1d920be Merge remote-tracking branch 'origin/main' into feature/#321 2025-02-07 00:23:32 +01:00
7542fd70ed #351 : Added map editor settings modal 2025-02-07 00:23:13 +01:00
9f866fea72 Reapply tilelist processing changes 2025-02-06 23:31:50 +01:00
ec6f3031b8 Rebuilt side panel for object & tile lists
Reorganised file structure
2025-02-06 23:20:30 +01:00
838610d041 Cleaned sound composable code 2025-02-06 22:35:54 +01:00
fb3a59aa59 Cache audio 2025-02-06 22:32:25 +01:00
ccb64fc048 mp3 > wav 2025-02-06 22:01:16 +01:00
db52bcfff3 Global const for composable 2025-02-06 21:46:51 +01:00
12735756d7 Added more SFX logic 2025-02-06 21:43:09 +01:00
6383320e8c #209 : Improved logic for button presses 2025-02-06 21:20:49 +01:00
557b8aaabb Added connect sound 2025-02-06 21:08:55 +01:00
c09e9ea841 Replaced button press sound 2025-02-06 21:05:29 +01:00
c2d41a63a7 Added playSound func, use this on attack 2025-02-06 21:00:32 +01:00
122a178feb Minor change 2025-02-06 14:50:30 +01:00
909dbf4280 Fix for scroll 2025-02-06 14:36:28 +01:00
8add054f63 Added drag & resize logic to tileList and mapObjectList 2025-02-06 14:32:39 +01:00
04d55f994e Reimplemented web worker logic for tile analysis 2025-02-06 14:14:41 +01:00
b83c340385 Iso depth fix for character on spawn 2025-02-06 14:04:43 +01:00
d5984f1c3f npm update 2025-02-06 14:03:33 +01:00
7071d934b4 Update origin X and Y values in real-time 2025-02-06 14:02:50 +01:00
15b212160d Walk improvement 2025-02-06 13:46:21 +01:00
2a2841cf16 Fix 2025-02-06 13:34:12 +01:00
a545018639 Removed unused param 2025-02-06 13:24:32 +01:00
90f3056e08 Minor improvements 2025-02-06 13:22:52 +01:00
7730fd81bd Phaser moment 2025-02-05 22:13:29 +01:00
b195f1399f Updated modal bg styles to correct values, started working on map object setting modal 2025-02-05 21:04:47 +01:00
3c06f7db97 Loop fix, added btn-indigo to general styling 2025-02-05 19:50:53 +01:00
6c7864b4d4 Moved map character network event logic into characters component, added playAnimation function to characterComposable, finished attack animation 2025-02-05 18:27:33 +01:00
0c9a41c286 Image dimension bug fix 2025-02-05 17:42:16 +01:00
dffdd0542f #343: Depth sorting improvement for character component 2025-02-05 17:06:31 +01:00
d2abf8fda8 #327 : Fixed map load bug after closing map editor 2025-02-05 17:01:27 +01:00
fdbc101f96 npm run format 2025-02-05 15:19:13 +01:00
7ff1de4018 Removed client notif. Server sends one already. 2025-02-05 15:12:52 +01:00
f258c65403 Merge remote-tracking branch 'origin/main' into feature/map-refactor 2025-02-05 15:11:14 +01:00
bab13646ed Updated packages 2025-02-05 15:10:30 +01:00
adc3eba237 Fixed event tile erasing and moving/flipping map objects 2025-02-04 21:39:59 -06:00
2097a51f07 Put back Colin's FE changes 2025-02-05 04:33:40 +01:00
50daf01a01 Depth sorting works semi-better this way 2025-02-05 03:36:24 +01:00
14474f7665 Removed map from mapEffects type 2025-02-05 03:04:33 +01:00
f14d9baaa1 Send PVP value as integer 2025-02-05 02:55:33 +01:00
d2b6d8dcb3 Showing placed map object works in both game and map editor, updated types, cleaned some logic 2025-02-05 02:27:18 +01:00
027fdd7dac TS improvements, WIP loading map objects in game map, WIP loading tile textures 2025-02-05 00:47:28 +01:00
2b40741ca7 npm run format, moved some files for improved file structure, removed redundant logic 2025-02-05 00:19:55 +01:00
aee18956f3 Restored tile editing and proper map clearing behavior 2025-02-04 14:09:57 -06:00
cf54ab842a Merge remote-tracking branch 'origin/main' into feature/map-refactor
# Conflicts:
#	src/components/gameMaster/mapEditor/Map.vue
#	src/components/gameMaster/mapEditor/partials/MapObjectList.vue
#	src/components/gameMaster/mapEditor/partials/TileList.vue
#	src/components/screens/MapEditor.vue
#	src/composables/pointerHandlers/useMapEditorPointerHandlers.ts
2025-02-04 15:15:29 +01:00
d25100c810 Depth sorting 2025-02-04 15:10:43 +01:00
cd1daf9345 Somehwat improved default object position 2025-02-04 15:07:25 +01:00
0ecd951710 Redundant code removal and synchronizing map settings modal with the editor 2025-02-03 16:18:20 -06:00
ff9dcb91b0 Map object position as tile coordinates, map objects correctly snap to each tile now when moving, and fixed bug of placing objects over eachother on the same tile 2025-02-03 16:18:20 -06:00
841ec0f3df Map object position as tile coordinates, map objects correctly snap to each tile now when moving, and fixed bug of placing objects over eachother on the same tile 2025-02-03 14:53:46 -06:00
90d7252784 Map object depth calculation adjustment 2025-02-02 14:39:21 -06:00
554497ecbc Map object isometric placement 2025-02-02 13:45:35 -06:00
efeae337ab Restored map editor event tiles 2025-02-02 00:02:19 -06:00
ad47b37279 deleting map objects restored 2025-02-01 23:19:45 -06:00
5e11b67774 Moving map objects restored 2025-02-01 21:36:37 -06:00
7daefb74eb Restored placed map object selection and implemented object snapping to tile coordinates 2025-02-01 18:44:00 -06:00
4adcf8d61d Placement of map objects 2025-02-01 15:56:07 -06:00
e53e154d16 npm run format 2025-02-01 16:18:33 +01:00
d65ceba66a Temp. depth sorting fix 2025-02-01 16:14:15 +01:00
db426bb03e Fix for chat bubble 2025-02-01 15:48:42 +01:00
af26ca5e89 Added container for character for easier X and Y coord handling 2025-02-01 15:27:14 +01:00
e4b9bb4d61 Refactored Character.vue as preparation for attack anims. 2025-02-01 15:10:52 +01:00
d7f60d7bfc Don't include sprite dimensions in payload sent to server 2025-02-01 14:49:33 +01:00
cfdfa98379 Revert 2025-02-01 14:22:42 +01:00
63889a537a npm update 2025-02-01 14:22:37 +01:00
99bb1555a0 Listen for attack events. TODO: finish anim. handling 2025-02-01 04:30:07 +01:00
ac1396304f Added web worker to improve tile analysis performance 2025-02-01 03:11:13 +01:00
09ee9bf01d Show tile & mapObject list on top of toolbar buttons, re-enabled camera follow 2025-02-01 02:26:54 +01:00
09b458eeef Merge remote-tracking branch 'origin/main' into feature/#321 2025-02-01 01:43:12 +01:00
9d95562679 Implemented logic to walk with arrow keys 2025-02-01 01:43:01 +01:00
a9de031673 poc 2025-02-01 01:31:28 +01:00
8e81ce716b Minor tweaks and fixes 2025-01-31 23:11:15 +01:00
2c1db56cc4 Merge remote-tracking branch 'origin/feature/#321' 2025-01-31 23:02:01 +01:00
4fba3678d6 Added eraser to tool check 2025-01-31 23:01:19 +01:00
d29ca10ba9 Merge remote-tracking branch 'origin/feature/#321' 2025-01-31 23:00:16 +01:00
67f83c3447 Forgor styling on the other tiles, fix tab closing 2025-01-31 22:57:14 +01:00
8f82bad3fa Merge remote-tracking branch 'origin/feature/#321' 2025-01-31 22:39:34 +01:00
d665ac989c #321 - Made mapObjectList & tileList side panels 2025-01-31 22:35:13 +01:00
e389534e30 npm run format 2025-01-31 22:33:45 +01:00
7d3946e274 #96 - Renamed and refactored pointer handler composables in favor of arrow key movement. 2025-01-31 22:26:23 +01:00
0f46e3b6d2 #323 - Added alt to all images, also removed unused templates
Templates for old user panel hadnt been removed yet
2025-01-31 19:35:29 +01:00
6ca82733eb #324 - Fix Character profile image 2025-01-31 19:04:42 +01:00
eb61f45535 npm update 2025-01-31 18:49:03 +01:00
a181fc7fe3 npm update 2025-01-31 17:06:16 +01:00
507d4226ac Bug fix for character profile, greatly improved javascript 2025-01-31 03:23:23 +01:00
5dd9d1e7af Added temp. offset logic for easier sprite management 2025-01-31 02:16:19 +01:00
15f9e9861e Zoom 2025-01-31 01:55:52 +01:00
7fd334d414 Applied styling fix 2025-01-31 01:19:49 +01:00
c7d4b5f2c3 Updates TS hints 2025-01-31 01:18:49 +01:00
5747166822 Don't toggle accordion on button press 2025-01-31 01:17:44 +01:00
c010373e5b Updated loading indicator to match the other one we have 2025-01-30 18:35:51 +01:00
57ad9d4889 Don't update after closing sprite action img offset modal 2025-01-30 18:32:33 +01:00
f268ac9e5b Removed comment, updated types for sprite actions, minor modal component improvement, added components for better sprite management 2025-01-30 18:29:55 +01:00
fb6e2aa742 Undo and redo cycling through map edit history 2025-01-29 19:48:09 -06:00
8befce7ffb Update packages 2025-01-29 23:28:04 +01:00
e530f69311 Recording a stack of editor tile changes with command pattern 2025-01-28 15:33:47 -06:00
144a513cb6 Switched list modals to right side of screen 2025-01-28 14:12:18 -06:00
2a6321b06b Tile and map object list modals start at top left 2025-01-28 14:09:40 -06:00
ba90982e35 Implemented tap vs hold drawing setting 2025-01-28 13:52:15 -06:00
014c08b17a Set depth to 9999 to always show above character sprite 2025-01-28 17:54:12 +01:00
bdbda6456c CharHair work 2025-01-28 16:32:16 +01:00
85537840ab Improved code 2025-01-28 05:14:37 +01:00
2b7082ac92 Bug fix caused by formatting 2025-01-28 05:01:52 +01:00
abc58bfa38 npm run format, removed container that character was in 2025-01-28 04:57:21 +01:00
027325f2bf Merge remote-tracking branch 'origin/main' into feature/map-refactor 2025-01-28 01:59:29 +01:00
517e92b07b Fully restored tile picker function 2025-01-27 14:49:27 -06:00
6bede8c44e Map editor ui fixes, switching back to game from map editor, draw/tap checkbox, and partial restoration of tile picker function 2025-01-27 14:33:29 -06:00
9e652868ca Prepping to work on Placed map objects 2025-01-27 00:18:46 -06:00
35f0dcca64 Hierarchical pointer handling logic 2025-01-26 21:21:24 -06:00
9618e07bc6 refactoring pointer events and input handling improvements 2025-01-26 20:50:34 -06:00
791830fd6f Refactoring of modalShown booleans 2025-01-26 18:56:13 -06:00
37acf1782b Removed isAnimated and isLooping fields 2025-01-27 01:51:53 +01:00
14aa696197 Changes to mapeditorcomposable, fix pencil and fill tool for tiles
Locked in, made mapeditor my bi-
2025-01-26 23:28:15 +01:00
cfac1d508b Minor change 2025-01-25 16:49:45 +01:00
82cfe5902f Bye 2025-01-25 16:49:25 +01:00
284ca6f64e Finish improving effects component 2025-01-25 15:44:49 +01:00
967cb1893d Re-applied changes 2025-01-25 15:38:20 +01:00
18db005bc1 Merge remote-tracking branch 'origin/main' into feature/map-refactor 2025-01-25 14:48:24 +01:00
c8473fc206 Removed console.log 2025-01-25 14:48:16 +01:00
b5e84c133a Merge remote-tracking branch 'origin/main' into feature/map-refactor
# Conflicts:
#	src/components/game/map/Effects.vue
2025-01-25 14:47:00 +01:00
3f75e4acd8 Merge remote-tracking branch 'origin/main' into feature/#313 2025-01-25 14:46:15 +01:00
765d0986bf Merge remote-tracking branch 'origin/feature/#313' 2025-01-25 14:45:48 +01:00
95c3a1af61 Merge remote-tracking branch 'origin/main' into feature/#313 2025-01-25 14:45:38 +01:00
45a9d8cfdb Formatted code 2025-01-25 14:45:09 +01:00
e0a48a089a Sorted imports 2025-01-25 13:49:52 +01:00
69f9944dc7 Use Dexie instead of Pinia store values in tileList inside mapEditor 2025-01-25 13:49:03 +01:00
9cdfcbcc56 npm run format 2025-01-25 13:24:26 +01:00
a614ee6241 Fixed value could be undefined ts error 2025-01-24 19:43:31 -06:00
7a51323682 Load map data inside a composable instead of Pinia store 2025-01-25 02:38:40 +01:00
807bc2066e Merge remote-tracking branch 'origin/main' into feature/map-refactor 2025-01-25 02:38:13 +01:00
fab0b08425 Load map data inside a composable instead of Pinia store 2025-01-25 02:38:02 +01:00
b074270c75 TS fix 2025-01-25 00:06:25 +01:00
30845b80e9 Minor formatting improvement 2025-01-25 00:02:22 +01:00
bde0f74f19 Re-added required package 2025-01-24 22:58:46 +01:00
bc685c63ef Cleaned code; overwrite cache if newer results are found 2025-01-23 20:40:41 +01:00
7a922261e3 Removed mapList from Pinia store, we fetch them from mapStorage 2025-01-23 20:04:47 +01:00
3936676f2c fog transparency fix 2025-01-23 13:04:43 -06:00
9744083dea Renamed downloadAndStore to downloadCache 2025-01-23 19:46:19 +01:00
176f31d84a Light calculations with minute percision 2025-01-23 02:38:30 -06:00
faad00b2a5 Simplified effects code and implemented smooth interpolation between day an night light values 2025-01-23 01:56:20 -06:00
a61e05592d Removed weather effects booleans, now to disable weather effects, setting the value to 0 is the way 2025-01-22 18:00:39 -06:00
5202251ac7 Tailwind improvement 2025-01-23 00:50:35 +01:00
5e2781b265 Removed placedMapObject depth field, cleaned package.json, creating & deleting maps now works with mapStorage 2025-01-23 00:47:34 +01:00
ebd6d96e54 Started streamlining map editor with regular map logic 2025-01-22 20:34:56 +01:00
41005735f9 Added version to dexie base class 2025-01-22 20:34:16 +01:00
78f1c6e6a0 Added font to loading text 2025-01-22 00:36:00 +01:00
2d48f83802 Load tiles before showing map editor UI 2025-01-22 00:31:14 +01:00
7dccb73698 TS improvement 2025-01-21 23:38:42 +01:00
7171112881 Moved effects.vue into map folder 2025-01-21 23:33:56 +01:00
9a601b7e2e Wait for map load before loading effects
Both command and mapSettings effects work again
2025-01-21 19:59:40 +01:00
5c68b02fff Removed redundant const 2025-01-19 00:06:36 +01:00
b86d9dd4ce Half working effects
Command triggered effects still brokey
2025-01-19 00:03:08 +01:00
93baa10acf Improvements 2025-01-14 02:39:41 +01:00
419cf319be TS fix 2025-01-14 02:17:35 +01:00
1cd7f28402 Removed debugging code 2025-01-13 15:02:53 +01:00
0657dbcb1b Tiny improvements 2025-01-13 14:51:28 +01:00
5cf7423a5c PlacedMapObjects.vue refactor for map 2025-01-13 11:40:41 +01:00
4d88917526 Loading placed map objects works again 2025-01-13 11:02:04 +01:00
8f07cf5093 Map editor tiles are now fully loaded from cache 2025-01-13 10:52:40 +01:00
367d536c52 more shit 2025-01-12 22:11:33 +01:00
3f8c911e9d Clean up 2025-01-12 20:54:59 +01:00
689e443b3d . 2025-01-12 03:24:31 +01:00
4fead371d7 sprite shit 2025-01-11 21:48:08 +01:00
b9bcfc719f Merge remote-tracking branch 'origin/feature/cache' 2025-01-10 23:23:00 +01:00
9de7af961e Improved map tile initialising 2025-01-10 23:22:32 +01:00
4067ec2585 . 2025-01-10 21:03:02 +01:00
fb18841c91 npm run format 2025-01-09 16:02:17 +01:00
7b1dcf7ce3 Added comments 2025-01-09 16:00:36 +01:00
7546116878 Better variable namings 2025-01-09 15:58:02 +01:00
03fef60621 Load textures using cache data instead of data sent from server 2025-01-08 21:13:04 +01:00
574777da80 stuffs 2025-01-07 22:20:46 +01:00
2b84bfcad2 #305 - Made all list types full height 2025-01-07 21:00:26 +01:00
f829cfb883 #309 - Removed mobile hidden styling from inner ui box 2025-01-07 20:20:52 +01:00
c2db9b5469 POC working new caching method - moved controllers folder, renamed assets to textures, fixed HTTP bug, formatted code 2025-01-07 03:59:08 +01:00
6e30a8530a POC working new caching method - moved controllers folder, renamed assets to textures, fixed HTTP bug, formatted code 2025-01-07 03:59:02 +01:00
41f82897a8 Better naming 2025-01-06 00:11:19 +01:00
37b97b0aac Fixes 2025-01-05 22:06:05 +01:00
c1d9cc3a11 Disabled vue dev tools, replaced ref with shallowRef, better naming 2025-01-05 21:00:16 +01:00
b54b825422 Map event tile improvements 2025-01-05 06:22:28 +01:00
0142850983 Map editor WIP 2025-01-05 04:01:50 +01:00
2d09715dc4 Map editor improvements 2025-01-05 01:43:39 +01:00
ef807982a5 Map editor improvements 2025-01-05 00:07:55 +01:00
ae0841889b Fixed emits 2025-01-04 23:17:54 +01:00
bdd2f93175 Added gap between list elements
(Cyan boxes touched when 1 item was selected and other was hovered over)
2025-01-04 22:41:20 +01:00
10f6dc3802 TS improvements 2025-01-04 19:17:10 +01:00
700bd57e67 Almost finalised refactoring 2025-01-03 14:35:08 +01:00
145143cdc5 4k on chat bubble text 2025-01-02 20:37:37 +01:00
201853a3ec Activated 4k on username text rendering 2025-01-02 20:33:54 +01:00
40c87f0ee3 Renamed zone > map 2025-01-02 17:31:31 +01:00
736ddddc54 Fix chips input component 2025-01-02 01:38:42 +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
205 changed files with 13814 additions and 8120 deletions

View File

@ -1,5 +1,6 @@
VITE_NAME=Sylvan Quest VITE_NAME=Noxious
VITE_DEVELOPMENT=true VITE_DOMAIN=localhost
VITE_ENVIRONMENT=development
VITE_SERVER_ENDPOINT=http://localhost:4000 VITE_SERVER_ENDPOINT=http://localhost:4000
VITE_TILE_SIZE_X=64 VITE_TILE_SIZE_WIDTH=64
VITE_TILE_SIZE_Y=32 VITE_TILE_SIZE_HEIGHT=32

View File

@ -1,13 +0,0 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
extends: ['plugin:vue/vue3-essential', 'eslint:recommended', '@vue/eslint-config-typescript', '@vue/eslint-config-prettier/skip-formatting'],
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
'vue/multi-word-component-names': 'off'
}
}

View File

@ -4,5 +4,8 @@
"tabWidth": 2, "tabWidth": 2,
"singleQuote": true, "singleQuote": true,
"printWidth": 300, "printWidth": 300,
"trailingComma": "none" "trailingComma": "none",
"plugins": ["@ianvs/prettier-plugin-sort-imports"],
"importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy", "classProperties"],
"importOrderCaseSensitive": false
} }

View File

@ -1,7 +1,6 @@
{ {
"recommendations": [ "recommendations": [
"Vue.volar", "Vue.volar",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode" "esbenp.prettier-vscode"
] ]
} }

70
Caddyfile Normal file
View File

@ -0,0 +1,70 @@
{
# Global options
admin off # Disable admin API
# Global logging configuration
log {
output file /var/log/caddy/access.log
format json
level INFO
}
}
noxious.gg {
# Root directory for your Vue app
root * ./dist
# Enable compression with optimal settings
encode zstd gzip
# Handle SPA routing
try_files {path} /index.html
# Serve static files with optimizations
file_server
# Enhanced security headers
header {
# Existing headers with improvements
X-Frame-Options "SAMEORIGIN"
X-XSS-Protection "1; mode=block"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
# Additional security headers
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
# Remove server information
-Server
}
# Improved cache configuration for static assets
@static {
file
path *.js *.css *.png *.jpg *.jpeg *.gif *.ico *.svg *.woff *.woff2 *.ttf *.eot
}
header @static {
Cache-Control "public, max-age=31536000, immutable"
Vary Accept-Encoding
}
# Cache control for HTML files
@html {
file
path *.html
}
header @html {
Cache-Control "no-cache, must-revalidate"
}
# Handle errors
handle_errors {
respond "{http.error.status_code} {http.error.status_text}" {http.error.status_code}
}
}
# Improved redirect configuration
www.noxious.gg {
redir https://noxious.gg{uri} permanent
}

View File

@ -1,32 +0,0 @@
# Build stage
FROM node:22.4.1-alpine as builder
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci
COPY . .
# Set environment variables
ARG VITE_NAME=${VITE_NAME}
ENV VITE_NAME=${VITE_NAME}
ARG VITE_DEVELOPMENT=${VITE_DEVELOPMENT}
ENV VITE_DEVELOPMENT=${VITE_DEVELOPMENT}
ARG VITE_SERVER_ENDPOINT=${VITE_SERVER_ENDPOINT}
ENV VITE_SERVER_ENDPOINT=${VITE_SERVER_ENDPOINT}
ARG VITE_TILE_SIZE_X=${VITE_TILE_SIZE_X}
ENV VITE_TILE_SIZE_X=${VITE_TILE_SIZE_X}
ARG VITE_TILE_SIZE_Y=${VITE_TILE_SIZE_Y}
ENV VITE_TILE_SIZE_Y=${VITE_TILE_SIZE_Y}
# Build the application
RUN npm run build-ntc
# Production stage
FROM nginx:1.26.1-alpine
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@ -1,4 +0,0 @@
{
"schemaVersion": 2,
"dockerfilePath" :"./Dockerfile"
}

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <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> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -1,16 +0,0 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Redirect example
location /discord {
return 301 https://discord.gg/JTev3nzeDa;
}
# Serve static files
location / {
try_files $uri $uri/ /index.html;
}
}

5101
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,39 +11,36 @@
"test:unit": "vitest", "test:unit": "vitest",
"build-only": "vite build", "build-only": "vite build",
"type-check": "vue-tsc --build --force", "type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/" "format": "prettier --write src/"
}, },
"dependencies": { "dependencies": {
"@vueuse/core": "^10.5.0", "@vueuse/core": "^10.5.0",
"@vueuse/integrations": "^10.5.0", "@vueuse/integrations": "^10.5.0",
"axios": "^1.7.7", "axios": "^1.7.9",
"dexie": "^4.0.8", "dexie": "^4.0.11",
"phaser": "^3.86.0", "phaser": "^3.88.2",
"pinia": "^2.1.6", "phaser3-rex-plugins": "^1.80.13",
"socket.io-client": "^4.8.0", "phavuer": "^0.16.5",
"pinia": "^2.3.1",
"sharp": "^0.33.5",
"socket.io-client": "^4.8.1",
"universal-cookie": "^6.1.3", "universal-cookie": "^6.1.3",
"vue": "^3.5.12", "vite-plugin-image-optimizer": "^1.1.8",
"zod": "^3.22.2" "vue": "^3.5.13",
"zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
"@rushstack/eslint-patch": "^1.10.3", "@ianvs/prettier-plugin-sort-imports": "^4.4.0",
"@tauri-apps/cli": "^2.2.7",
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.4",
"@types/jsdom": "^21.1.7", "@types/jsdom": "^21.1.7",
"@types/node": "^20.14.11", "@types/node": "^20.14.11",
"@vitejs/plugin-vue": "^5.0.5", "@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.5.1", "@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"easystarjs": "^0.4.4",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.27.0",
"jsdom": "^24.1.1", "jsdom": "^24.1.1",
"npm-run-all2": "^6.2.3", "npm-run-all2": "^6.2.3",
"phaser3-rex-plugins": "^1.80.8",
"phavuer": "^0.16.1",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"sass": "^1.79.4", "sass": "^1.79.4",
@ -51,7 +48,6 @@
"typescript": "~5.6.2", "typescript": "~5.6.2",
"vite": "^5.4.9", "vite": "^5.4.9",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-vue-devtools": "^7.5.2",
"vitest": "^2.0.3", "vitest": "^2.0.3",
"vue-tsc": "^1.6.5" "vue-tsc": "^1.6.5"
} }

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

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 325 B

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 9.5L12 14.5L7 9.5" stroke="#fff" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 325 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 847 B

After

Width:  |  Height:  |  Size: 847 B

View File

Before

Width:  |  Height:  |  Size: 745 B

After

Width:  |  Height:  |  Size: 745 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="M16.3333 17.5L11.0833 12.25C10.6667 12.5833 10.1875 12.8472 9.64583 13.0417C9.10417 13.2361 8.52778 13.3333 7.91667 13.3333C6.40278 13.3333 5.12153 12.809 4.07292 11.7604C3.02431 10.7118 2.5 9.43056 2.5 7.91667C2.5 6.40278 3.02431 5.12153 4.07292 4.07292C5.12153 3.02431 6.40278 2.5 7.91667 2.5C9.43056 2.5 10.7118 3.02431 11.7604 4.07292C12.809 5.12153 13.3333 6.40278 13.3333 7.91667C13.3333 8.52778 13.2361 9.10417 13.0417 9.64583C12.8472 10.1875 12.5833 10.6667 12.25 11.0833L17.5 16.3333L16.3333 17.5ZM7.91667 11.6667C8.95833 11.6667 9.84375 11.3021 10.5729 10.5729C11.3021 9.84375 11.6667 8.95833 11.6667 7.91667C11.6667 6.875 11.3021 5.98958 10.5729 5.26042C9.84375 4.53125 8.95833 4.16667 7.91667 4.16667C6.875 4.16667 5.98958 4.53125 5.26042 5.26042C4.53125 5.98958 4.16667 6.875 4.16667 7.91667C4.16667 8.95833 4.53125 9.84375 5.26042 10.5729C5.98958 11.3021 6.875 11.6667 7.91667 11.6667Z" fill="#808080"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<path d="M490.667,405.333h-56.811C424.619,374.592,396.373,352,362.667,352s-61.931,22.592-71.189,53.333H21.333
C9.557,405.333,0,414.891,0,426.667S9.557,448,21.333,448h270.144c9.237,30.741,37.483,53.333,71.189,53.333
s61.931-22.592,71.189-53.333h56.811c11.797,0,21.333-9.557,21.333-21.333S502.464,405.333,490.667,405.333z M362.667,458.667
c-17.643,0-32-14.357-32-32s14.357-32,32-32s32,14.357,32,32S380.309,458.667,362.667,458.667z"/>
</g>
</g>
<g>
<g>
<path d="M490.667,64h-56.811c-9.259-30.741-37.483-53.333-71.189-53.333S300.736,33.259,291.477,64H21.333
C9.557,64,0,73.557,0,85.333s9.557,21.333,21.333,21.333h270.144C300.736,137.408,328.96,160,362.667,160
s61.931-22.592,71.189-53.333h56.811c11.797,0,21.333-9.557,21.333-21.333S502.464,64,490.667,64z M362.667,117.333
c-17.643,0-32-14.357-32-32c0-17.643,14.357-32,32-32s32,14.357,32,32C394.667,102.976,380.309,117.333,362.667,117.333z"/>
</g>
</g>
<g>
<g>
<path d="M490.667,234.667H220.523c-9.259-30.741-37.483-53.333-71.189-53.333s-61.931,22.592-71.189,53.333H21.333
C9.557,234.667,0,244.224,0,256c0,11.776,9.557,21.333,21.333,21.333h56.811c9.259,30.741,37.483,53.333,71.189,53.333
s61.931-22.592,71.189-53.333h270.144c11.797,0,21.333-9.557,21.333-21.333C512,244.224,502.464,234.667,490.667,234.667z
M149.333,288c-17.643,0-32-14.357-32-32s14.357-32,32-32c17.643,0,32,14.357,32,32S166.976,288,149.333,288z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 946 KiB

View File

Before

Width:  |  Height:  |  Size: 109 B

After

Width:  |  Height:  |  Size: 109 B

View File

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 696 B

View File

Before

Width:  |  Height:  |  Size: 708 B

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/assets/tlogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 301 KiB

After

Width:  |  Height:  |  Size: 302 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 400 KiB

After

Width:  |  Height:  |  Size: 400 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 453 KiB

After

Width:  |  Height:  |  Size: 454 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

4
src-tauri/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/
/gen/schemas

5149
src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

25
src-tauri/Cargo.toml Normal file
View File

@ -0,0 +1,25 @@
[package]
name = "app"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
edition = "2021"
rust-version = "1.77.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.0.4", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
tauri = { version = "2.2.4", features = [] }
tauri-plugin-log = "2.0.0-rc"

3
src-tauri/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@ -0,0 +1,11 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": [
"main"
],
"permissions": [
"core:default"
]
}

BIN
src-tauri/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src-tauri/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
src-tauri/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

16
src-tauri/src/lib.rs Normal file
View File

@ -0,0 +1,16 @@
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
if cfg!(debug_assertions) {
app.handle().plugin(
tauri_plugin_log::Builder::default()
.level(log::LevelFilter::Info)
.build(),
)?;
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

6
src-tauri/src/main.rs Normal file
View File

@ -0,0 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
app_lib::run();
}

37
src-tauri/tauri.conf.json Normal file
View File

@ -0,0 +1,37 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "noxious",
"version": "0.1.0",
"identifier": "com.noxious.app",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:5173",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build-only"
},
"app": {
"windows": [
{
"title": "Noxious",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}

View File

@ -1,56 +1,65 @@
<template> <template>
<Debug />
<Notifications /> <Notifications />
<BackgroundImageLoader />
<GmPanel v-if="gameStore.character?.role === 'gm'" /> <GmPanel v-if="gameStore.character?.role === 'gm'" />
<component :is="currentScreen" /> <component :is="currentScreen" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useGameStore } from '@/stores/gameStore'
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
import Notifications from '@/components/utilities/Notifications.vue'
import BackgroundImageLoader from '@/components/utilities/BackgroundImageLoader.vue'
import GmPanel from '@/components/gameMaster/GmPanel.vue' import GmPanel from '@/components/gameMaster/GmPanel.vue'
import Login from '@/components/screens/Login.vue'
import Characters from '@/components/screens/Characters.vue' import Characters from '@/components/screens/Characters.vue'
import Game from '@/components/screens/Game.vue' import Game from '@/components/screens/Game.vue'
import ZoneEditor from '@/components/screens/ZoneEditor.vue' import Loading from '@/components/screens/Loading.vue'
import Login from '@/components/screens/Login.vue'
import MapEditor from '@/components/screens/MapEditor.vue'
import Debug from '@/components/utilities/Debug.vue'
import Notifications from '@/components/utilities/Notifications.vue'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { useSoundComposable } from '@/composables/useSoundComposable'
import { socketManager } from '@/managers/SocketManager'
import { useGameStore } from '@/stores/gameStore'
import { computed, watch } from 'vue' import { computed, watch } from 'vue'
const gameStore = useGameStore() const gameStore = useGameStore()
const zoneEditorStore = useZoneEditorStore()
const mapEditor = useMapEditorComposable()
const { playSound } = useSoundComposable()
const currentScreen = computed(() => { const currentScreen = computed(() => {
if (!gameStore.connection) return Login if (!gameStore.game.isLoaded) return Loading
if (!gameStore.token) return Login if (!socketManager.connection) return Login
if (!socketManager.token) return Login
if (!gameStore.character) return Characters if (!gameStore.character) return Characters
if (zoneEditorStore.active) return ZoneEditor if (mapEditor.active.value) return MapEditor
return Game return Game
}) })
// Watch zoneEditorStore.active and empty gameStore.game.loadedAssets // Watch mapEditor.active and empty gameStore.game.loadedAssets
watch( watch(
() => zoneEditorStore.active, () => mapEditor.active.value,
() => { () => {
gameStore.game.loadedAssets = [] gameStore.game.loadedTextures = []
} }
) )
// #209: Play sound when a button is pressed // #209: Play sound when a button is pressed
addEventListener('click', (event) => { addEventListener('click', (event) => {
if (!(event.target instanceof HTMLButtonElement)) { const classList = ['btn-cyan', 'btn-red', 'btn-indigo', 'btn-empty', 'btn-sound']
return const target = event.target as HTMLElement
// console.log(target) // Uncomment to log the clicked element
if (classList.some((className) => target.classList.contains(className))) {
playSound('/assets/sounds/button-click.wav')
} }
const audio = new Audio('/assets/music/click-btn.mp3')
audio.play()
}) })
// Watch for "G" key press and toggle the gm panel // Watch for "G" key press and toggle the gm panel
addEventListener('keydown', (event) => { addEventListener('keydown', (event) => {
if (gameStore.character?.role !== 'gm') return // Only allow toggling the gm panel if the character is a gm if (gameStore.character?.role !== 'gm') return // Only allow toggling the gm panel if the character is a gm
// Check if no input is active // Check if no input is active or focus is on an input
if (event.repeat || event.isComposing || event.defaultPrevented) return if (event.repeat || event.isComposing || event.defaultPrevented || document.activeElement?.tagName.toUpperCase() === 'INPUT' || document.activeElement?.tagName.toUpperCase() === 'TEXTAREA') {
return
}
if (event.key === 'G') { if (event.key === 'G') {
gameStore.toggleGmPanel() gameStore.toggleGmPanel()

10
src/application/config.ts Normal file
View File

@ -0,0 +1,10 @@
export default {
name: import.meta.env.VITE_NAME,
domain: import.meta.env.VITE_DOMAIN,
environment: import.meta.env.VITE_ENVIRONMENT,
server_endpoint: import.meta.env.VITE_SERVER_ENDPOINT,
tile_size: {
width: Number(import.meta.env.VITE_TILE_SIZE_WIDTH),
height: Number(import.meta.env.VITE_TILE_SIZE_HEIGHT)
}
}

63
src/application/enums.ts Normal file
View File

@ -0,0 +1,63 @@
export enum Direction {
POSITIVE,
NEGATIVE,
UNCHANGED
}
export enum SocketEvent {
CONNECT_ERROR = 'connect_error',
RECONNECT_FAILED = 'reconnect_failed',
CLOSE = '52',
DATA = '51',
CHARACTER_CONNECT = '50',
CHARACTER_CREATE = '49',
CHARACTER_DELETE = '48',
CHARACTER_LIST = '47',
GM_CHARACTERHAIR_CREATE = '46',
GM_CHARACTERHAIR_REMOVE = '45',
GM_CHARACTERHAIR_LIST = '44',
GM_CHARACTERHAIR_UPDATE = '43',
GM_CHARACTERTYPE_CREATE = '42',
GM_CHARACTERTYPE_REMOVE = '41',
GM_CHARACTERTYPE_LIST = '40',
GM_CHARACTERTYPE_UPDATE = '39',
GM_ITEM_CREATE = '38',
GM_ITEM_REMOVE = '37',
GM_ITEM_LIST = '36',
GM_ITEM_UPDATE = '35',
GM_MAPOBJECT_LIST = '34',
GM_MAPOBJECT_REMOVE = '33',
GM_MAPOBJECT_UPDATE = '32',
GM_MAPOBJECT_UPLOAD = '31',
GM_SPRITE_COPY = '30',
GM_SPRITE_CREATE = '29',
GM_SPRITE_DELETE = '28',
GM_SPRITE_LIST = '27',
GM_SPRITE_UPDATE = '26',
GM_TILE_DELETE = '25',
GM_TILE_LIST = '24',
GM_TILE_UPDATE = '23',
GM_TILE_UPLOAD = '22',
GM_MAP_CREATE = '21',
GM_MAP_DELETE = '20',
GM_MAP_REQUEST = '19',
GM_MAP_UPDATE = '18',
MAP_CHARACTER_MOVEERROR = '17',
DISCONNECT = 'disconnect',
USER_DISCONNECT = '15',
LOGIN = '14',
LOGGED_IN = '13',
NOTIFICATION = '12',
DATE = '11',
FAILED = '10',
COMPLETED = '9',
CONNECTION = 'connection',
WEATHER = '7',
CHARACTER_DISCONNECT = '6',
MAP_CHARACTER_ATTACK = '5',
MAP_CHARACTER_TELEPORT = '4',
MAP_CHARACTER_JOIN = '3',
MAP_CHARACTER_LEAVE = '2',
MAP_CHARACTER_MOVE = '1',
CHAT_MESSAGE = '0'
}

View File

@ -1,18 +1,28 @@
export type UUID = `${string}-${string}-${string}-${string}-${string}`
export type Notification = { export type Notification = {
id?: string id?: string
title?: string title?: string
message?: string message?: string
} }
export type AssetDataT = { export type HttpResponse<T> = {
success: boolean
message?: string
data?: T
}
export type TextureData = {
key: string key: string
data: string data: string // URL or Base64 encoded blob
group: 'tiles' | 'objects' | 'sprites' | 'sprite_animations' | 'sound' | 'music' | 'ui' | 'font' | 'other' group: 'tiles' | 'map_objects' | 'sprites' | 'sprite_animations' | 'sound' | 'music' | 'ui' | 'font' | 'other'
updatedAt: Date updatedAt: Date
isAnimated?: boolean originX?: number
frameCount?: number originY?: number
frameRate?: number
frameWidth?: number frameWidth?: number
frameHeight?: number frameHeight?: number
frameCount?: number
} }
export type Tile = { export type Tile = {
@ -23,97 +33,92 @@ export type Tile = {
updatedAt: Date updatedAt: Date
} }
export type Object = { export type MapObject = {
id: string id: string
name: string name: string
tags: any | null tags: string[]
depthOffsets: number[]
originX: number originX: number
originY: number originY: number
isAnimated: boolean frameRate: number
frameSpeed: number
frameWidth: number frameWidth: number
frameHeight: number frameHeight: number
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
ZoneObject: ZoneObject[]
} }
export type Item = { export type Item = {
id: string id: string
name: string name: string
description: string | null description: string | null
itemType: ItemType
stackable: boolean stackable: boolean
rarity: ItemRarity
sprite?: Sprite
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
characters: CharacterItem[]
} }
export type Zone = { export type ItemType = 'WEAPON' | 'HELMET' | 'CHEST' | 'LEGS' | 'BOOTS' | 'GLOVES' | 'RING' | 'NECKLACE'
id: number export type ItemRarity = 'COMMON' | 'UNCOMMON' | 'RARE' | 'EPIC' | 'LEGENDARY'
export type Map = {
id: string
name: string name: string
width: number width: number
height: number height: number
tiles: any | null tiles: string[][]
pvp: boolean pvp: boolean
zoneEffects: ZoneEffect[] mapEffects: MapEffect[]
zoneEventTiles: ZoneEventTile[] mapEventTiles: MapEventTile[]
zoneObjects: ZoneObject[] placedMapObjects: PlacedMapObject[]
characters: Character[] characters: Character[]
chats: Chat[] chats: Chat[]
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
} }
export type ZoneEffect = { export type MapEffect = {
id: string id: string
zoneId: number
zone: Zone
effect: string effect: string
strength: number strength: number
} }
export type ZoneObject = { export type PlacedMapObject = {
id: string id: string
zoneId: number mapObject: MapObject | string
zone: Zone
objectId: string
object: Object
depth: number
isRotated: boolean isRotated: boolean
positionX: number positionX: number
positionY: number positionY: number
} }
export enum ZoneEventTileType { export enum MapEventTileType {
BLOCK = 'BLOCK', BLOCK = 'BLOCK',
TELEPORT = 'TELEPORT', TELEPORT = 'TELEPORT',
NPC = 'NPC', NPC = 'NPC',
ITEM = 'ITEM' ITEM = 'ITEM'
} }
export type ZoneEventTile = { export type MapEventTile = {
id: string id: string
zoneId: number map: string
zone: Zone type: MapEventTileType
type: ZoneEventTileType
positionX: number positionX: number
positionY: number positionY: number
teleport?: ZoneEventTileTeleport teleport?: MapEventTileTeleport
} }
export type ZoneEventTileTeleport = { export type MapEventTileTeleport = {
id: string id: string
zoneEventTileId: string mapEventTile: MapEventTile
zoneEventTile: ZoneEventTile toMap: Map
toZoneId: number
toZone: Zone
toPositionX: number toPositionX: number
toPositionY: number toPositionY: number
toRotation: number toRotation: number
} }
export type User = { export type User = {
id: number id: string
username: string username: string
password: string password: string
characters: Character[] characters: Character[]
@ -133,20 +138,28 @@ export enum CharacterRace {
} }
export type CharacterType = { export type CharacterType = {
id: number id: string
name: string name: string
gender: CharacterGender gender: CharacterGender
race: CharacterRace race: CharacterRace
characters: Character[] isSelectable: boolean
spriteId?: string
sprite?: Sprite sprite?: Sprite
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
} }
export type CharacterHair = {
id: string
name: string
sprite: string | Sprite
gender: CharacterGender
color: string
isSelectable: boolean
}
export type Character = { export type Character = {
id: number id: string
userId: number userid: string
user: User user: User
name: string name: string
hitpoints: number hitpoints: number
@ -158,81 +171,92 @@ export type Character = {
positionX: number positionX: number
positionY: number positionY: number
rotation: number rotation: number
zoneId: number characterType: UUID | null
zone: Zone characterHair: UUID | null
characterTypeId: number | null map: UUID
characterType: CharacterType | null
chats: Chat[] chats: Chat[]
items: CharacterItem[] items: CharacterItem[]
equipment: CharacterEquipment[]
} }
export type ZoneCharacter = { export type MapCharacter = {
character: Character character: Character
isMoving?: boolean isMoving: boolean
} isAttacking?: boolean
export type ExtendedCharacter = Character & {
isMoving?: boolean
} }
export type CharacterItem = { export type CharacterItem = {
id: number id: string
characterId: number
character: Character character: Character
itemId: string
item: Item item: Item
quantity: number quantity: number
} }
export type CharacterEquipment = {
id: string
slot: CharacterEquipmentSlotType
characterItem: CharacterItem
}
export enum CharacterEquipmentSlotType {
HEAD = 'HEAD',
BODY = 'BODY',
ARMS = 'ARMS',
LEGS = 'LEGS',
NECK = 'NECK',
RING = 'RING'
}
export type Sprite = { export type Sprite = {
id: string id: string
name: string name: string
width: number | null
height: number | null
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
spriteActions: SpriteAction[] spriteActions: SpriteAction[]
characterTypes: CharacterType[] characterTypes: CharacterType[]
} }
export interface SpriteImage {
url: string
offset: {
x: number
y: number
}
}
export type SpriteAction = { export type SpriteAction = {
id: string id: string
spriteId: string sprite: string
sprite: Sprite
action: string action: string
sprites: string[] sprites: SpriteImage[]
originX: number originX: number
originY: number originY: number
isAnimated: boolean
isLooping: boolean
frameWidth: number frameWidth: number
frameHeight: number frameHeight: number
frameSpeed: number frameRate: number
} }
export type Chat = { export type Chat = {
id: number id: string
characterId: number
character: Character character: Character
zoneId: number map: Map
zone: Zone
message: string message: string
createdAt: Date createdAt: Date
} }
export type ChatMessage = {
character: Character
message: string
}
export type WorldSettings = { export type WorldSettings = {
date: Date date: Date
isRainEnabled: boolean weatherState: WeatherState
isFogEnabled: boolean
fogDensity: number
} }
export type WeatherState = { export type WeatherState = {
isRainEnabled: boolean
rainPercentage: number rainPercentage: number
isFogEnabled: boolean
fogDensity: number fogDensity: number
} }
export type mapLoadData = {
mapId: string
characters: MapCharacter[]
}

View File

@ -0,0 +1,45 @@
import config from '@/application/config'
import type { HttpResponse } from '@/application/types'
import type { BaseStorage } from '@/storage/baseStorage'
export function uuidv4() {
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => (+c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (+c / 4)))).toString(16))
}
export function unduplicateArray(array: any[]) {
const arrayToProcess = typeof array.flat === 'function' ? array.flat() : array
return [...new Set(arrayToProcess)]
}
export async function downloadCache<T extends { id: string; updatedAt: Date }>(endpoint: string, storage: BaseStorage<T>) {
const request = await fetch(`${config.server_endpoint}/cache/${endpoint}`)
const response = (await request.json()) as HttpResponse<T[]>
if (!response.success) {
console.error(`Failed to download ${endpoint}:`, response.message)
return
}
const items = response.data ?? []
const serverItemIds = new Set(items.map((item) => item.id))
// Remove items that don't exist on server
const existingItems = await storage.getAll()
for (const existingItem of existingItems) {
if (!serverItemIds.has(existingItem.id)) {
await storage.delete(existingItem.id)
}
}
// Update or add new items
for (const item of items) {
let overwrite = false
const existingItem = await storage.getById(item.id)
if (!existingItem || item.updatedAt > existingItem.updatedAt) {
overwrite = true
}
await storage.add(item, overwrite)
}
}

Binary file not shown.

View File

@ -23,6 +23,14 @@ body {
// Disable pinch zoom // Disable pinch zoom
touch-action: pan-x pan-y; 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, h1,
@ -45,7 +53,7 @@ label {
button, button,
a { a {
@apply font-medium drop-shadow-20; @apply font-medium;
} }
button, button,
@ -58,13 +66,20 @@ input {
appearance: textfield; appearance: textfield;
} }
&[type='number']::-webkit-inner-spin-button, &[type='number']::-webkit-inner-spin-button,
&[type='number']::-webkit-outer-spin-button { &[type='number']::-webkit-outer-spin-button,
&[type='radio'] {
-webkit-appearance: none; -webkit-appearance: none;
} }
} }
.input-field { .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 font-default;
&:focus-visible {
@apply outline-none border-cyan rounded bg-gray-900;
}
&::placeholder {
@apply focus-visible:text-gray-300/50;
}
&.inactive { &.inactive {
@apply bg-gray-600/50 hover:cursor-not-allowed; @apply bg-gray-600/50 hover:cursor-not-allowed;
&::placeholder { &::placeholder {
@ -73,6 +88,12 @@ input {
} }
} }
select {
&.input-field {
@apply appearance-none bg-[url('/assets/icons/mapEditor/dropdown-chevron.svg')] bg-no-repeat bg-[calc(100%_-_10px)_center] bg-[length:20px] text-white;
}
}
.form-field-full { .form-field-full {
@apply w-full flex flex-col mb-5; @apply w-full flex flex-col mb-5;
label { label {
@ -99,11 +120,20 @@ button {
} }
&.btn-red { &.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, &.active,
&:hover { &:hover {
@apply bg-red-300; @apply bg-red-500;
}
}
&.btn-indigo {
@apply bg-indigo-500 text-gray-50 text-base leading-5 rounded py-2.5;
&.active,
&:hover {
@apply bg-indigo-600;
} }
} }
@ -113,7 +143,7 @@ button {
&.active, &.active,
&.selected, &.selected,
&:hover { &:hover {
@apply bg-gray-700 border-gray-700; @apply bg-gray border-gray;
} }
} }
@ -130,10 +160,26 @@ button {
} }
} }
.character { .character.active {
&.active { @apply bg-gray bg-none;
@apply pr-px border-r-0;
} }
.list-open {
@apply w-[calc(75%_-_40px)] max-xl:w-[calc(100%_-_360px)];
}
.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 { .text-pixel {

View File

@ -1,218 +0,0 @@
<template>
<Scene name="effects" @preload="preloadScene" @create="createScene" @update="updateScene" />
</template>
<script setup lang="ts">
import { Scene } from 'phavuer'
import { useZoneStore } from '@/stores/zoneStore'
import { useGameStore } from '@/stores/gameStore'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import type { WeatherState } from '@/types'
// Constants
const SUNRISE_HOUR = 6
const SUNSET_HOUR = 20
const DAY_STRENGTH = 100
const NIGHT_STRENGTH = 30
// Stores
const gameStore = useGameStore()
const zoneStore = useZoneStore()
// Scene ref
const sceneRef = ref<Phaser.Scene | null>(null)
// Effect refs
const lightEffect = ref<Phaser.GameObjects.Graphics | null>(null)
const rainEmitter = ref<Phaser.GameObjects.Particles.ParticleEmitter | null>(null)
const fogSprite = ref<Phaser.GameObjects.Sprite | null>(null)
// State refs
const weatherState = ref<WeatherState>({
isRainEnabled: false,
rainPercentage: 0,
isFogEnabled: false,
fogDensity: 0
})
// Scene lifecycle methods
const preloadScene = async (scene: Phaser.Scene) => {
scene.load.image('raindrop', 'assets/raindrop.png')
scene.load.image('fog', 'assets/fog.png')
}
const createScene = async (scene: Phaser.Scene) => {
sceneRef.value = scene
setupEffects(scene)
setupSocketListeners()
}
const updateScene = () => {
updateEffects()
}
// Effect setup
const setupEffects = (scene: Phaser.Scene) => {
createLightEffect(scene)
createRainEffect(scene)
createFogEffect(scene)
}
const createLightEffect = (scene: Phaser.Scene) => {
lightEffect.value = scene.add.graphics()
lightEffect.value.setDepth(1000)
}
const createRainEffect = (scene: Phaser.Scene) => {
rainEmitter.value = scene.add.particles(0, 0, 'raindrop', {
x: { min: 0, max: window.innerWidth },
y: -50,
quantity: 5,
lifespan: 2000,
speedY: { min: 300, max: 500 },
scale: { start: 0.005, end: 0.005 },
alpha: { start: 0.5, end: 0 },
blendMode: 'ADD'
})
rainEmitter.value.setDepth(900)
rainEmitter.value.stop()
}
const createFogEffect = (scene: Phaser.Scene) => {
fogSprite.value = scene.add.sprite(window.innerWidth / 2, window.innerHeight / 2, 'fog')
fogSprite.value.setScale(2)
fogSprite.value.setAlpha(0)
fogSprite.value.setDepth(950)
}
// Lighting calculations
const calculateLightStrength = (time: Date): number => {
const hour = time.getHours()
const minute = time.getMinutes()
let strength = DAY_STRENGTH
// Night time (10 PM - 6 AM)
if (hour >= SUNSET_HOUR || hour < SUNRISE_HOUR) {
strength = NIGHT_STRENGTH
}
// Full daylight (7 AM - 7 PM)
else if (hour > SUNRISE_HOUR && hour < SUNSET_HOUR - 2) {
strength = DAY_STRENGTH
}
// Sunrise transition (6 AM - 7 AM)
else if (hour === SUNRISE_HOUR) {
strength = NIGHT_STRENGTH + ((DAY_STRENGTH - NIGHT_STRENGTH) * minute) / 60
}
// Sunset transition (8 PM - 10 PM)
else if (hour >= SUNSET_HOUR - 2 && hour < SUNSET_HOUR) {
const totalMinutes = (hour - (SUNSET_HOUR - 2)) * 60 + minute
const transitionProgress = totalMinutes / 120 // 2 hours = 120 minutes
strength = DAY_STRENGTH - (DAY_STRENGTH - NIGHT_STRENGTH) * transitionProgress
}
return strength
}
// Effect updates
const updateEffects = () => {
const effects = zoneStore.zone?.zoneEffects || []
if (effects.length > 0) {
updateZoneEffects(effects)
} else {
// Make sure we're getting the current time
const lightStrength = calculateLightStrength(gameStore.world.date)
updateLightEffect(lightStrength)
updateWeatherEffects()
}
}
const updateZoneEffects = (effects: any[]) => {
// Always update light based on time when zone effects are present
updateLightEffect(calculateLightStrength(gameStore.world.date))
effects.forEach((effect) => {
switch (effect.effect) {
case 'rain':
updateRainEffect(effect.strength)
break
case 'fog':
updateFogEffect(effect.strength)
break
}
})
}
const updateWeatherEffects = () => {
updateRainEffect(weatherState.value.isRainEnabled ? weatherState.value.rainPercentage : 0)
updateFogEffect(weatherState.value.isFogEnabled ? weatherState.value.fogDensity * 100 : 0)
}
const updateLightEffect = (strength: number) => {
if (!lightEffect.value) return
const darkness = 1 - strength / 100
lightEffect.value.clear()
lightEffect.value.fillStyle(0x000000, darkness)
lightEffect.value.fillRect(0, 0, window.innerWidth, window.innerHeight)
}
const updateRainEffect = (strength: number) => {
if (!rainEmitter.value) return
if (strength > 0) {
rainEmitter.value.start()
rainEmitter.value.setQuantity(Math.floor((strength / 100) * 10))
} else {
rainEmitter.value.stop()
}
}
const updateFogEffect = (strength: number) => {
if (!fogSprite.value) return
fogSprite.value.setAlpha(strength / 100)
}
// Socket handlers
const setupSocketListeners = () => {
// Initial weather state
gameStore.connection?.emit('weather', (response: WeatherState) => {
if (zoneStore.zone?.zoneEffects) return
weatherState.value = response
updateEffects()
})
// Weather updates
gameStore.connection?.on('weather', (data: WeatherState) => {
weatherState.value = data
updateEffects()
})
// Time updates
gameStore.connection?.on('date', () => {
if (zoneStore.zone?.zoneEffects) return
updateEffects()
})
}
const updateEffectWindowSize = () => {
if(rainEmitter.value) rainEmitter.value.updateConfig({x: {min: 0, max: window.innerWidth}})
if(fogSprite.value) {
fogSprite.value.setX(window.innerWidth / 2)
fogSprite.value.setY(window.innerHeight / 2)
}
}
// Watchers
watch(() => zoneStore.zone?.zoneEffects, updateEffects, { deep: true })
onMounted(() => {
window.addEventListener("resize", updateEffectWindowSize);
})
// Cleanup
onBeforeUnmount(() => {
window.removeEventListener("resize", updateEffectWindowSize);
if (sceneRef.value) sceneRef.value.scene.remove('effects')
gameStore.connection?.off('weather')
})
</script>

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="flex flex-wrap items-center input-field gap-1"> <div class="flex flex-wrap items-center input-field gap-1" @click="focusInput">
<div v-for="(chip, i) in internalValue" :key="i" class="flex gap-2.5 items-center bg-cyan rounded py-1 px-2"> <div v-for="(chip, i) in internalValue" :key="i" class="flex gap-2.5 items-center bg-cyan rounded py-1 px-2" role="listitem">
<span class="text-xs text-white">{{ chip }}</span> <span class="text-xs text-white">{{ chip }}</span>
<button type="button" class="text-xs cursor-pointer text-white font-light font-default not-italic hover:text-gray-50" @click="deleteChip(i)" aria-label="Remove chip">×</button> <button type="button" class="text-xs cursor-pointer text-white font-light font-default not-italic hover:text-gray-50" @click.stop="deleteChip(i)" aria-label="Remove tag">×</button>
</div> </div>
<input class="outline-none border-none p-1 text-gray-300" placeholder="Tag name" v-model="currentInput" @keypress.enter.prevent="addChip" @keydown.backspace="handleBackspace" /> <input ref="inputRef" class="outline-none border-none p-1 text-gray-300 min-w-[60px] flex-grow" :placeholder="placeholder" v-model.trim="currentInput" @keydown="handleKeydown" @paste="handlePaste" :maxlength="maxChipLength" aria-label="Add new tag" />
</div> </div>
</template> </template>
@ -14,20 +14,29 @@ import type { Ref } from 'vue'
interface Props { interface Props {
modelValue?: string[] modelValue?: string[]
maxChips?: number
maxChipLength?: number
placeholder?: string
allowDuplicates?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
modelValue: () => [] modelValue: () => [],
maxChips: 10,
maxChipLength: 20,
placeholder: 'Add tag',
allowDuplicates: false
}) })
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', value: string[]): void (e: 'update:modelValue', value: string[]): void
(e: 'error', message: string): void
}>() }>()
const currentInput: Ref<string> = ref('') const currentInput: Ref<string> = ref('')
const internalValue = ref<string[]>([]) const internalValue = ref<string[]>([])
const inputRef = ref<HTMLInputElement | null>(null)
// Initialize internalValue with props.modelValue
watch( watch(
() => props.modelValue, () => props.modelValue,
(newValue) => { (newValue) => {
@ -36,9 +45,27 @@ watch(
{ immediate: true } { immediate: true }
) )
const validateChip = (chip: string): boolean => {
if (!chip) {
return false
}
if (!props.allowDuplicates && internalValue.value.includes(chip)) {
emit('error', 'Duplicate tags are not allowed')
return false
}
if (internalValue.value.length >= props.maxChips) {
emit('error', `Maximum ${props.maxChips} tags allowed`)
return false
}
return true
}
const addChip = () => { const addChip = () => {
const trimmedInput = currentInput.value.trim() const trimmedInput = currentInput.value.trim()
if (trimmedInput && !internalValue.value.includes(trimmedInput)) { if (validateChip(trimmedInput)) {
internalValue.value.push(trimmedInput) internalValue.value.push(trimmedInput)
emit('update:modelValue', internalValue.value) emit('update:modelValue', internalValue.value)
currentInput.value = '' currentInput.value = ''
@ -50,10 +77,36 @@ const deleteChip = (index: number) => {
emit('update:modelValue', internalValue.value) emit('update:modelValue', internalValue.value)
} }
const handleBackspace = (event: KeyboardEvent) => { const handleKeydown = (event: KeyboardEvent) => {
if (event.key === 'Backspace' && currentInput.value === '' && internalValue.value.length > 0) { switch (event.key) {
internalValue.value.pop() case 'Enter':
emit('update:modelValue', internalValue.value) event.preventDefault()
addChip()
break
case 'Backspace':
if (currentInput.value === '' && internalValue.value.length > 0) {
deleteChip(internalValue.value.length - 1)
} }
break
}
}
const handlePaste = (event: ClipboardEvent) => {
event.preventDefault()
const pastedText = event.clipboardData?.getData('text')
if (pastedText) {
const chips = pastedText
.split(/[,\n]/)
.map((chip) => chip.trim())
.filter(Boolean)
chips.forEach((chip) => {
currentInput.value = chip
addChip()
})
}
}
const focusInput = () => {
inputRef.value?.focus()
} }
</script> </script>

View File

@ -0,0 +1,103 @@
<template>
<Container ref="characterContainer" :x="currentPositionX" :y="currentPositionY" :depth="isometricDepth">
<ChatBubble :mapCharacter="props.mapCharacter" />
<HealthBar :mapCharacter="props.mapCharacter" />
<CharacterHair :mapCharacter="props.mapCharacter" :flipX="isFlippedX" />
<Sprite ref="characterSprite" :flipX="isFlippedX" />
</Container>
</template>
<script lang="ts" setup>
import { type MapCharacter } 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 { useCharacterSpriteComposable } from '@/composables/useCharacterSpriteComposable'
import { useSoundComposable } from '@/composables/useSoundComposable'
import { useGameStore } from '@/stores/gameStore'
import { useMapStore } from '@/stores/mapStore'
import { Container, Sprite, useScene } from 'phavuer'
import { onMounted, onUnmounted, watch } from 'vue'
const props = defineProps<{
tileMap: Phaser.Tilemaps.Tilemap
mapCharacter: MapCharacter
}>()
const gameStore = useGameStore()
const mapStore = useMapStore()
const scene = useScene()
const { characterContainer, characterSprite, currentPositionX, currentPositionY, isometricDepth, isFlippedX, updatePosition, playAnimation, updateSprite, initializeSprite, cleanup } = useCharacterSpriteComposable(scene, props.tileMap, props.mapCharacter)
const { playSound, stopSound } = useSoundComposable()
const handlePositionUpdate = (newValues: any, oldValues: any) => {
if (!newValues) return
if (!oldValues || newValues.positionX !== oldValues.positionX || newValues.positionY !== oldValues.positionY) {
updatePosition(newValues.positionX, newValues.positionY)
}
if (newValues.isMoving !== oldValues?.isMoving || newValues.rotation !== oldValues?.rotation) {
updateSprite()
}
}
/**
* Plays walk sound when character is moving
*/
watch(
() => props.mapCharacter.isMoving,
(newValue) => {
if (newValue) {
playSound('/assets/sounds/walk.wav', false, true)
} else {
stopSound('/assets/sounds/walk.wav')
}
}
)
/**
* Plays attack animation and sound when character is attacking
*/
watch(
() => props.mapCharacter.isAttacking,
(newValue) => {
if (newValue) {
playAnimation('attack')
playSound('/assets/sounds/attack.wav', false, true)
} else {
stopSound('/assets/sounds/attack.wav')
}
mapStore.updateCharacterProperty(props.mapCharacter.character.id, 'isAttacking', false)
}
)
/**
* Handles position updates and movement delay
*/
watch(
() => ({
positionX: props.mapCharacter.character.positionX,
positionY: props.mapCharacter.character.positionY,
isMoving: props.mapCharacter.isMoving,
rotation: props.mapCharacter.character.rotation,
isAttacking: props.mapCharacter.isAttacking
}),
async (oldValues, newValues) => {
handlePositionUpdate(oldValues, newValues)
}
)
onMounted(async () => {
await initializeSprite()
if (props.mapCharacter.character.id === gameStore.character!.id) {
scene.cameras.main.startFollow(characterContainer.value as Phaser.GameObjects.Container)
}
})
onUnmounted(() => {
cleanup()
})
</script>

View File

@ -0,0 +1,63 @@
<template>
<Image ref="image" v-if="hairSpriteId" />
</template>
<script lang="ts" setup>
import type { MapCharacter, Sprite as SpriteT } from '@/application/types'
import { loadSpriteTextures } from '@/services/textureService'
import { CharacterHairStorage, CharacterTypeStorage, SpriteStorage } from '@/storage/storages'
import { Image, refObj, useScene } from 'phavuer'
import { computed, onMounted, ref, watch } from 'vue'
const props = defineProps<{
mapCharacter: MapCharacter
}>()
const scene = useScene()
const hairSpriteId = ref('')
const hairSprite = ref<SpriteT | null>(null)
const characterSpriteHeight = ref(0)
const image = refObj<Phaser.GameObjects.Image>()
const flipX = computed(() => [6, 0].includes(props.mapCharacter.character.rotation ?? 0))
const texture = computed(() => {
const direction = flipX.value ? 'back' : 'front'
return `${hairSpriteId.value}-${direction}`
})
watch(
() => props.mapCharacter.character,
(newValue) => {
if (!image.value) return
image.value.setTexture(texture.value)
},
{ deep: true }
)
onMounted(async () => {
if (!props.mapCharacter.character.characterType || !props.mapCharacter.character.characterHair) return
const characterTypeStorage = new CharacterTypeStorage()
const characterHairStorage = new CharacterHairStorage()
const spriteStorage = new SpriteStorage()
const characterType = await characterTypeStorage.getById(props.mapCharacter.character.characterType!)
if (!characterType) return
characterSpriteHeight.value = 100
hairSpriteId.value = await characterHairStorage.getSpriteId(props.mapCharacter.character.characterHair)
if (!hairSpriteId.value) return
hairSprite.value = await spriteStorage.getById(hairSpriteId.value)
if (!hairSprite.value) return
await loadSpriteTextures(scene, hairSpriteId.value)
if (!image.value) return
image.value.setOrigin(0.5, 2.15)
image.value.setTexture(texture.value)
image.value.setSize(30, 40)
})
</script>

View File

@ -0,0 +1,45 @@
<template>
<Container ref="characterChatContainer">
<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 { MapCharacter } from '@/application/types'
import { Container, refObj, RoundRectangle, Text, useGame } from 'phavuer'
import { onMounted } from 'vue'
const props = defineProps<{
mapCharacter: MapCharacter
}>()
const game = useGame()
const characterChatContainer = refObj<Phaser.GameObjects.Container>()
const createChatBubble = (container: Phaser.GameObjects.Container) => {
container.setName(`${props.mapCharacter.character.name}_chatBubble`)
}
const createChatText = (text: Phaser.GameObjects.Text) => {
text.setName(`${props.mapCharacter.character.name}_chatText`)
text.setFontSize(13)
text.setFontFamily('Arial')
text.setOrigin(0.5, 10.9)
text.setResolution(2)
// 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(() => {
characterChatContainer.value!.setName(`${props.mapCharacter.character!.name}_chatContainer`)
characterChatContainer.value!.setVisible(false)
})
</script>

View File

@ -0,0 +1,34 @@
<template>
<Container :depth="999">
<Text @create="createNicknameText" :text="props.mapCharacter.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 { MapCharacter } from '@/application/types'
import { Container, RoundRectangle, Text, useGame } from 'phavuer'
const props = defineProps<{
mapCharacter: MapCharacter
}>()
const game = useGame()
const createNicknameText = (text: Phaser.GameObjects.Text) => {
text.setFontSize(13)
text.setFontFamily('Arial')
text.setOrigin(0.5, 9)
text.setResolution(2)
// 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,173 @@
<template>
<div class="absolute" v-if="gameStore.uiSettings.isCharacterProfileOpen" :style="modalStyle">
<div class="relative">
<img src="/assets/ui-elements/profile-ui-box-outer.svg" class="absolute w-full h-full" alt="" />
<img src="/assets/ui-elements/profile-ui-box-inner.svg" class="absolute left-2 bottom-2 w-[calc(100%_-_16px)] h-[calc(100%_-_40px)]" alt="" />
<div @mousedown="startDrag" class="cursor-move px-5 py-2.5 flex justify-between items-center relative">
<span class="text-xs text-white font-thin">Character Profile [Alt+C]</span>
<button @click="gameStore.uiSettings.isCharacterProfileOpen = false" class="w-3.5 h-3.5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out">
<img draggable="false" src="/assets/icons/modal/close-button-white.svg" class="w-full h-full" alt="Close button icon" />
</button>
</div>
<div class="py-4 px-6 flex flex-col gap-7 relative z-10">
<div class="flex flex-col gap-2.5">
<div class="flex justify-between">
<div>
<p class="text-sm m-0 font-bold text-white tracking-wide">{{ gameStore.character?.name }}</p>
<span class="text-xs">{{ gameStore.character?.experience }} / 18.600XP</span>
</div>
<a class="hover:cursor-pointer bg-[url('/assets/ui-elements/button-ui-box-textured-small.svg')] bg-no-repeat block w-8 h-8 relative mx-[3px]">
<img class="hover:drop-shadow-default w-3.5 h-3.5 m-[9px] object-contain" draggable="false" src="/assets/icons/plus-green-icon.svg" alt="Plus button icon" />
</a>
</div>
<div class="flex justify-between">
<div class="flex flex-col gap-0.5">
<div class="w-9 h-9 default-border rounded-sm bg-gray relative hover:bg-gray-600">
<span class="absolute w-full top-1/2 -translate-y-1/2 text-[6px] text-center">CROWN</span>
</div>
<div class="w-9 h-9 default-border rounded-sm bg-gray relative hover:bg-gray-600">
<span class="absolute w-full top-1/2 -translate-y-1/2 text-[6px] text-center">R-HAND</span>
</div>
<div class="flex gap-0.5 items-end">
<div class="w-9 h-9 default-border rounded-sm bg-gray relative hover:bg-gray-600">
<span class="absolute w-full top-1/2 -translate-y-1/2 text-[6px] text-center">L-HAND</span>
</div>
<div class="w-6 h-6 default-border rounded-sm bg-gray relative hover:bg-gray-600">
<span class="absolute w-full top-1/2 -translate-y-1/2 text-[6px] text-center">RING</span>
</div>
</div>
</div>
<img src="/assets/placeholders/inventory_player.png" class="w-8 h-auto" alt="Player character sprite" />
<div class="flex flex-col items-end gap-0.5">
<div class="w-9 h-9 default-border rounded-sm bg-gray relative hover:bg-gray-600">
<img class="w-6 h-6 center-element" src="/assets/icons/profile/helmet.svg" alt="Helmet icon" />
</div>
<div class="w-9 h-9 default-border rounded-sm bg-gray relative hover:bg-gray-600">
<img class="w-6 h-6 center-element" src="/assets/icons/profile/chestplate.svg" alt="Chestplate icon" />
</div>
<div class="flex gap-0.5 items-end">
<div class="w-6 h-6 default-border rounded-sm bg-gray relative hover:bg-gray-600">
<img class="w-4 h-4 center-element" src="/assets/icons/profile/boots.svg" alt="Boots icon" />
</div>
<div class="w-9 h-9 default-border rounded-sm bg-gray relative hover:bg-gray-600">
<img class="w-6 h-6 center-element" src="/assets/icons/profile/legs.svg" alt="Legs icon" />
</div>
</div>
</div>
</div>
<div class="flex justify-between">
<div class="flex flex-col">
<div class="w-[105px] h-px mb-[3px] flex justify-between">
<div class="w-px h-full bg-gray-300"></div>
<div class="w-[101px] h-full bg-gray-300"></div>
<div class="w-px h-full bg-gray-300"></div>
</div>
<div class="flex gap-11">
<p class="m-0 text-xs text-white tracking-wide">Health</p>
<span class="m-0 text-xs text-white tracking-wide">+ 15</span>
</div>
<div class="w-[105px] h-px my-[3px] flex justify-between">
<div class="w-px h-full bg-gray-300"></div>
<div class="w-[101px] h-full bg-gray-300"></div>
<div class="w-px h-full bg-gray-300"></div>
</div>
<div class="flex gap-11">
<p class="m-0 text-xs text-white tracking-wide">Health</p>
<span class="m-0 text-xs text-white tracking-wide">+ 15</span>
</div>
<div class="w-[105px] h-px mt-[3px] flex justify-between">
<div class="w-px h-full bg-gray-300"></div>
<div class="w-[101px] h-full bg-gray-300"></div>
<div class="w-px h-full bg-gray-300"></div>
</div>
</div>
<div class="flex flex-col">
<div class="w-[105px] h-px mb-[3px] flex justify-between">
<div class="w-px h-full bg-gray-300"></div>
<div class="w-[101px] h-full bg-gray-300"></div>
<div class="w-px h-full bg-gray-300"></div>
</div>
<div class="flex gap-11">
<p class="m-0 text-xs text-white tracking-wide">Health</p>
<span class="m-0 text-xs text-white tracking-wide">+ 15</span>
</div>
<div class="w-[105px] h-px my-[3px] flex justify-between">
<div class="w-px h-full bg-gray-300"></div>
<div class="w-[101px] h-full bg-gray-300"></div>
<div class="w-px h-full bg-gray-300"></div>
</div>
<div class="flex gap-11">
<p class="m-0 text-xs text-white tracking-wide">Health</p>
<span class="m-0 text-xs text-white tracking-wide">+ 15</span>
</div>
<div class="w-[105px] h-px mt-[3px] flex justify-between">
<div class="w-px h-full bg-gray-300"></div>
<div class="w-[101px] h-full bg-gray-300"></div>
<div class="w-px h-full bg-gray-300"></div>
</div>
</div>
</div>
</div>
<div class="grid grid-rows-4 grid-cols-6 gap-0.5">
<div v-for="n in 24" class="w-9 h-9 default-border rounded-sm bg-gray relative hover:bg-gray-600"></div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useGameStore } from '@/stores/gameStore'
import { computed, onMounted, onUnmounted, ref } from 'vue'
const gameStore = useGameStore()
const width = ref(286)
const height = ref(483)
const x = ref(window.innerWidth / 2 - 143)
const y = ref(window.innerHeight / 2 - 241)
const isDragging = ref(false)
const modalStyle = computed(() => ({
top: `${y.value}px`,
left: `${x.value}px`,
width: `${width.value}px`,
height: `${height.value}px`
}))
function startDrag(event: MouseEvent) {
isDragging.value = true
const startX = event.clientX - x.value
const startY = event.clientY - y.value
function drag(event: MouseEvent) {
if (!isDragging.value) return
x.value = event.clientX - startX
y.value = event.clientY - startY
}
function stopDrag() {
isDragging.value = false
removeEventListener('mousemove', drag)
removeEventListener('mouseup', stopDrag)
}
addEventListener('mousemove', drag)
addEventListener('mouseup', stopDrag)
}
function keyPress(event: KeyboardEvent) {
if (event.altKey && event.key === 'c') {
gameStore.toggleCharacterProfile()
}
}
onMounted(() => {
addEventListener('keydown', keyPress)
})
onUnmounted(() => {
removeEventListener('keydown', keyPress)
})
</script>

View File

@ -2,41 +2,60 @@
<div class="w-full md:min-w-[350px] max-w-[750px] flex flex-col absolute left-1/2 -translate-x-1/2 bottom-5"> <div class="w-full md:min-w-[350px] max-w-[750px] flex flex-col absolute left-1/2 -translate-x-1/2 bottom-5">
<div ref="chatWindow" class="w-full overflow-auto h-32 mb-5 bg-gray rounded-md border-2 border-solid border-gray-500 text-gray-300" v-show="gameStore.uiSettings.isChatOpen"> <div ref="chatWindow" class="w-full overflow-auto h-32 mb-5 bg-gray rounded-md border-2 border-solid border-gray-500 text-gray-300" v-show="gameStore.uiSettings.isChatOpen">
<div v-for="message in chats" class="flex-col py-2 items-center p-3"> <div v-for="message in chats" class="flex-col py-2 items-center p-3">
<span class="text-ellipsis overflow-hidden whitespace-nowrap text-sm" :class="{ 'text-cyan-300': gameStore.character?.role == 'gm' }">{{ message.character.name }}</span> <span class="text-ellipsis overflow-hidden whitespace-nowrap text-sm" :class="{ 'text-cyan-300': gameStore.character?.role == 'gm' }">{{ message.character }}</span>
<p class="text-gray-50 m-0">{{ message.message }}</p> <p class="text-gray-50 m-0">{{ message.message }}</p>
</div> </div>
</div> </div>
<div class="w-96 mx-auto relative"> <div class="w-96 mx-auto relative">
<img src="/assets/icons/ingameUI/chat-icon.svg" class="absolute top-1/2 -translate-y-1/2 left-2.5 h-4 w-4 opacity-50" /> <img src="/assets/icons/ingameUI/chat-icon.svg" class="absolute top-1/2 -translate-y-1/2 left-2.5 h-4 w-4 opacity-50" alt="" />
<input <input
class="w-[332px] h-8 rounded-sm text-xs font-default pl-8 pr-4 py-0 bg-gray-600 border-2 border-solid border-gray-500 text-gray-300 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover focus:outline-none focus:ring-0 focus:border-cyan-800" class="w-[332px] h-8 rounded-sm text-xs font-default pl-8 pr-4 py-0 bg-gray-600 border-2 border-solid border-gray-500 text-gray-300 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover focus:outline-none focus:ring-0 focus:border-cyan-800"
placeholder="Type something..." placeholder="Type something..."
v-model="message" v-model="message"
@keypress="handleKeyPress" @keypress="handleKeyPress"
@submit="handleSubmit" @submit="handleSubmit"
ref="chatInput"
/> />
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, nextTick } from 'vue' import { SocketEvent } from '@/application/enums'
import { socketManager } from '@/managers/SocketManager'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import type { Character, ChatMessage } from '@/types' import { useMapStore } from '@/stores/mapStore'
import { useZoneStore } from '@/stores/zoneStore' import { onClickOutside, useFocus } from '@vueuse/core'
import { useScene } from 'phavuer' import { useScene } from 'phavuer'
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
const scene = useScene() const scene = useScene()
const gameStore = useGameStore() const gameStore = useGameStore()
const zoneStore = useZoneStore()
const message = ref('') const message = ref('')
const chats = ref([] as ChatMessage[]) const chats = ref<{ character: string; message: string }[]>([])
const chatWindow = ref<HTMLElement | null>(null) const chatWindow = ref<HTMLElement | null>(null)
const chatInput = ref<HTMLElement | null>(null)
const { focused } = useFocus(chatInput)
function focusChat(event: KeyboardEvent) {
if (event.key === 'Enter' && !focused.value) {
focused.value = true
}
}
onClickOutside(chatInput, (event) => unfocusChat(event, chatInput.value as HTMLElement))
function unfocusChat(event: Event, targetElement: HTMLElement) {
if (!(event.target instanceof Node) || !targetElement.contains(event.target)) {
targetElement.blur()
}
}
const sendMessage = () => { const sendMessage = () => {
if (!message.value.trim()) return if (!message.value.trim()) return
gameStore.connection?.emit('chat:send_message', { message: message.value }, (response: boolean) => {}) socketManager.emit(SocketEvent.CHAT_MESSAGE, { message: message.value }, (response: boolean) => {})
message.value = '' message.value = ''
} }
@ -60,18 +79,30 @@ const scrollToBottom = () => {
}) })
} }
gameStore.connection?.on('chat:message', (data: ChatMessage) => { socketManager.on(SocketEvent.CHAT_MESSAGE, (data: { character: string; message: string }) => {
chats.value.push(data) if (!data.character || !data.message) return
chats.value.push({ character: data.character, message: data.message })
scrollToBottom() scrollToBottom()
if (!zoneStore.characterLoaded) return const characterContainer = scene.children.getByName(data.character) as Phaser.GameObjects.Container
if (!characterContainer) {
console.log('No character container found')
return
}
const charChatContainer = scene.children.getByName(data.character.name + '_chatContainer') as Phaser.GameObjects.Container const characterChatContainer = characterContainer.getByName(data.character + '_chatContainer') as Phaser.GameObjects.Container
if (!charChatContainer) return if (!characterChatContainer) {
console.log('No character chat container found')
return
}
const chatBubble = charChatContainer.getByName(data.character.name + '_chatBubble') as Phaser.GameObjects.Container const chatBubble = characterChatContainer.getByName(data.character + '_chatBubble') as Phaser.GameObjects.Container
const chatText = charChatContainer.getByName(data.character.name + '_chatText') as Phaser.GameObjects.Text const chatText = characterChatContainer.getByName(data.character + '_chatText') as Phaser.GameObjects.Text
if (!chatText || !chatBubble) return if (!chatText || !chatBubble) {
console.log('No chat text or bubble found')
return
}
function calculateTextWidth(text: string, font: string, fontSize: number): number { function calculateTextWidth(text: string, font: string, fontSize: number): number {
// Create a canvas element // Create a canvas element
@ -96,28 +127,33 @@ gameStore.connection?.on('chat:message', (data: ChatMessage) => {
// setText but with max. char limit of 90 // setText but with max. char limit of 90
chatText.setText(data.message.substring(0, 90)) chatText.setText(data.message.substring(0, 90))
charChatContainer.setVisible(true) characterChatContainer.setVisible(true)
/** /**
* Hide chat bubble after a few seconds * Hide chat bubble after a few seconds
*/ */
// Clear any existing hide timer // Clear any existing hide timer
if (charChatContainer.getData('hideTimer')) { if (characterChatContainer.getData('hideTimer')) {
clearTimeout(charChatContainer.getData('hideTimer')) clearTimeout(characterChatContainer.getData('hideTimer'))
} }
// Set a new hide timer // Set a new hide timer
const hideTimer = setTimeout(() => { const hideTimer = setTimeout(() => {
charChatContainer.setVisible(false) characterChatContainer.setVisible(false)
}, 3000) }, 3000)
// Store the timer on the container itself // Store the timer on the container itself
charChatContainer.setData('hideTimer', hideTimer) characterChatContainer.setData('hideTimer', hideTimer)
}) })
scrollToBottom() scrollToBottom()
onMounted(() => {
addEventListener('keydown', focusChat)
})
onBeforeUnmount(() => { onBeforeUnmount(() => {
gameStore.connection?.off('chat:message') socketManager.off(SocketEvent.CHAT_MESSAGE)
removeEventListener('keydown', focusChat)
}) })
</script> </script>

View File

@ -0,0 +1,21 @@
<template>
<div class="absolute top-0 right-4 hidden lg:block" v-if="gameStore.world.date && typeof gameStore.world.date === 'object'">
<p class="text-white text-lg">
{{ useDateFormat(gameStore.world.date, 'YYYY/MM/DD HH:mm') }}
</p>
</div>
</template>
<script setup lang="ts">
import { SocketEvent } from '@/application/enums'
import { socketManager } from '@/managers/SocketManager'
import { useGameStore } from '@/stores/gameStore'
import { useDateFormat } from '@vueuse/core'
import { onUnmounted } from 'vue'
const gameStore = useGameStore()
onUnmounted(() => {
socketManager.off(SocketEvent.DATE)
})
</script>

View File

@ -6,7 +6,7 @@
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div> <div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
</div> </div>
<a class="group-hover:cursor-pointer bg-[url('/assets/ui-elements/button-ui-box-textured.svg')] bg-no-repeat block w-[42px] h-[42px] relative"> <a class="group-hover:cursor-pointer bg-[url('/assets/ui-elements/button-ui-box-textured.svg')] bg-no-repeat block w-[42px] h-[42px] relative">
<img class="group-hover:drop-shadow-default w-6 h-5 mx-[9px] my-[11px] object-contain" draggable="false" src="/assets/icons/ingameUI/menu-icon.svg" /> <img class="group-hover:drop-shadow-default w-6 h-5 mx-[9px] my-[11px] object-contain" draggable="false" src="/assets/icons/ingameUI/menu-icon.svg" alt="Menu button icon" />
</a> </a>
</li> </li>
<li class="menu-item group relative" @click="gameStore.toggleCharacterProfile"> <li class="menu-item group relative" @click="gameStore.toggleCharacterProfile">
@ -15,7 +15,7 @@
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div> <div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
</div> </div>
<a class="group-hover:cursor-pointer bg-[url('/assets/ui-elements/button-ui-box-textured.svg')] bg-no-repeat block w-[42px] h-[42px] relative"> <a class="group-hover:cursor-pointer bg-[url('/assets/ui-elements/button-ui-box-textured.svg')] bg-no-repeat block w-[42px] h-[42px] relative">
<img class="group-hover:drop-shadow-default w-8 h-8 m-[5px] object-contain" draggable="false" src="/assets/avatar/default/head.png" /> <img class="group-hover:drop-shadow-default w-8 h-8 m-[5px] object-contain" draggable="false" src="/assets/placeholders/head.png" alt="User profile button icon" />
<p class="absolute bottom-0 -right-1.5 m-0 max-w-4 font-ui z-10 text-white text-[12px] leading-[6px] drop-shadow-pixel"><span class="font-ui text-white text-[8px] ml-0.5">LVL</span> {{ characterLevel }}</p> <p class="absolute bottom-0 -right-1.5 m-0 max-w-4 font-ui z-10 text-white text-[12px] leading-[6px] drop-shadow-pixel"><span class="font-ui text-white text-[8px] ml-0.5">LVL</span> {{ characterLevel }}</p>
</a> </a>
</li> </li>
@ -24,8 +24,8 @@
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">Open Chat</p> <p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">Open Chat</p>
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div> <div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
</div> </div>
<a class="group-hover:bg-gray-800 bg-gray-900 group-hover:cursor-pointer border border-b-4 border-solid rounded border-gray-500 block w-[34px] h-[31px]"> <a class="group-hover:bg-gray-800 bg-gray-900 group-hover:cursor-pointer border-2 border-solid rounded border-gray-500 block w-[34px] h-[31px]">
<img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/ingameUI/chat-icon.svg" /> <img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/ingameUI/chat-icon.svg" alt="Open chat button icon" />
</a> </a>
</li> </li>
<li class="menu-item group relative"> <li class="menu-item group relative">
@ -33,8 +33,8 @@
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">World map</p> <p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">World map</p>
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div> <div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
</div> </div>
<a class="group-hover:bg-gray-800 bg-gray-900 group-hover:cursor-pointer border border-b-4 border-solid rounded border-gray-500 block w-[34px] h-[31px]"> <a class="group-hover:bg-gray-800 bg-gray-900 group-hover:cursor-pointer border-2 border-solid rounded border-gray-500 block w-[34px] h-[31px]">
<img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/ingameUI/map-icon.svg" /> <img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/ingameUI/map-icon.svg" alt="World map button icon" />
</a> </a>
</li> </li>
<li class="menu-item group relative"> <li class="menu-item group relative">
@ -42,8 +42,8 @@
<p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">Users</p> <p class="absolute w-full bottom-0 m-0 text-xs leading-6 text-white">Users</p>
<div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div> <div class="group-hover:block absolute -left-2 bg-gray-500 h-3.5 w-2 [clip-path:polygon(100%_0,_0_50%,_100%_100%)] top-1/2 -translate-y-1/2 hidden"></div>
</div> </div>
<a class="group-hover:bg-gray-800 bg-gray-900 group-hover:cursor-pointer border border-b-4 border-solid rounded border-gray-500 block w-[34px] h-[31px]"> <a class="group-hover:bg-gray-800 bg-gray-900 group-hover:cursor-pointer border-2 border-solid rounded border-gray-500 block w-[34px] h-[31px]">
<img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/ingameUI/socials-icon.svg" /> <img class="group-hover:drop-shadow-default w-6 h-6 m-[5px] object-contain" draggable="false" src="/assets/icons/ingameUI/socials-icon.svg" alt="Users button icon" />
</a> </a>
</li> </li>
</ul> </ul>

View File

@ -1,16 +1,16 @@
<template> <template>
<div class="absolute top-4 right-4 hidden lg:block"> <div class="absolute top-4 right-4 hidden lg:block">
<div class="w-40 h-40 rounded-full border border-solid border-gray-500 bg-[url('/assets/ui-texture.png')] bg-no-repeat"> <div class="w-40 h-40 rounded-full default-border bg-[url('/assets/ui-texture.png')] bg-no-repeat">
<div class="w-40 h-40 rounded-full shadow-inner"></div> <div class="w-40 h-40 rounded-full shadow-inner"></div>
</div> </div>
<div class="absolute -bottom-3 left-1/2 -translate-x-1/2 flex gap-1"> <div class="absolute -bottom-3 left-1/2 -translate-x-1/2 flex gap-1">
<button class="w-6 h-6 relative p-0"> <button class="w-6 h-6 relative p-0">
<img class="absolute w-3 h-3 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" src="/assets/icons/plus-icon.svg" /> <img class="w-3 h-3 center-element" src="/assets/icons/plus-icon.svg" alt="Zoom-in button icon" />
<img class="w-full h-full" src="/assets/ui-elements/button-ui-box-textured.svg" /> <img class="w-full h-full" src="/assets/ui-elements/button-ui-box-textured.svg" alt="" />
</button> </button>
<button class="w-6 h-6 relative p-0"> <button class="w-6 h-6 relative p-0">
<img class="absolute w-3 h-3 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" src="/assets/icons/minus-icon.svg" /> <img class="w-3 h-3 center-element" src="/assets/icons/minus-icon.svg" alt="Zoom-out button icon" />
<img class="w-full h-full" src="/assets/ui-elements/button-ui-box-textured.svg" /> <img class="w-full h-full" src="/assets/ui-elements/button-ui-box-textured.svg" alt="" />
</button> </button>
</div> </div>
</div> </div>

View File

@ -0,0 +1,49 @@
<template>
<Character v-for="item in mapStore.characters" :key="item.character.id" :tileMap :mapCharacter="item" />
</template>
<script setup lang="ts">
import { SocketEvent } from '@/application/enums'
import type { MapCharacter, UUID } from '@/application/types'
import Character from '@/components/game/character/Character.vue'
import { socketManager } from '@/managers/SocketManager'
import { useGameStore } from '@/stores/gameStore'
import { useMapStore } from '@/stores/mapStore'
import { onUnmounted } from 'vue'
const gameStore = useGameStore()
const mapStore = useMapStore()
const props = defineProps<{
tileMap: Phaser.Tilemaps.Tilemap
}>()
socketManager.on(SocketEvent.MAP_CHARACTER_JOIN, (data: MapCharacter) => {
mapStore.addCharacter(data)
})
socketManager.on(SocketEvent.MAP_CHARACTER_LEAVE, (characterId: UUID) => {
mapStore.removeCharacter(characterId)
})
socketManager.on(SocketEvent.MAP_CHARACTER_MOVE, ([characterId, posX, posY, rot, isMoving]: [UUID, number, number, number, boolean]) => {
mapStore.updateCharacterPosition([characterId, posX, posY, rot, isMoving])
if (characterId === gameStore.character?.id) {
gameStore.character!.positionX = posX
gameStore.character!.positionY = posY
gameStore.character!.rotation = rot
}
})
socketManager.on(SocketEvent.MAP_CHARACTER_ATTACK, (characterId: UUID) => {
mapStore.updateCharacterProperty(characterId, 'isAttacking', true)
})
onUnmounted(() => {
socketManager.off(SocketEvent.MAP_CHARACTER_ATTACK)
socketManager.off(SocketEvent.MAP_CHARACTER_MOVE)
socketManager.off(SocketEvent.MAP_CHARACTER_JOIN)
socketManager.off(SocketEvent.MAP_CHARACTER_LEAVE)
})
</script>

View File

@ -0,0 +1,71 @@
<template>
<MapTiles v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
<PlacedMapObjects v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
<Characters v-if="tileMap && mapStore.characters" :tileMap />
</template>
<script setup lang="ts">
import { SocketEvent } from '@/application/enums'
import type { mapLoadData } from '@/application/types'
import { unduplicateArray } from '@/application/utilities'
import Characters from '@/components/game/map/Characters.vue'
import MapTiles from '@/components/game/map/MapTiles.vue'
import PlacedMapObjects from '@/components/game/map/PlacedMapObjects.vue'
import { socketManager } from '@/managers/SocketManager'
import { createTileLayer, createTileMap, loadTileTexturesFromMapTileArray } from '@/services/mapService'
import { MapStorage } from '@/storage/storages'
import { useGameStore } from '@/stores/gameStore'
import { useMapStore } from '@/stores/mapStore'
import { useScene } from 'phavuer'
import { onMounted, onUnmounted, shallowRef, watch } from 'vue'
const scene = useScene()
const gameStore = useGameStore()
const mapStore = useMapStore()
const mapStorage = new MapStorage()
const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
const tileMapLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
// Event listeners
socketManager.on(SocketEvent.MAP_CHARACTER_TELEPORT, (data: mapLoadData) => {
mapStore.setMapId(data.mapId)
mapStore.setCharacters(data.characters)
})
async function initialize() {
if (!mapStore.mapId) return
const map = await mapStorage.getById(mapStore.mapId)
if (!map) return
await loadTileTexturesFromMapTileArray(mapStore.mapId, scene)
tileMap.value = createTileMap(scene, map)
tileMapLayer.value = createTileLayer(tileMap.value, unduplicateArray(map.tiles))
}
watch(
() => mapStore.mapId,
async () => {
await initialize()
}
)
onMounted(async () => {
if (!mapStore.mapId) return
await initialize()
})
onUnmounted(() => {
if (tileMap.value) {
tileMap.value.destroyLayer('tiles')
tileMap.value.removeAllLayers()
tileMap.value.destroy()
}
socketManager.off(SocketEvent.MAP_CHARACTER_TELEPORT)
})
</script>

View File

@ -0,0 +1,32 @@
<template>
<Controls v-if="tileMapLayer" :layer="tileMapLayer" :depth="0" />
</template>
<script setup lang="ts">
import Controls from '@/components/utilities/Controls.vue'
import { loadTileTexturesFromMapTileArray, placeTiles } from '@/services/mapService'
import { MapStorage } from '@/storage/storages'
import { useMapStore } from '@/stores/mapStore'
import { useScene } from 'phavuer'
import { onMounted } from 'vue'
const scene = useScene()
const mapStore = useMapStore()
const mapStorage = new MapStorage()
const props = defineProps<{
tileMap: Phaser.Tilemaps.Tilemap
tileMapLayer: Phaser.Tilemaps.TilemapLayer
}>()
onMounted(async () => {
if (!mapStore.mapId) return
const map = await mapStorage.getById(mapStore.mapId)
if (!map) return
await loadTileTexturesFromMapTileArray(mapStore.mapId, scene)
placeTiles(props.tileMap, props.tileMapLayer, map.tiles)
})
</script>

View File

@ -0,0 +1,31 @@
<template>
<PlacedMapObject v-for="placedMapObject in items" :tileMap :tileMapLayer :placedMapObject />
</template>
<script setup lang="ts">
import type { PlacedMapObject as PlacedMapObjectT } from '@/application/types'
import PlacedMapObject from '@/components/game/map/partials/PlacedMapObject.vue'
import { MapStorage } from '@/storage/storages'
import { useMapStore } from '@/stores/mapStore'
import { onMounted, ref } from 'vue'
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
defineProps<{
tileMap: Phaser.Tilemaps.Tilemap
tileMapLayer: TilemapLayer
}>()
const mapStore = useMapStore()
const mapStorage = new MapStorage()
const items = ref<PlacedMapObjectT[]>([])
onMounted(async () => {
if (!mapStore.mapId) return
const map = await mapStorage.getById(mapStore.mapId)
if (!map) return
items.value = map.placedMapObjects
})
</script>

View File

@ -0,0 +1,102 @@
<template>
<Zone :depth="baseDepth" :origin-x="mapObj?.originX" :origin-y="mapObj?.originY" :width="mapObj?.frameWidth" :height="mapObj?.frameHeight" :x="x" :y="y" />
</template>
<script setup lang="ts">
import type { MapObject, PlacedMapObject } from '@/application/types'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { calculateIsometricDepth } from '@/services/mapService'
import { onPreUpdate, useScene, Zone } from 'phavuer'
import { computed, onUnmounted } from 'vue'
interface Props {
obj?: PlacedMapObject
mapObj?: MapObject
x?: number
y?: number
}
const props = defineProps<Props>()
const mapEditor = useMapEditorComposable()
const scene = useScene()
const group = scene.add.group()
const partitionPoints = computed(() => {
if (!props.mapObj?.frameWidth || !props.mapObj?.depthOffsets.length) return []
const sliceCount = props.mapObj.depthOffsets.length
return Array.from({ length: sliceCount + 1 }, (_, i) => i * (props.mapObj!.frameWidth / sliceCount))
})
let baseDepth = 0
const createImagePartition = (startX: number, endX: number, depthOffset: number): void => {
if (!props.mapObj?.id) return
const img = scene.add.image(0, 0, props.mapObj.id)
img.setOrigin(props.mapObj.originX, props.mapObj.originY)
img.setCrop(startX, 0, endX, props.mapObj.frameHeight)
img.setDepth(baseDepth + depthOffset)
group.add(img)
}
const updateGroupProperties = (): void => {
if (!props.obj || !props.x || !props.y) return
const isMoving = mapEditor.movingPlacedObject.value?.id === props.obj.id
const isSelected = mapEditor.selectedMapObject.value?.id === props.obj.id
const isPlacedSelected = mapEditor.selectedPlacedObject.value?.id === props.obj.id
baseDepth = calculateIsometricDepth(props.obj.positionX, props.obj.positionY)
group.setXY(props.x, props.y)
group.setAlpha(isMoving || isSelected ? 0.5 : 1)
group.setTint(isPlacedSelected ? 0x00ff00 : 0xffffff)
group.setDepth(baseDepth)
}
const updateImageProperties = (): void => {
const orderedImages = group.getChildren() as Phaser.GameObjects.Image[]
orderedImages.forEach((image, index) => {
if (!props.obj || !props.mapObj || !props.x) return
image.flipX = props.obj.isRotated
if (props.obj.isRotated) {
const offsetNum = props.mapObj.depthOffsets.length
const xOffset = props.mapObj.frameWidth / offsetNum
image.x = props.x + (index < offsetNum / 2 ? -xOffset : xOffset)
image.setDepth(baseDepth - props.mapObj.depthOffsets[index])
} else {
image.x = props.x
image.setDepth(baseDepth + props.mapObj.depthOffsets[index])
}
})
}
onPreUpdate(() => {
updateGroupProperties()
updateImageProperties()
})
// Initial setup
const initializeGroup = (): void => {
if (!props.mapObj || !props.x || !props.y || !props.obj) return
baseDepth = calculateIsometricDepth(props.obj.positionX, props.obj.positionY)
group.setXY(props.x, props.y)
group.setOrigin(props.mapObj.originX, props.mapObj.originY)
const points = partitionPoints.value
for (let i = 0; i < points.length - 1; i++) {
createImagePartition(points[i], points[i + 1], props.mapObj.depthOffsets[i])
}
}
initializeGroup()
onUnmounted(() => {
group.destroy(true, true)
})
</script>

View File

@ -0,0 +1,70 @@
<template>
<ImageGroup v-bind="groupProps" v-if="mapObject && gameStore.isTextureLoaded(props.placedMapObject.mapObject as string)" />
</template>
<script setup lang="ts">
import type { MapObject, PlacedMapObject } from '@/application/types'
import ImageGroup from '@/components/game/map/partials/ImageGroup.vue'
import { loadMapObjectTextures, tileToWorldXY } from '@/services/mapService'
import { MapObjectStorage } from '@/storage/storages'
import { useGameStore } from '@/stores/gameStore'
import { useScene } from 'phavuer'
import { computed, onMounted, ref } from 'vue'
import Tilemap = Phaser.Tilemaps.Tilemap
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
const props = defineProps<{
placedMapObject: PlacedMapObject
tileMap: Tilemap
tileMapLayer: TilemapLayer
}>()
const scene = useScene()
const gameStore = useGameStore()
const mapObject = ref<MapObject>()
const groupProps = computed(() => ({
...calculateObjectPlacement(props.placedMapObject),
mapObj: mapObject.value,
obj: props.placedMapObject
}))
async function initialize() {
if (!props.placedMapObject.mapObject) return
/**
* Check if mapObject is an string or object, if its an object we assume its a mapObject and change it to a string
* We do this because this component is shared with the map editor, which gets sent the mapObject as an object by the server
*/
if (typeof props.placedMapObject.mapObject === 'object') {
// @ts-ignore
props.placedMapObject.mapObject = props.placedMapObject.mapObject.id
}
const mapObjectStorage = new MapObjectStorage()
const _mapObject = await mapObjectStorage.getById(props.placedMapObject.mapObject as string)
if (!_mapObject) return
console.log(_mapObject)
mapObject.value = _mapObject
await loadMapObjectTextures([_mapObject], scene)
}
function calculateObjectPlacement(mapObj: PlacedMapObject): { x: number; y: number } {
let position = tileToWorldXY(props.tileMapLayer, mapObj.positionX, mapObj.positionY)
return {
x: position.worldPositionX,
y: position.worldPositionY
}
}
onMounted(async () => {
await initialize()
})
</script>

View File

@ -1,12 +1,12 @@
<template> <template>
<Modal :isModalOpen="gameStore.uiSettings.isGmPanelOpen" @modal:close="() => gameStore.toggleGmPanel()" :modal-width="1000" :modal-height="650" :can-full-screen="true"> <Modal :isModalOpen="gameStore.uiSettings.isGmPanelOpen" @modal:close="() => gameStore.toggleGmPanel()" :modal-width="1000" :modal-height="650" :is-full-screen="true" :bg-style="'dark'">
<template #modalHeader> <template #modalHeader>
<div class="flex gap-1.5 flex-wrap"> <div class="flex gap-1.5 flex-wrap">
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">General</button> <button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">General</button>
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">Users</button> <button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">Users</button>
<button @mousedown.stop class="btn-cyan py-1.5 px-4 min-w-24">Chats</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 @mousedown.stop class="btn-cyan active py-1.5 px-4 min-w-24">Asset manager</button>
<button class="btn-cyan py-1.5 px-4 min-w-24" type="button" @click="() => zoneEditorStore.toggleActive()">Zone manager</button> <button class="btn-cyan py-1.5 px-4 min-w-24" type="button" @click="mapEditor.toggleActive()">Map editor</button>
</div> </div>
</template> </template>
<template #modalBody> <template #modalBody>
@ -18,14 +18,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'
import Modal from '@/components/utilities/Modal.vue'
import AssetManager from '@/components/gameMaster/assetManager/AssetManager.vue' import AssetManager from '@/components/gameMaster/assetManager/AssetManager.vue'
import Modal from '@/components/utilities/Modal.vue'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useZoneEditorStore } from '@/stores/zoneEditorStore' import { ref } from 'vue'
const mapEditor = useMapEditorComposable()
const gameStore = useGameStore() const gameStore = useGameStore()
const zoneEditorStore = useZoneEditorStore()
let toggle = ref('asset-manager') let toggle = ref('asset-manager')
</script> </script>

View File

@ -1,85 +1,79 @@
<template> <template>
<div class="flex h-full w-full relative"> <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"> <div class="w-2/12 flex flex-col relative overflow-auto rounded-md default-border bg-gray p-2.5">
<!-- Asset Categories --> <!-- Asset Categories -->
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'tiles' }" @click="() => (selectedCategory = 'tiles')"> <a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group" :class="{ 'bg-cyan': selectedCategory === 'tiles' }" @click="() => (selectedCategory = 'tiles')">
<span :class="{ 'text-white': selectedCategory === 'tiles' }">Tiles</span> <span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'tiles' }">Tiles</span>
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'objects' }" @click="() => (selectedCategory = 'objects')"> <a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group" :class="{ 'bg-cyan': selectedCategory === 'map_objects' }" @click="() => (selectedCategory = 'map_objects')">
<span :class="{ 'text-white': selectedCategory === 'objects' }">Objects</span> <span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'map_objects' }">Map objects</span>
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'sprites' }" @click="() => (selectedCategory = 'sprites')"> <a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group" :class="{ 'bg-cyan': selectedCategory === 'sprites' }" @click="() => (selectedCategory = 'sprites')">
<span :class="{ 'text-white': selectedCategory === 'sprites' }">Sprites</span> <span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'sprites' }">Sprites</span>
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
<a class="relative p-2.5 hover:cursor-pointer"> <a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group" :class="{ 'bg-cyan': selectedCategory === 'items' }" @click="() => (selectedCategory = 'items')">
<span>Items</span> <span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'items' }">Items</span>
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
<a class="relative p-2.5 hover:cursor-pointer"> <a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group">
<span>NPC's</span> <span class="group-hover:text-white">NPC's</span>
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'shops' }" @click="() => (selectedCategory = 'shops')"> <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="{ 'text-white': selectedCategory === 'shops' }">Shops</span> <span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'shops' }">Shops</span>
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'characterTypes' }" @click="() => (selectedCategory = 'characterTypes')"> <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="{ 'text-white': selectedCategory === 'characterTypes' }">Character types</span> <span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'characterTypes' }">Character types</span>
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
<a class="relative p-2.5 hover:cursor-pointer" :class="{ 'bg-cyan/80': selectedCategory === 'characterHair' }" @click="() => (selectedCategory = 'characterHair')"> <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="{ 'text-white': selectedCategory === 'characterHair' }">Character hair</span> <span class="group-hover:text-white" :class="{ 'text-white': selectedCategory === 'characterHair' }">Character hair</span>
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
<a class="relative p-2.5 hover:cursor-pointer"> <a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group">
<span>Mounts</span> <span class="group-hover:text-white">Mounts</span>
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
<a class="relative p-2.5 hover:cursor-pointer"> <a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group">
<span>Pets</span> <span class="group-hover:text-white">Pets</span>
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
<a class="relative p-2.5 hover:cursor-pointer"> <a class="relative p-2.5 hover:cursor-pointer hover:bg-cyan rounded group">
<span>Emoticons</span> <span class="group-hover:text-white">Emoticons</span>
<div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
</a> </a>
</div> </div>
<div class="absolute w-px bg-gray-500 h-full top-0 left-1/6"></div>
<!-- Assets list --> <!-- Assets list -->
<div class="overflow-auto h-full w-4/12 flex flex-col relative"> <div class="overflow-auto h-full w-4/12 flex flex-col relative">
<TileList v-if="selectedCategory === 'tiles'" /> <TileList v-if="selectedCategory === 'tiles'" />
<ObjectList v-if="selectedCategory === 'objects'" /> <MapObjectList v-if="selectedCategory === 'map_objects'" />
<SpriteList v-if="selectedCategory === 'sprites'" /> <SpriteList v-if="selectedCategory === 'sprites'" />
<ItemList v-if="selectedCategory === 'items'" />
<CharacterTypeList v-if="selectedCategory === 'characterTypes'" /> <CharacterTypeList v-if="selectedCategory === 'characterTypes'" />
<CharacterHairList v-if="selectedCategory === 'characterHair'" />
</div> </div>
<div class="absolute w-px bg-gray-500 h-full top-0 left-1/2"></div>
<!-- Asset details --> <!-- Asset details -->
<div class="flex w-1/2 after:hidden flex-col relative overflow-auto"> <div class="flex w-7/12 after:hidden flex-col relative overflow-auto">
<TileDetails v-if="selectedCategory === 'tiles' && assetManagerStore.selectedTile" /> <TileDetails v-if="selectedCategory === 'tiles' && assetManagerStore.selectedTile" />
<ObjectDetails v-if="selectedCategory === 'objects' && assetManagerStore.selectedObject" /> <MapObjectDetails v-if="selectedCategory === 'map_objects' && assetManagerStore.selectedMapObject" />
<SpriteDetails v-if="selectedCategory === 'sprites' && assetManagerStore.selectedSprite" /> <SpriteDetails v-if="selectedCategory === 'sprites' && assetManagerStore.selectedSprite" />
<ItemDetails v-if="selectedCategory === 'items' && assetManagerStore.selectedItem" />
<CharacterTypeDetails v-if="selectedCategory === 'characterTypes' && assetManagerStore.selectedCharacterType" /> <CharacterTypeDetails v-if="selectedCategory === 'characterTypes' && assetManagerStore.selectedCharacterType" />
<CharacterHairDetails v-if="selectedCategory === 'characterHair' && assetManagerStore.selectedCharacterHair" />
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import CharacterHairDetails from '@/components/gameMaster/assetManager/partials/characterHair/CharacterHairDetails.vue'
import { useAssetManagerStore } from '@/stores/assetManagerStore' import CharacterHairList from '@/components/gameMaster/assetManager/partials/characterHair/CharacterHairList.vue'
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 CharacterTypeDetails from '@/components/gameMaster/assetManager/partials/characterType/CharacterTypeDetails.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 MapObjectDetails from '@/components/gameMaster/assetManager/partials/mapObject/MapObjectDetails.vue'
import MapObjectList from '@/components/gameMaster/assetManager/partials/mapObject/MapObjectList.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 assetManagerStore = useAssetManagerStore()
const selectedCategory = ref('tiles') const selectedCategory = ref('tiles')

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