Compare commits

...

478 Commits

Author SHA1 Message Date
888918face Refactoring editor 2026-03-31 10:56:44 -05:00
0885da8d44 Fixed dynamic updates on scene rendering 2026-03-29 19:08:58 -05:00
8af961c6d3 Fixed knulli rendering 2026-03-29 18:53:42 -05:00
ef5febdde3 Fixed dolphin rendering. 2026-03-29 18:42:59 -05:00
6d7fbd3926 Change to square only 2026-03-29 17:58:13 -05:00
2680d373d8 Fixed boot.dol in wii 2026-03-29 16:51:54 -05:00
2b2ddb3cf2 Fixed spritebatch flickering on Dolphin 2026-03-29 16:10:39 -05:00
85ff95296b Fix Linux again 2026-03-29 15:19:15 -05:00
314a2de41a Fixed text on PSP 2026-03-29 14:45:40 -05:00
26fafab47a Fix copy issues 2026-03-29 14:25:10 -05:00
e56ff20e2d Attempting to fix PSP alpha textures 2026-03-29 13:38:55 -05:00
55d44f229d Fixed crash on PSP 2026-03-29 10:35:57 -05:00
1c5e50cc4d Test text rendering 2026-03-29 10:15:22 -05:00
ea898da6c2 Fix compile 2026-03-28 21:52:52 -05:00
dbb7e9f53c Getting shaders working with lua. 2026-03-28 21:50:59 -05:00
cbb68a399d Fix compile error 2026-03-28 15:43:38 -05:00
0e794f28b1 Disable paletted textures for now 2026-03-28 15:40:30 -05:00
87d2d9123e Re-implement RGBA textures 2026-03-28 15:21:33 -05:00
6823a4ddb5 Try again again 2026-03-28 11:35:11 -05:00
20a7c70081 Fixiing weird action path missing? 2026-03-28 11:26:25 -05:00
9caa33b3bb Restore all builds 2026-03-28 11:14:15 -05:00
2d7e61460a fix 2026-03-28 11:05:36 -05:00
a4b7fb3f44 Try again 2026-03-28 11:04:42 -05:00
70056cf4ca Temp only build knulli
Some checks failed
Build Dusk / build-knulli (push) Failing after 21s
2026-03-28 11:02:43 -05:00
5f4ab71ade Add knulli build 2026-03-28 11:02:34 -05:00
f3adb3257b Cleanup knulli 2026-03-28 11:00:18 -05:00
438edda7fd Fixed knulli 2026-03-28 10:56:40 -05:00
d5b0441e6f Fixed GLES support (partially), PSP still not working 2026-03-28 10:51:50 -05:00
9ba0ceb000 Moved texture setting around 2026-03-28 09:48:24 -05:00
9474a68995 Slightly more accurate, likely going to have to change how paletted textures work 2026-03-27 21:01:29 -05:00
09c35f0aa6 Builds on knulli 2026-03-27 20:48:43 -05:00
a2113442cb Builds on knulli 2026-03-27 15:59:26 -05:00
d91808487f Allow texture to be NULL.
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
Build Dusk / build-linux (push) Failing after 20s
Build Dusk / build-psp (push) Failing after 16s
Build Dusk / build-gamecube (push) Failing after 18s
Build Dusk / build-wii (push) Failing after 19s
2026-03-27 13:46:18 -05:00
933949cc19 Progress on PSP paletted textures
Some checks failed
Build Dusk / run-tests (push) Failing after 19s
Build Dusk / build-linux (push) Failing after 17s
Build Dusk / build-psp (push) Failing after 21s
Build Dusk / build-gamecube (push) Failing after 19s
Build Dusk / build-wii (push) Failing after 15s
2026-03-27 08:04:34 -05:00
407620387d Test paletted stuff
Some checks failed
Build Dusk / run-tests (push) Failing after 26s
Build Dusk / build-linux (push) Failing after 25s
Build Dusk / build-psp (push) Failing after 18s
Build Dusk / build-gamecube (push) Failing after 18s
Build Dusk / build-wii (push) Failing after 18s
2026-03-26 14:48:20 -05:00
98947dea26 starting textures
Some checks failed
Build Dusk / run-tests (push) Failing after 16s
Build Dusk / build-linux (push) Failing after 17s
Build Dusk / build-psp (push) Failing after 18s
Build Dusk / build-gamecube (push) Failing after 17s
Build Dusk / build-wii (push) Failing after 17s
2026-03-23 19:42:24 -05:00
ebff7af9b5 fix
Some checks failed
Build Dusk / run-tests (push) Failing after 17s
Build Dusk / build-linux (push) Failing after 20s
Build Dusk / build-psp (push) Failing after 20s
Build Dusk / build-gamecube (push) Failing after 19s
Build Dusk / build-wii (push) Failing after 17s
2026-03-23 15:37:53 -05:00
b23c4b83ae played around with color, will likely stick to textures.
Some checks failed
Build Dusk / run-tests (push) Failing after 13s
Build Dusk / build-linux (push) Failing after 15s
Build Dusk / build-psp (push) Failing after 14s
Build Dusk / build-gamecube (push) Failing after 13s
Build Dusk / build-wii (push) Failing after 14s
2026-03-22 23:53:23 -05:00
c0cff40628 Merge pull request 'shader' (#2) from shader into main
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
Build Dusk / build-linux (push) Failing after 14s
Build Dusk / build-psp (push) Failing after 14s
Build Dusk / build-gamecube (push) Failing after 13s
Build Dusk / build-wii (push) Failing after 13s
Reviewed-on: #2
2026-03-23 04:33:20 +00:00
97513e354c Dolphin shaders
Some checks failed
Build Dusk / run-tests (pull_request) Failing after 13s
Build Dusk / build-linux (pull_request) Failing after 18s
Build Dusk / build-psp (pull_request) Failing after 18s
Build Dusk / build-gamecube (pull_request) Failing after 16s
Build Dusk / build-wii (pull_request) Failing after 13s
2026-03-22 23:32:43 -05:00
c277ae7aff DOlphin shader prog 2026-03-22 18:14:56 -05:00
e1835e6282 Merge pull request 'Pull shader code into main' (#1) from shader into main
Some checks failed
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
Build Dusk / build-gamecube (push) Has been cancelled
Build Dusk / build-wii (push) Has been cancelled
Reviewed-on: #1
2026-03-22 15:46:37 +00:00
5ac21db997 Shaders adapted for Legacy GL
Some checks failed
Build Dusk / run-tests (pull_request) Failing after 24s
Build Dusk / build-linux (pull_request) Failing after 18s
Build Dusk / build-psp (pull_request) Failing after 18s
Build Dusk / build-gamecube (pull_request) Failing after 15s
Build Dusk / build-wii (pull_request) Failing after 16s
2026-03-22 10:44:28 -05:00
ca0e9fc3b2 Implement spritebatch properly. 2026-03-22 09:13:42 -05:00
66ebcb1608 shader prog 2026-03-17 17:05:39 -05:00
ff92a78dda Shader first pass 2026-03-17 08:42:43 -05:00
7356286fe0 Adjust how deadzones work
Some checks failed
Build Dusk / run-tests (push) Failing after 15s
Build Dusk / build-linux (push) Failing after 21s
Build Dusk / build-psp (push) Failing after 17s
Build Dusk / build-gamecube (push) Failing after 17s
Build Dusk / build-wii (push) Failing after 17s
2026-03-11 13:00:11 -05:00
54e8e68f86 Update build to use checkout v6
Some checks failed
Build Dusk / run-tests (push) Failing after 18s
Build Dusk / build-linux (push) Failing after 17s
Build Dusk / build-psp (push) Failing after 18s
Build Dusk / build-gamecube (push) Failing after 13s
Build Dusk / build-wii (push) Failing after 16s
2026-03-11 10:42:54 -05:00
d21cd7f78b Update error and debug logging methods
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
Build Dusk / build-linux (push) Failing after 15s
Build Dusk / build-psp (push) Failing after 22s
Build Dusk / build-gamecube (push) Failing after 18s
Build Dusk / build-wii (push) Failing after 15s
2026-03-11 10:33:43 -05:00
1d7516982a Fixed dolphin input
Some checks failed
Build Dusk / run-tests (push) Failing after 15s
Build Dusk / build-linux (push) Failing after 15s
Build Dusk / build-psp (push) Failing after 15s
Build Dusk / build-gamecube (push) Failing after 17s
Build Dusk / build-wii (push) Failing after 14s
2026-03-11 08:11:49 -05:00
c77a11442c Fix input on linux 2026-03-11 07:56:03 -05:00
5bd43a4643 Fix Dolphin crash 2026-03-11 07:27:06 -05:00
9b87dfa1a9 Only exec action on main 2026-03-10 21:59:15 -05:00
2e3173ea40 Enable all jobs.
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
Build Dusk / build-linux (push) Failing after 16s
Build Dusk / build-psp (push) Failing after 15s
Build Dusk / build-gamecube (push) Failing after 16s
Build Dusk / build-wii (push) Failing after 14s
2026-03-10 21:53:13 -05:00
68eac7cf83 Build wii
Some checks failed
Build Dusk / build-wii (push) Failing after 14s
2026-03-10 21:48:12 -05:00
6709505630 Build gamecube
Some checks failed
Build Dusk / build-gamecube (push) Failing after 16s
2026-03-10 21:43:59 -05:00
af6e962a5d Try rename
Some checks failed
Build Dusk / build-psp (push) Failing after 13s
2026-03-10 21:39:00 -05:00
18e6bdabaa test 2
Some checks failed
Build Dusk / build-psp (push) Failing after 14s
2026-03-10 21:35:50 -05:00
9743942eae Try zip PSP
Some checks failed
Build Dusk / build-psp (push) Failing after 17s
2026-03-10 21:33:27 -05:00
23062137a8 Disable tests for now.
Some checks failed
Build Dusk / build-linux (push) Failing after 14s
2026-03-10 21:23:08 -05:00
46f7fb5ccd Use v6
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
Build Dusk / build-linux (push) Failing after 28s
2026-03-10 21:21:37 -05:00
9c90c49a6b Test build linux
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
Build Dusk / build-linux (push) Failing after 28s
2026-03-10 21:19:55 -05:00
4517b63557 Fixed compiling
Some checks failed
Build Dusk / run-tests (push) Failing after 15s
2026-03-10 21:11:12 -05:00
58c239f4b4 Fixing tests more.
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
2026-03-10 20:53:28 -05:00
cc8845ba3e Run linux tests
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
2026-03-10 20:51:58 -05:00
6b69ce2901 Try github
Some checks failed
Build Dusk / run-tests (push) Failing after 36s
2026-03-10 20:46:29 -05:00
55300ed21c test2
Some checks failed
Build Dusk / run-tests (push) Successful in 30s
Build Dusk / build-linux (push) Failing after 6s
2026-03-10 16:48:14 -05:00
7346dd4339 Test
Some checks failed
Build Dusk / run-tests (push) Successful in 25s
Build Dusk / build-linux (push) Failing after 7s
2026-03-10 16:45:24 -05:00
2caf3b92ce Try github workspace
Some checks failed
Build Dusk / run-tests (push) Successful in 6s
Build Dusk / build-linux (push) Failing after 7s
2026-03-10 16:30:46 -05:00
af2cd72a1f Try mount rather than volume
Some checks failed
Build Dusk / run-tests (push) Successful in 4s
Build Dusk / build-linux (push) Failing after 4s
2026-03-10 16:30:08 -05:00
3d455ec1f8 Remove volume
Some checks failed
Build Dusk / run-tests (push) Failing after 3s
Build Dusk / build-linux (push) Failing after 3s
2026-03-10 16:26:29 -05:00
15982d7735 Try realpath over pwd
Some checks failed
Build Dusk / run-tests (push) Failing after 4s
Build Dusk / build-linux (push) Failing after 6s
2026-03-10 16:24:41 -05:00
5ae3542bd9 where am I?
Some checks failed
Build Dusk / run-tests (push) Failing after 4s
Build Dusk / build-linux (push) Failing after 5s
2026-03-10 16:23:32 -05:00
b1b02ae24b Test lsla
Some checks failed
Build Dusk / run-tests (push) Successful in 4s
Build Dusk / build-linux (push) Failing after 4s
2026-03-10 16:21:31 -05:00
f0964e2c92 Test runner
Some checks failed
Build Dusk / run-tests (push) Failing after 2m18s
Build Dusk / build-linux (push) Failing after 6s
2026-03-10 16:16:53 -05:00
e9661d2998 ADd check
Some checks failed
Build Dusk / run-tests (push) Failing after 2m21s
2026-03-10 16:01:44 -05:00
ea6468f2a9 Use git runner temp
Some checks failed
Build Dusk / run-tests (push) Failing after 29s
2026-03-10 15:36:28 -05:00
a2b38d3b83 Test Docker user
Some checks failed
Build Dusk / run-tests (push) Failing after 25s
2026-03-10 15:34:53 -05:00
d67ef02941 Use script system
All checks were successful
Build Dusk / run-tests (push) Successful in 1m15s
Build Dusk / build-linux (push) Successful in 1m6s
2026-03-10 15:15:31 -05:00
549ebe25d8 Let's get it building on linux in gitea
All checks were successful
Build Dusk / run-tests (push) Successful in 2m16s
Build Dusk / build-linux (push) Successful in 1m23s
2026-03-10 15:10:18 -05:00
9a98348582 Renders on Dolphin also. 2026-03-10 15:07:50 -05:00
c5f5b025a6 Game no longer crashes on Dolphin 2026-03-09 08:05:26 -05:00
23eaffa3a7 Fix some dolphin stuff. 2026-03-08 19:55:48 -05:00
c161809248 Renders on PSP identically. 2026-03-08 19:51:00 -05:00
4bf26dc818 Let's get this rendering on PSP and Dolphin. 2026-03-08 15:46:38 -05:00
5dd22fad6c Fixed some bugs. 2026-03-08 13:55:11 -05:00
2c3fdf7803 Add compile time endianess 2026-03-08 13:44:52 -05:00
e984b9f5d7 Asset compartmentalized 2026-03-08 13:29:40 -05:00
a3c2e37b17 Fixed errors 2026-03-08 12:01:22 -05:00
edf1b5a0a3 Technically working 2026-03-08 11:35:21 -05:00
8efdf59ebd More fixes 2026-03-08 10:20:55 -05:00
5c4537b2fa input prog 2026-03-07 22:11:11 -06:00
71e6079054 More code moving 2026-03-07 12:09:40 -06:00
dd048d9b0d Moved a bunch of code around 2026-03-07 09:35:56 -06:00
93074d653e idk 2026-03-06 16:34:45 -06:00
9139c4350a Moved all files. 2026-03-06 14:01:21 -06:00
38ce768168 kms 2026-03-06 13:40:27 -06:00
82b3dc576c remove un-needed files 2026-03-03 12:29:04 -06:00
2167889f48 Merge branch 'main' into break-literally-everything 2026-03-03 12:28:48 -06:00
e9b02c2acf Whatewver
Some checks failed
Build Dusk / run-tests (push) Successful in 1m26s
Build Dusk / build-linux (push) Successful in 1m26s
Build Dusk / build-psp (push) Failing after 1m28s
Build Dusk / build-dolphin (push) Failing after 1m28s
2026-03-02 20:14:21 -06:00
9ee446431b Moved build stuff to docker 2026-03-02 06:59:51 -06:00
df106e3988 "progress" 2026-02-28 09:55:21 -06:00
d0a057e0ee Moved all defs into main file. 2026-02-17 11:33:00 -06:00
8b49902bf6 Moved some files around 2026-02-17 10:59:21 -06:00
71c1e56564 Fix endian again
Some checks failed
Build Dusk / run-tests (push) Successful in 1m35s
Build Dusk / build-linux (push) Successful in 1m42s
Build Dusk / build-psp (push) Failing after 1m44s
Build Dusk / build-dolphin (push) Failing after 2m18s
2026-02-16 19:15:29 -06:00
1b12e67de2 Use internal endian tool
Some checks failed
Build Dusk / run-tests (push) Successful in 1m27s
Build Dusk / build-linux (push) Successful in 1m21s
Build Dusk / build-psp (push) Failing after 1m31s
Build Dusk / build-dolphin (push) Failing after 1m49s
2026-02-16 13:34:20 -06:00
291bb4bb81 Build?
Some checks failed
Build Dusk / run-tests (push) Successful in 1m20s
Build Dusk / build-linux (push) Successful in 1m30s
Build Dusk / build-psp (push) Failing after 1m35s
Build Dusk / build-dolphin (push) Failing after 2m6s
2026-02-16 13:21:04 -06:00
342ddb19f8 Finally got text rendering again. 2026-02-16 13:12:12 -06:00
9c9d2d548e Fixed whatever problem was with texture loading. 2026-02-16 12:29:25 -06:00
d7a0bb4509 Fix palette indexer bytes 2026-02-16 12:01:06 -06:00
2b1a3323a8 Tileset creator done 2026-02-16 12:00:55 -06:00
99d030003c DEbug not working so moving pcs 2026-02-15 16:41:33 -06:00
92a753560b prog 2026-02-15 01:09:28 -06:00
af9904c892 Scripts work again.
All checks were successful
Build Dusk / run-tests (push) Successful in 1m40s
Build Dusk / build-linux (push) Successful in 1m45s
Build Dusk / build-psp (push) Successful in 1m47s
Build Dusk / build-dolphin (push) Successful in 2m23s
2026-02-13 19:36:59 -06:00
e5e8c49f6c Mostly nuking old system
Some checks failed
Build Dusk / build-linux (push) Failing after 1m24s
Build Dusk / run-tests (push) Failing after 1m17s
Build Dusk / build-psp (push) Failing after 1m34s
Build Dusk / build-dolphin (push) Failing after 2m5s
2026-02-13 19:13:26 -06:00
b37e5f45ca Sweeper
All checks were successful
Build Dusk / run-tests (push) Successful in 1m28s
Build Dusk / build-linux (push) Successful in 1m33s
Build Dusk / build-psp (push) Successful in 1m53s
Build Dusk / build-dolphin (push) Successful in 2m19s
2026-02-10 21:06:09 -06:00
e1f08b07aa Need a break from Dolphin
All checks were successful
Build Dusk / run-tests (push) Successful in 1m36s
Build Dusk / build-linux (push) Successful in 1m14s
Build Dusk / build-psp (push) Successful in 2m2s
Build Dusk / build-dolphin (push) Successful in 2m49s
2026-02-09 22:18:44 -06:00
073ee8dca9 Trying to find dolphin texture bug
All checks were successful
Build Dusk / run-tests (push) Successful in 1m37s
Build Dusk / build-linux (push) Successful in 1m23s
Build Dusk / build-psp (push) Successful in 1m45s
Build Dusk / build-dolphin (push) Successful in 2m20s
2026-02-09 14:53:27 -06:00
a26e51cf46 Use local mirror for cglm
All checks were successful
Build Dusk / run-tests (push) Successful in 1m26s
Build Dusk / build-linux (push) Successful in 1m33s
Build Dusk / build-psp (push) Successful in 2m8s
Build Dusk / build-dolphin (push) Successful in 2m1s
2026-02-09 13:19:13 -06:00
dfed732825 Try different branch for upload binary action
All checks were successful
Build Dusk / run-tests (push) Successful in 1m46s
Build Dusk / build-linux (push) Successful in 2m10s
Build Dusk / build-psp (push) Successful in 2m42s
Build Dusk / build-dolphin (push) Successful in 3m23s
2026-02-09 12:13:48 -06:00
87aa70c6d2 Push userdata?
Some checks failed
Build Dusk / run-tests (push) Successful in 1m43s
Build Dusk / build-linux (push) Failing after 1m40s
Build Dusk / build-psp (push) Failing after 2m21s
Build Dusk / build-dolphin (push) Failing after 2m42s
2026-02-09 12:03:35 -06:00
aa2979ffe7 Fix main
Some checks failed
Build Dusk / run-tests (push) Failing after 1m15s
Build Dusk / build-linux (push) Failing after 1m34s
Build Dusk / build-psp (push) Failing after 1m45s
Build Dusk / build-dolphin (push) Failing after 2m7s
2026-02-09 11:54:53 -06:00
236e16aa6d Fix url format
Some checks failed
Build Dusk / run-tests (push) Failing after 1s
Build Dusk / build-linux (push) Failing after 1s
Build Dusk / build-psp (push) Failing after 1s
Build Dusk / build-dolphin (push) Failing after 1s
2026-02-09 11:51:04 -06:00
184bb970e6 Update to not rely on third party actions
Some checks failed
Build Dusk / run-tests (push) Failing after 2s
Build Dusk / build-linux (push) Failing after 2s
Build Dusk / build-psp (push) Failing after 1s
Build Dusk / build-dolphin (push) Failing after 2s
2026-02-09 10:59:34 -06:00
bd54469891 test some stuff
Some checks failed
Build Dusk / run-tests (push) Failing after 2m48s
Build Dusk / build-linux (push) Failing after 2m21s
Build Dusk / build-psp (push) Failing after 1m46s
Build Dusk / build-dolphin (push) Failing after 1m57s
2026-02-09 10:39:46 -06:00
2f5dccc3ef Texture loading
All checks were successful
Build Dusk / run-tests (push) Successful in 1m44s
Build Dusk / build-linux (push) Successful in 1m38s
Build Dusk / build-psp (push) Successful in 1m59s
Build Dusk / build-dolphin (push) Successful in 1m59s
2026-02-09 09:29:16 -06:00
592edb90a0 Test lua rgb rainbow
All checks were successful
Build Dusk / run-tests (push) Successful in 1m15s
Build Dusk / build-linux (push) Successful in 1m23s
Build Dusk / build-psp (push) Successful in 1m26s
Build Dusk / build-dolphin (push) Successful in 2m6s
2026-02-08 22:30:53 -06:00
3db7e6b1b9 Fixed palette lookup
All checks were successful
Build Dusk / run-tests (push) Successful in 1m18s
Build Dusk / build-linux (push) Successful in 1m21s
Build Dusk / build-psp (push) Successful in 1m30s
Build Dusk / build-dolphin (push) Successful in 1m51s
2026-02-08 21:26:42 -06:00
13c4df0d85 Compiles on dolphin, finally
All checks were successful
Build Dusk / run-tests (push) Successful in 1m39s
Build Dusk / build-linux (push) Successful in 1m34s
Build Dusk / build-psp (push) Successful in 2m7s
Build Dusk / build-dolphin (push) Successful in 1m48s
2026-02-08 19:30:02 -06:00
ef25fb09da Fix linux building
All checks were successful
Build Dusk / run-tests (push) Successful in 2m20s
Build Dusk / build-linux (push) Successful in 2m5s
Build Dusk / build-psp (push) Successful in 2m19s
Build Dusk / build-dolphin (push) Successful in 3m39s
2026-02-08 10:14:52 -06:00
03cf4a9efe See if dolphin will render the floating text demo
Some checks failed
Build Dusk / run-tests (push) Successful in 1m6s
Build Dusk / build-linux (push) Failing after 1m12s
Build Dusk / build-psp (push) Successful in 2m0s
Build Dusk / build-dolphin (push) Successful in 2m41s
2026-02-08 10:09:45 -06:00
53dd36efdd Fixed alpha textures properly on PSP
Some checks failed
Build Dusk / run-tests (push) Successful in 1m25s
Build Dusk / build-linux (push) Failing after 1m13s
Build Dusk / build-psp (push) Successful in 2m2s
Build Dusk / build-dolphin (push) Successful in 2m22s
2026-02-08 09:54:40 -06:00
ad9e841a42 Removed CXX target
All checks were successful
Build Dusk / run-tests (push) Successful in 1m46s
Build Dusk / build-linux (push) Successful in 1m36s
Build Dusk / build-psp (push) Successful in 2m56s
Build Dusk / build-dolphin (push) Successful in 5m38s
2026-02-07 17:08:03 -06:00
14f3f464c7 Prog on fixing psp alpha textures
All checks were successful
Build Dusk / run-tests (push) Successful in 1m20s
Build Dusk / build-linux (push) Successful in 1m39s
Build Dusk / build-psp (push) Successful in 1m51s
Build Dusk / build-dolphin (push) Successful in 2m36s
2026-02-07 15:29:29 -06:00
cbe51cc8d0 Speedup build
All checks were successful
Build Dusk / run-tests (push) Successful in 1m10s
Build Dusk / build-linux (push) Successful in 1m4s
Build Dusk / build-psp (push) Successful in 1m36s
Build Dusk / build-dolphin (push) Successful in 2m6s
2026-02-06 16:50:50 -06:00
efaa3f6eea Fix building on PSP
All checks were successful
Build Dusk / run-tests (push) Successful in 1m26s
Build Dusk / build-linux (push) Successful in 1m31s
Build Dusk / build-psp (push) Successful in 1m44s
Build Dusk / build-dolphin (push) Successful in 2m36s
2026-02-06 16:29:12 -06:00
52cce9a3b0 ADd meta.xml
Some checks failed
Build Dusk / build-dolphin (push) Successful in 3m6s
Build Dusk / run-tests (push) Successful in 1m38s
Build Dusk / build-linux (push) Failing after 2m15s
Build Dusk / build-psp (push) Failing after 2m23s
2026-02-06 16:14:54 -06:00
b7b390311e Forgor wii stuff
Some checks failed
Build Dusk / build-dolphin (push) Failing after 3m13s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 16:09:31 -06:00
c1eeddd14b Fix dir spelling
Some checks failed
Build Dusk / build-dolphin (push) Failing after 2m22s
Build Dusk / run-tests (push) Successful in 2m4s
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / build-linux (push) Has been cancelled
2026-02-06 16:03:47 -06:00
1a7a55dfc3 Fix missing mkdirp
Some checks failed
Build Dusk / build-dolphin (push) Failing after 4m14s
Build Dusk / run-tests (push) Successful in 3m24s
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / build-linux (push) Has been cancelled
2026-02-06 15:54:06 -06:00
fe5927ea6a Add more bin
Some checks failed
Build Dusk / build-dolphin (push) Failing after 3m41s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 15:49:52 -06:00
119c794ad7 Add bin
Some checks failed
Build Dusk / build-dolphin (push) Failing after 4m13s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 15:44:11 -06:00
e2076b2c1c Also build wii
Some checks failed
Build Dusk / build-dolphin (push) Failing after 3m55s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 15:39:33 -06:00
5208c5148e Fix cmake?
Some checks failed
Build Dusk / build-dolphin (push) Failing after 5m18s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 15:32:00 -06:00
d80660b097 cur dir
Some checks failed
Build Dusk / build-dolphin (push) Failing after 3m30s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 15:27:33 -06:00
b916d0278b install cmake
Some checks failed
Build Dusk / build-dolphin (push) Failing after 3m5s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has started running
2026-02-06 15:21:24 -06:00
d51e13e620 Update
Some checks failed
Build Dusk / build-gamecube (push) Failing after 1m36s
Build Dusk / run-tests (push) Successful in 4m9s
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / build-linux (push) Has started running
2026-02-06 15:13:03 -06:00
40ad4326ef Do it all ourselves
Some checks failed
Build Dusk / build-gamecube (push) Failing after 12s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 15:11:50 -06:00
b8afc1684a with --yes
Some checks failed
Build Dusk / build-gamecube (push) Failing after 41s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 15:10:47 -06:00
8348b31ac8 re
Some checks failed
Build Dusk / build-gamecube (push) Failing after 20s
Build Dusk / run-tests (push) Has started running
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
2026-02-06 15:08:58 -06:00
411f2dbcce 403 fix?
Some checks failed
Build Dusk / run-tests (push) Waiting to run
Build Dusk / build-gamecube (push) Failing after 18s
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / build-linux (push) Has been cancelled
2026-02-06 15:02:41 -06:00
ee89c08160 Manually setup ppc
Some checks failed
Build Dusk / build-gamecube (push) Failing after 6s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has started running
2026-02-06 15:00:28 -06:00
357607a89f more echoing
Some checks failed
Build Dusk / build-gamecube (push) Failing after 1m13s
Build Dusk / run-tests (push) Successful in 3m11s
Build Dusk / build-linux (push) Failing after 2m36s
Build Dusk / build-psp (push) Has been cancelled
2026-02-06 14:52:45 -06:00
8d6dc2df44 Try sudo
Some checks failed
Build Dusk / build-gamecube (push) Failing after 1m16s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 14:50:49 -06:00
5207582ab3 Try more
Some checks failed
Build Dusk / build-gamecube (push) Failing after 1m15s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 14:47:44 -06:00
71768e6154 Try outside
Some checks failed
Build Dusk / build-gamecube (push) Failing after 1m33s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 14:45:07 -06:00
ecbe235523 Try try again
Some checks failed
Build Dusk / build-gamecube (push) Failing after 53s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 14:43:10 -06:00
afef079d1e Fix cli order
Some checks failed
Build Dusk / build-gamecube (push) Failing after 1m0s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 14:40:07 -06:00
df17696c69 try append u
Some checks failed
Build Dusk / build-gamecube (push) Failing after 52s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 14:38:49 -06:00
065bf0908f Fix crap
Some checks failed
Build Dusk / build-gamecube (push) Failing after 42s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 14:34:53 -06:00
5b6755e9cf Try fix workdir
Some checks failed
Build Dusk / build-gamecube (push) Failing after 1m38s
Build Dusk / run-tests (push) Successful in 4m0s
Build Dusk / build-linux (push) Failing after 2m53s
Build Dusk / build-psp (push) Failing after 3m30s
2026-02-06 14:19:59 -06:00
b08482acf1 Build cube again
Some checks failed
Build Dusk / build-gamecube (push) Failing after 1m11s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 14:15:29 -06:00
80c9c1d389 Try mount differently.
Some checks failed
Build Dusk / build-gamecube (push) Failing after 1m12s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 14:12:05 -06:00
bb7db57bda yes
Some checks failed
Build Dusk / build-gamecube (push) Failing after 50s
Build Dusk / run-tests (push) Successful in 2m5s
Build Dusk / build-linux (push) Failing after 2m17s
Build Dusk / build-psp (push) Has been cancelled
2026-02-06 14:05:28 -06:00
6a83ac767c cli 2026-02-06 14:05:18 -06:00
7e47ef9d74 try official docker steps
Some checks failed
Build Dusk / build-gamecube (push) Failing after 38s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 14:03:34 -06:00
e7ec603526 yet another
Some checks failed
Build Dusk / build-gamecube (push) Failing after 44s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 14:00:49 -06:00
2d8ae09bd8 Test again?
Some checks failed
Build Dusk / build-gamecube (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
2026-02-06 13:58:48 -06:00
b2affbc0a7 Try something else?
Some checks failed
Build Dusk / build-gamecube (push) Failing after 39s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 13:56:39 -06:00
d50bc61ada Prog
Some checks failed
Build Dusk / build-linux (push) Waiting to run
Build Dusk / build-psp (push) Waiting to run
Build Dusk / build-gamecube (push) Failing after 29s
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 13:54:53 -06:00
ec6b032b45 Take sH out
Some checks failed
Build Dusk / build-gamecube (push) Failing after 56s
Build Dusk / build-wii (push) Failing after 44s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 13:51:07 -06:00
bc72f48496 Try again?
Some checks failed
Build Dusk / build-gamecube (push) Failing after 44s
Build Dusk / run-tests (push) Has been cancelled
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / build-wii (push) Has been cancelled
2026-02-06 13:48:11 -06:00
dcf06fbd36 Update shell
Some checks failed
Build Dusk / build-gamecube (push) Failing after 1m3s
Build Dusk / run-tests (push) Has been cancelled
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / build-wii (push) Has been cancelled
2026-02-06 13:46:15 -06:00
96311d72c2 Retry build
Some checks failed
Build Dusk / build-gamecube (push) Failing after 1m26s
Build Dusk / build-wii (push) Failing after 1m19s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 13:41:27 -06:00
07938cccc7 Build dolphin first while I test.
Some checks failed
Build Dusk / build-dolphin (push) Failing after 5m38s
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-06 13:34:56 -06:00
097c8c00f9 Fixed flickering
Some checks failed
Build Dusk / run-tests (push) Successful in 1m42s
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-dolphin (push) Has been cancelled
2026-02-06 13:31:15 -06:00
aa5b41fe31 First texture rendering (if broken) 2026-02-06 12:48:49 -06:00
0d56859d94 Alpha textures 2026-02-05 23:28:26 -06:00
1af5f238e4 RGBA textures 2026-02-05 21:57:56 -06:00
dd697d5650 Emu vs Real! 2026-02-05 08:43:49 -06:00
5cf299a1c7 Mesh working, lua broken 2026-02-05 08:21:19 -06:00
67bf825cc9 Camera 2026-02-05 00:42:04 -06:00
56e1696cd4 Asset loading 2026-02-04 21:52:14 -06:00
d955fb6430 Debugging functions. 2026-02-04 18:32:20 -06:00
dd910a31aa Actually compiled 2026-02-04 17:44:53 -06:00
708c4d0ec3 "Improved" 2026-02-04 15:54:09 -06:00
ad13d6c6a1 SDL2 example builds. 2026-02-04 11:17:25 -06:00
1c32158142 DOlphin progress 2026-02-04 10:16:16 -06:00
5cea284906 Map loading
All checks were successful
Build Dusk / run-tests (push) Successful in 1m15s
Build Dusk / build-linux (push) Successful in 1m33s
Build Dusk / build-psp (push) Successful in 1m56s
2026-02-03 19:20:50 -06:00
13dba8b604 Restore some map stuff
All checks were successful
Build Dusk / run-tests (push) Successful in 1m44s
Build Dusk / build-linux (push) Successful in 1m35s
Build Dusk / build-psp (push) Successful in 1m53s
2026-02-03 15:40:56 -06:00
22398ddcef Added story flags
All checks were successful
Build Dusk / run-tests (push) Successful in 1m27s
Build Dusk / build-linux (push) Successful in 1m13s
Build Dusk / build-psp (push) Successful in 1m46s
2026-02-03 15:16:21 -06:00
94e2cc6210 Screen background
All checks were successful
Build Dusk / run-tests (push) Successful in 1m33s
Build Dusk / build-linux (push) Successful in 1m40s
Build Dusk / build-psp (push) Successful in 1m50s
2026-02-03 13:37:00 -06:00
da3513f63d Fix 1
All checks were successful
Build Dusk / run-tests (push) Successful in 1m28s
Build Dusk / build-linux (push) Successful in 1m50s
Build Dusk / build-psp (push) Successful in 2m37s
2026-02-03 13:18:40 -06:00
2c83e4ba9f mkdirs
Some checks failed
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-02-03 13:18:32 -06:00
c862071126 Text
Some checks failed
Build Dusk / run-tests (push) Failing after 1m47s
Build Dusk / build-linux (push) Failing after 1m47s
Build Dusk / build-psp (push) Failing after 1m53s
2026-02-03 10:22:39 -06:00
fed819e9b2 renders
Some checks failed
Build Dusk / run-tests (push) Failing after 1m10s
Build Dusk / build-linux (push) Failing after 1m22s
Build Dusk / build-psp (push) Failing after 1m31s
2026-02-01 22:04:07 -06:00
c6f4518684 More lua stuff, yay.
Some checks failed
Build Dusk / run-tests (push) Failing after 1m18s
Build Dusk / build-linux (push) Failing after 1m43s
Build Dusk / build-psp (push) Failing after 1m46s
2026-02-01 21:54:33 -06:00
053778a502 Refactored and simplified lua stuff a lot.
Some checks failed
Build Dusk / run-tests (push) Failing after 1m23s
Build Dusk / build-linux (push) Failing after 1m47s
Build Dusk / build-psp (push) Failing after 1m41s
2026-02-01 21:28:21 -06:00
78e1ae885a Add color support.
Some checks failed
Build Dusk / run-tests (push) Failing after 1m35s
Build Dusk / build-linux (push) Failing after 1m34s
Build Dusk / build-psp (push) Failing after 1m53s
2026-02-01 11:16:52 -06:00
982d28a3e0 prog
All checks were successful
Build Dusk / run-tests (push) Successful in 1m39s
Build Dusk / build-linux (push) Successful in 1m36s
Build Dusk / build-psp (push) Successful in 1m52s
2026-01-31 21:20:33 -06:00
c2cad858a5 Allow strings to be returned from structs
All checks were successful
Build Dusk / run-tests (push) Successful in 1m33s
Build Dusk / build-linux (push) Successful in 1m53s
Build Dusk / build-psp (push) Successful in 1m51s
2026-01-28 18:34:23 -06:00
794e0574ad Moved a few things around, definitely not clean but better.
Some checks failed
Build Dusk / run-tests (push) Failing after 2m3s
Build Dusk / build-linux (push) Successful in 2m13s
Build Dusk / build-psp (push) Successful in 1m56s
2026-01-28 15:00:59 -06:00
c190271565 Builds on PSP properly.
Some checks failed
Build Dusk / run-tests (push) Failing after 2m16s
Build Dusk / build-linux (push) Successful in 2m0s
Build Dusk / build-psp (push) Successful in 1m53s
2026-01-28 12:28:27 -06:00
ae8a869f64 Sort fix
All checks were successful
Build Dusk / run-tests (push) Successful in 3m44s
Build Dusk / build-linux (push) Successful in 4m34s
Build Dusk / build-psp (push) Successful in 2m43s
2026-01-28 11:44:25 -06:00
69d64eb8e4 Update PSP toolchain
Some checks failed
Build Dusk / run-tests (push) Failing after 3m35s
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / build-linux (push) Has been cancelled
2026-01-28 11:40:07 -06:00
b2f2df650a restored build
Some checks failed
Build Dusk / run-tests (push) Failing after 1m43s
Build Dusk / build-linux (push) Successful in 1m50s
Build Dusk / build-psp (push) Has been cancelled
2026-01-28 11:34:42 -06:00
6af570fab2 See why PSP fails on ubuntu vm 2026-01-28 11:33:12 -06:00
9ed902017c Cleanup scene.
Some checks failed
Build Dusk / run-tests (push) Failing after 2m9s
Build Dusk / build-linux (push) Successful in 1m50s
Build Dusk / build-psp (push) Failing after 1m49s
2026-01-28 11:02:53 -06:00
32b41c98e1 Rendering a moving square entirely from lua.
Some checks failed
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-01-28 11:01:07 -06:00
6bdb4ae30d Scene methods
Some checks failed
Build Dusk / run-tests (push) Failing after 2m14s
Build Dusk / build-linux (push) Successful in 2m12s
Build Dusk / build-psp (push) Failing after 1m30s
2026-01-28 10:31:31 -06:00
25dc97e3cc Update some script stuff.
Some checks failed
Build Dusk / run-tests (push) Failing after 2m12s
Build Dusk / build-linux (push) Successful in 2m3s
Build Dusk / build-psp (push) Failing after 1m28s
2026-01-27 23:48:46 -06:00
cc85983736 Add struct metafield
Some checks failed
Build Dusk / run-tests (push) Failing after 1m55s
Build Dusk / build-linux (push) Successful in 1m58s
Build Dusk / build-psp (push) Failing after 1m26s
2026-01-27 21:16:53 -06:00
6e78ee188d Event in lua (partial)
Some checks failed
Build Dusk / run-tests (push) Failing after 2m30s
Build Dusk / build-linux (push) Failing after 2m37s
Build Dusk / build-psp (push) Failing after 1m54s
2026-01-27 12:47:15 -06:00
9b73f1717f Event
Some checks failed
Build Dusk / run-tests (push) Failing after 1m44s
Build Dusk / build-linux (push) Successful in 1m50s
Build Dusk / build-psp (push) Failing after 1m53s
2026-01-27 10:41:32 -06:00
c7b9a53535 Locale script
Some checks failed
Build Dusk / run-tests (push) Failing after 2m12s
Build Dusk / build-linux (push) Successful in 1m49s
Build Dusk / build-psp (push) Failing after 1m39s
2026-01-27 09:52:08 -06:00
2b9be6675c Locale
Some checks failed
Build Dusk / run-tests (push) Failing after 2m3s
Build Dusk / build-linux (push) Successful in 1m50s
Build Dusk / build-psp (push) Failing after 1m48s
2026-01-27 09:24:16 -06:00
fb93453482 Cleaned some tools up
Some checks failed
Build Dusk / run-tests (push) Failing after 2m13s
Build Dusk / build-linux (push) Successful in 2m4s
Build Dusk / build-psp (push) Failing after 1m47s
2026-01-27 08:40:13 -06:00
81b08b2eba idk
Some checks failed
Build Dusk / run-tests (push) Successful in 1m57s
Build Dusk / build-linux (push) Successful in 2m9s
Build Dusk / build-psp (push) Failing after 1m39s
2026-01-26 22:54:05 -06:00
d1b03c4cb3 prog
Some checks failed
Build Dusk / run-tests (push) Failing after 1m58s
Build Dusk / build-linux (push) Failing after 1m26s
Build Dusk / build-psp (push) Failing after 1m41s
2026-01-26 18:27:12 -06:00
9544d15a18 Dynamically assign script values for items and inputs
Some checks failed
Build Dusk / run-tests (push) Failing after 1m22s
Build Dusk / build-linux (push) Successful in 2m8s
Build Dusk / build-psp (push) Failing after 1m38s
2026-01-26 08:48:17 -06:00
0392dd0e7f Added csv_to_array tool
Some checks failed
Build Dusk / run-tests (push) Failing after 1m41s
Build Dusk / build-linux (push) Failing after 1m26s
Build Dusk / build-psp (push) Failing after 1m46s
2026-01-26 08:37:36 -06:00
9c25fde548 Fixed ints
Some checks failed
Build Dusk / run-tests (push) Successful in 2m10s
Build Dusk / build-linux (push) Successful in 2m22s
Build Dusk / build-psp (push) Failing after 1m39s
2026-01-26 00:19:25 -06:00
2c9d0c6cff Fixed duskdefs
Some checks failed
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-01-26 00:18:43 -06:00
9897dbe031 env_to_h cleaned
Some checks failed
Build Dusk / run-tests (push) Failing after 1m40s
Build Dusk / build-linux (push) Failing after 1m34s
Build Dusk / build-psp (push) Failing after 1m41s
2026-01-25 23:00:11 -06:00
e78f117cfd Switched env to python
Some checks failed
Build Dusk / run-tests (push) Failing after 1m20s
Build Dusk / build-linux (push) Failing after 1m25s
Build Dusk / build-psp (push) Failing after 1m25s
2026-01-25 22:52:12 -06:00
07afc3813a Refactor
Some checks failed
Build Dusk / run-tests (push) Successful in 1m55s
Build Dusk / build-linux (push) Successful in 2m0s
Build Dusk / build-psp (push) Failing after 1m40s
2026-01-25 21:23:09 -06:00
d788de8637 Starting refactor of tools, thank gosh
Some checks failed
Build Dusk / run-tests (push) Successful in 1m56s
Build Dusk / build-linux (push) Successful in 1m44s
Build Dusk / build-psp (push) Failing after 1m35s
2026-01-25 21:07:31 -06:00
d749ac8a91 Add missing dnfs
Some checks failed
Build Dusk / run-tests (push) Failing after 1m28s
Build Dusk / build-linux (push) Failing after 1m24s
Build Dusk / build-psp (push) Failing after 1m53s
2026-01-25 16:07:44 -06:00
f71c271c97 item
Some checks failed
Build Dusk / run-tests (push) Failing after 1m31s
Build Dusk / build-linux (push) Failing after 1m8s
Build Dusk / build-psp (push) Failing after 1m32s
2026-01-25 15:01:25 -06:00
e1d7b7308f Progness
Some checks failed
Build Dusk / run-tests (push) Successful in 2m32s
Build Dusk / build-linux (push) Successful in 2m3s
Build Dusk / build-psp (push) Failing after 1m41s
2026-01-24 10:07:50 -06:00
26a71a823a Test assert
Some checks failed
Build Dusk / run-tests (push) Successful in 2m6s
Build Dusk / build-linux (push) Successful in 1m56s
Build Dusk / build-psp (push) Failing after 1m48s
2026-01-06 21:23:39 -06:00
5e39097faa Improve sorting
Some checks failed
Build Dusk / run-tests (push) Successful in 1m53s
Build Dusk / build-linux (push) Successful in 1m35s
Build Dusk / build-psp (push) Failing after 1m50s
2026-01-06 11:30:26 -06:00
0df7845f2c Added memory checks
Some checks failed
Build Dusk / run-tests (push) Successful in 2m6s
Build Dusk / build-linux (push) Successful in 2m6s
Build Dusk / build-psp (push) Failing after 1m47s
2026-01-06 11:02:26 -06:00
af5bf987c8 Add inventory.
All checks were successful
Build Dusk / run-tests (push) Successful in 2m12s
Build Dusk / build-linux (push) Successful in 1m49s
Build Dusk / build-psp (push) Successful in 1m52s
2026-01-06 07:47:16 -06:00
024ace1078 Fixed PSP building
All checks were successful
Build Dusk / run-tests (push) Successful in 1m18s
Build Dusk / build-linux (push) Successful in 1m53s
Build Dusk / build-psp (push) Successful in 2m8s
2026-01-05 20:13:11 -06:00
8d00fe9d16 Test PSP update
Some checks failed
Build Dusk / run-tests (push) Successful in 1m50s
Build Dusk / build-linux (push) Successful in 1m43s
Build Dusk / build-psp (push) Failing after 1m27s
2026-01-05 20:00:26 -06:00
ab422b14dd Update PSP SDK
Some checks failed
Build Dusk / run-tests (push) Successful in 2m0s
Build Dusk / build-linux (push) Successful in 2m11s
Build Dusk / build-psp (push) Failing after 1m43s
2026-01-05 19:19:16 -06:00
95c0690216 Add extra time checks.
Some checks failed
Build Dusk / run-tests (push) Successful in 1m51s
Build Dusk / build-linux (push) Successful in 1m49s
Build Dusk / build-psp (push) Failing after 1m47s
2026-01-05 18:11:02 -06:00
6cb80e9e23 Testing time (and found some bugs!)
Some checks failed
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
2026-01-05 18:09:31 -06:00
83b799caa8 String.h
Some checks failed
Build Dusk / run-tests (push) Successful in 2m2s
Build Dusk / build-linux (push) Successful in 2m1s
Build Dusk / build-psp (push) Failing after 1m21s
2026-01-05 17:20:17 -06:00
a793ac2ff7 Add runner for testing
Some checks failed
Build Dusk / run-tests (push) Successful in 1m58s
Build Dusk / build-linux (push) Successful in 1m53s
Build Dusk / build-psp (push) Failing after 1m56s
2026-01-05 16:14:12 -06:00
aec937b04b Add some tests
Some checks failed
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / build-linux (push) Has been cancelled
2026-01-05 16:13:14 -06:00
8ee46fd204 add tests.
Some checks failed
Build Dusk / build-linux (push) Successful in 2m5s
Build Dusk / build-psp (push) Failing after 1m44s
2026-01-05 13:25:39 -06:00
726233e55f Map exec
Some checks failed
Build Dusk / build-linux (push) Successful in 1m40s
Build Dusk / build-psp (push) Failing after 1m38s
2025-12-26 20:38:24 +10:00
7940f4c487 Map refactoring to prep for loading
Some checks failed
Build Dusk / build-linux (push) Successful in 1m35s
Build Dusk / build-psp (push) Failing after 1m46s
2025-12-26 15:01:43 +10:00
b16dbaceec Modules
Some checks failed
Build Dusk / build-linux (push) Successful in 1m13s
Build Dusk / build-psp (push) Failing after 1m27s
2025-12-25 08:02:11 +10:00
f39b2060a8 iuno just screwing around tbh
Some checks failed
Build Dusk / build-linux (push) Failing after 1m5s
Build Dusk / build-psp (push) Failing after 1m24s
2025-12-24 10:44:53 +10:00
aed202ebf9 Add include()
Some checks failed
Build Dusk / build-linux (push) Successful in 1m16s
Build Dusk / build-psp (push) Failing after 1m14s
2025-12-24 09:41:05 +10:00
a495179e5f Prog
Some checks failed
Build Dusk / build-linux (push) Successful in 1m19s
Build Dusk / build-psp (push) Failing after 1m36s
2025-12-05 14:41:13 -06:00
4e1b404820 Add script context
Some checks failed
Build Dusk / build-linux (push) Successful in 1m41s
Build Dusk / build-psp (push) Failing after 2m36s
2025-12-04 20:57:12 -06:00
8c74ee31e0 Add lua diff
All checks were successful
Build Dusk / build-linux (push) Successful in 1m51s
Build Dusk / build-psp (push) Successful in 2m2s
2025-12-04 00:39:09 -06:00
77d3c54ebb Fixed PSP build
Some checks failed
Build Dusk / build-linux (push) Failing after 1m53s
Build Dusk / build-psp (push) Failing after 1m27s
2025-12-04 00:33:42 -06:00
b5de39926b Lua 2025-12-04 00:30:44 -06:00
3a8dafbb91 Fix compile issue
Some checks failed
Build Dusk / build-linux (push) Failing after 2m13s
Build Dusk / build-psp (push) Failing after 2m36s
2025-12-04 00:26:49 -06:00
6b22f547fe PyGL
Some checks failed
Build Dusk / build-linux (push) Failing after 2m5s
Build Dusk / build-psp (push) Has been cancelled
2025-12-04 00:23:50 -06:00
de78be3e25 Attempt install pyqt5
Some checks failed
Build Dusk / build-linux (push) Failing after 1m48s
Build Dusk / build-psp (push) Failing after 2m4s
2025-12-04 00:12:04 -06:00
9f507be7bc Lua script something
Some checks failed
Build Dusk / build-linux (push) Failing after 2m56s
Build Dusk / build-psp (push) Failing after 1m49s
2025-12-04 00:02:26 -06:00
9aaf271996 fixing some stuff but nothing really.
Some checks failed
Build Dusk / build-linux (push) Failing after 50s
Build Dusk / build-psp (push) Failing after 1m1s
2025-11-28 10:45:07 -06:00
b01c0d37b0 Region editor
Some checks failed
Build Dusk / build-linux (push) Failing after 51s
Build Dusk / build-psp (push) Failing after 55s
2025-11-28 08:48:42 -06:00
538079880d Abt to cutscene
Some checks failed
Build Dusk / build-linux (push) Failing after 6m26s
Build Dusk / build-psp (push) Failing after 1m1s
2025-11-25 08:45:12 -06:00
fe0529d021 Fixed initial chunk load buggy
Some checks failed
Build Dusk / build-linux (push) Failing after 2m15s
Build Dusk / build-psp (push) Failing after 1m48s
2025-11-25 08:27:43 -06:00
d068f0f2c3 Fixed double clicking to ent
Some checks failed
Build Dusk / build-linux (push) Failing after 1m8s
Build Dusk / build-psp (push) Has been cancelled
2025-11-25 08:23:27 -06:00
f9a64b8d54 Fixed entity positioning
Some checks failed
Build Dusk / build-linux (push) Failing after 57s
Build Dusk / build-psp (push) Has been cancelled
2025-11-25 08:21:56 -06:00
01cbfaae95 Trying to find entity editor bug
Some checks failed
Build Dusk / build-linux (push) Failing after 41s
Build Dusk / build-psp (push) Failing after 1m1s
2025-11-24 13:13:49 -06:00
f9006a90d5 Editor has chunk loading
Some checks failed
Build Dusk / build-linux (push) Failing after 57s
Build Dusk / build-psp (push) Failing after 1m0s
2025-11-23 22:44:31 -06:00
7daeaee6b5 Basically entity editing done
Some checks failed
Build Dusk / build-linux (push) Failing after 43s
Build Dusk / build-psp (push) Failing after 49s
2025-11-22 11:56:08 -06:00
03218ce20f Ent saving and loading
Some checks failed
Build Dusk / build-linux (push) Failing after 49s
Build Dusk / build-psp (push) Failing after 59s
2025-11-22 11:20:07 -06:00
6f33522c1c Begin adding entities to editor
Some checks failed
Build Dusk / build-linux (push) Failing after 56s
Build Dusk / build-psp (push) Failing after 1m8s
2025-11-22 10:38:16 -06:00
3697cc3eef Prep ent
Some checks failed
Build Dusk / build-linux (push) Failing after 55s
Build Dusk / build-psp (push) Failing after 56s
2025-11-20 16:45:50 -06:00
51a1077fda Finally merged map asset and map tool
Some checks failed
Build Dusk / build-linux (push) Failing after 39s
Build Dusk / build-psp (push) Failing after 54s
2025-11-19 20:25:25 -06:00
8740c2b165 Fixed underflow error on worldpos.
All checks were successful
Build Dusk / build-linux (push) Successful in 55s
Build Dusk / build-psp (push) Successful in 1m4s
2025-11-19 15:59:44 -06:00
6ed2bdd4c5 Added some extra checks around world positions, revealing bug. Likely going to sign all world coordinates.
All checks were successful
Build Dusk / build-linux (push) Successful in 48s
Build Dusk / build-psp (push) Successful in 59s
2025-11-19 15:52:43 -06:00
c32df89490 Added diagonal ramps
All checks were successful
Build Dusk / build-linux (push) Successful in 55s
Build Dusk / build-psp (push) Successful in 1m4s
2025-11-19 15:40:37 -06:00
bd5a67676b Minor improvements, add east and west ramp
All checks were successful
Build Dusk / build-linux (push) Successful in 48s
Build Dusk / build-psp (push) Successful in 1m0s
2025-11-19 13:25:58 -06:00
903dab49e3 Editor partially started.
All checks were successful
Build Dusk / build-linux (push) Successful in 44s
Build Dusk / build-psp (push) Successful in 55s
2025-11-19 13:00:35 -06:00
1668c4b0d2 Bit more rendering
Some checks failed
Build Dusk / build-linux (push) Failing after 53s
Build Dusk / build-psp (push) Failing after 47s
2025-11-19 09:52:31 -06:00
2179a27bf5 Prog
All checks were successful
Build Dusk / build-linux (push) Successful in 1m3s
Build Dusk / build-psp (push) Successful in 1m10s
2025-11-19 09:14:32 -06:00
6e7a0cba76 Readme
All checks were successful
Build Dusk / build-linux (push) Successful in 54s
Build Dusk / build-psp (push) Successful in 1m8s
2025-11-17 11:28:10 -06:00
69b37b30bc Finally ready to merge the two tool codebases
All checks were successful
Build Dusk / build-linux (push) Successful in 54s
Build Dusk / build-psp (push) Successful in 1m3s
2025-11-16 23:52:52 -06:00
ae941a0fdb Fixed crash
All checks were successful
Build Dusk / build-linux (push) Successful in 51s
Build Dusk / build-psp (push) Successful in 56s
2025-11-16 17:24:54 -06:00
1b741a81e5 Add .editor to ignore
All checks were successful
Build Dusk / build-linux (push) Successful in 42s
Build Dusk / build-psp (push) Successful in 51s
2025-11-16 16:26:59 -06:00
edf321515b Remove .editor
Some checks failed
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / build-linux (push) Has been cancelled
2025-11-16 16:26:49 -06:00
c874e6c197 Fixed some stuff, procrastinating the real problem
All checks were successful
Build Dusk / build-linux (push) Successful in 47s
Build Dusk / build-psp (push) Successful in 1m6s
2025-11-16 16:18:01 -06:00
9a59c22288 Try load chunk data.
All checks were successful
Build Dusk / build-linux (push) Successful in 53s
Build Dusk / build-psp (push) Successful in 56s
2025-11-16 15:02:18 -06:00
750e8840f0 Prepping editor more...
All checks were successful
Build Dusk / build-linux (push) Successful in 39s
Build Dusk / build-psp (push) Successful in 1m6s
2025-11-16 14:43:29 -06:00
cf59989167 Closer to actually editing
All checks were successful
Build Dusk / build-linux (push) Successful in 50s
Build Dusk / build-psp (push) Successful in 59s
2025-11-16 10:40:20 -06:00
7c194ab4b4 About to draw chunk
All checks were successful
Build Dusk / build-linux (push) Successful in 1m5s
Build Dusk / build-psp (push) Successful in 1m5s
2025-11-16 09:11:58 -06:00
be422d0a1e More langtool improvements
All checks were successful
Build Dusk / build-linux (push) Successful in 1m2s
Build Dusk / build-psp (push) Successful in 1m5s
2025-11-16 00:04:28 -06:00
68b63d3007 Start work on editor
All checks were successful
Build Dusk / build-linux (push) Successful in 54s
Build Dusk / build-psp (push) Successful in 1m10s
2025-11-15 23:38:31 -06:00
8525138594 test
All checks were successful
Build Dusk / build-linux (push) Successful in 54s
Build Dusk / build-psp (push) Successful in 1m9s
2025-11-15 22:31:08 -06:00
7b9f8b190e Fix?
Some checks failed
Build Dusk / build-linux (push) Failing after 52s
Build Dusk / build-psp (push) Failing after 55s
2025-11-15 22:29:07 -06:00
67f62daa9f Cmake fix
Some checks failed
Build Dusk / build-linux (push) Failing after 38s
Build Dusk / build-psp (push) Failing after 43s
2025-11-15 22:27:09 -06:00
0ec701f30b Libs
Some checks failed
Build Dusk / build-linux (push) Failing after 41s
Build Dusk / build-psp (push) Failing after 46s
2025-11-15 22:23:56 -06:00
c53439066e Cleanup, prep for editor
Some checks failed
Build Dusk / build-linux (push) Failing after 40s
Build Dusk / build-psp (push) Failing after 1m7s
2025-11-15 22:21:03 -06:00
7278bd0c6f Remove file
Some checks failed
Build Dusk / build-linux (push) Failing after 43s
Build Dusk / build-psp (push) Failing after 1m6s
2025-11-15 20:06:44 -06:00
b842e5821a Add defs generator.
Some checks failed
Build Dusk / build-linux (push) Failing after 52s
Build Dusk / build-psp (push) Failing after 1m9s
2025-11-12 19:25:10 -06:00
f7d4cce485 Remove release for now
All checks were successful
Build Dusk / build-linux (push) Successful in 47s
Build Dusk / build-psp (push) Successful in 56s
2025-11-12 15:41:48 -06:00
4f502b707f Test4
All checks were successful
Build Dusk / build-linux (push) Successful in 42s
Build Dusk / build-psp (push) Successful in 49s
Build Dusk / release (push) Successful in 6s
2025-11-12 15:38:39 -06:00
09f182228f Test3? 2025-11-12 15:38:23 -06:00
69ce48a8b9 Test2
All checks were successful
Build Dusk / build-linux (push) Successful in 47s
Build Dusk / build-psp (push) Successful in 52s
Build Dusk / release (push) Successful in 7s
2025-11-12 15:33:46 -06:00
5c2788efe4 Test
Some checks failed
Build Dusk / build-linux (push) Successful in 48s
Build Dusk / build-psp (push) Successful in 53s
Build Dusk / release (push) Failing after 7s
2025-11-12 15:31:18 -06:00
768323b5b6 Fix GHES error
Some checks failed
Build Dusk / build-linux (push) Successful in 50s
Build Dusk / build-psp (push) Successful in 53s
Build Dusk / release (push) Failing after 15s
2025-11-12 15:25:36 -06:00
e203f225e2 Fix build
Some checks failed
Build Dusk / build-psp (push) Failing after 50s
Build Dusk / release (push) Has been skipped
Build Dusk / build-linux (push) Failing after 1m1s
2025-11-12 15:20:41 -06:00
ab1e2476a0 PSP
Some checks failed
Build Dusk / build-linux (push) Successful in 48s
Build Dusk / build-psp (push) Failing after 56s
Build Dusk / release (push) Has been skipped
2025-11-12 15:15:46 -06:00
312f32e786 Add PSP build
Some checks failed
Build Dusk / build-linux (push) Successful in 43s
Build Dusk / build-psp (push) Successful in 58s
Build Dusk / release (push) Failing after 6s
2025-11-12 15:11:00 -06:00
397466f0a8 Fix ubuntu compiling
Some checks failed
Build Dusk / build-linux (push) Successful in 43s
Build Dusk / release (push) Failing after 12s
2025-11-12 15:09:17 -06:00
1a773cb8ba TEST
Some checks failed
Build Dusk / build-linux (push) Failing after 1m32s
Build Dusk / release (push) Has been skipped
2025-11-12 14:54:32 -06:00
8441c325fa Add pthread
Some checks failed
Build Dusk / build-linux (push) Failing after 42s
Build Dusk / release (push) Has been skipped
2025-11-12 14:51:38 -06:00
cd4a1afbba add case
Some checks failed
Build Dusk / build-linux (push) Failing after 50s
Build Dusk / release (push) Has been skipped
2025-11-12 14:48:45 -06:00
ae75a932bf WSrap switch with braces
Some checks failed
Build Dusk / build-linux (push) Failing after 51s
Build Dusk / release (push) Has been skipped
2025-11-12 14:46:20 -06:00
0fa2beede4 Init error state
Some checks failed
Build Dusk / build-linux (push) Failing after 37s
Build Dusk / release (push) Has been skipped
2025-11-12 14:12:57 -06:00
8e5d5ca1d7 Try affixing to const in initialization
Some checks failed
Build Dusk / build-linux (push) Failing after 49s
Build Dusk / release (push) Has been skipped
2025-11-12 14:11:48 -06:00
2a68414eec Try fix error state (s)
Some checks failed
Build Dusk / build-linux (push) Failing after 49s
Build Dusk / release (push) Has been skipped
2025-11-12 14:10:17 -06:00
3f1c8e28e9 Try fix error state?
Some checks failed
Build Dusk / build-linux (push) Failing after 42s
Build Dusk / release (push) Has been skipped
2025-11-12 14:08:41 -06:00
348531352e libzip dev
Some checks failed
Build Dusk / build-linux (push) Failing after 48s
Build Dusk / release (push) Has been skipped
2025-11-12 13:10:01 -06:00
6770cc422a polib
Some checks failed
Build Dusk / build-linux (push) Failing after 45s
Build Dusk / release (push) Has been skipped
2025-11-12 13:07:41 -06:00
7c157e22c7 Test linux build first
Some checks failed
Build Dusk / build-linux (push) Failing after 41s
Build Dusk / release (push) Has been skipped
2025-11-12 13:05:06 -06:00
0cfc6d0503 Omit sudo
Some checks failed
Build Dusk / build-linux (push) Failing after 36s
Build Dusk / build-psp (push) Failing after 31s
Build Dusk / release (push) Has been skipped
2025-11-12 13:03:13 -06:00
f7fbd16e57 TEst
Some checks failed
Build Dusk / build-linux (push) Failing after 48s
Build Dusk / build-psp (push) Failing after 6s
Build Dusk / release (push) Has been skipped
2025-11-12 12:59:12 -06:00
542aeadf0f Handle stairs better 2025-11-11 23:40:50 -06:00
84593867dc Realized why so much vram was being used. 2025-11-11 22:54:02 -06:00
9f23533069 Make stairs work 2025-11-11 22:12:08 -06:00
4f8f6a47cb Fixed some memory things on PSP 2025-11-11 20:07:28 -06:00
7d7a3f30e6 Test 2025-11-11 19:52:09 -06:00
d39ed1ea5a Chunk loading improvements 2025-11-11 19:36:04 -06:00
5c8b314689 basically chunk loading 2025-11-11 19:24:56 -06:00
9953d7d388 Prog 2025-11-11 15:50:57 -06:00
5adf8a0773 Prepping map stuff 2025-11-11 12:25:46 -06:00
26bfb912f1 Tiles 2025-11-11 07:50:20 -06:00
c07d0b32a9 qucik fx 2025-11-10 16:41:15 -06:00
562da971e9 Tile under foot 2025-11-10 11:01:41 -06:00
3eb24da475 idk2 2025-11-09 22:17:26 -06:00
8977d50992 idk 2025-11-09 22:09:22 -06:00
13365dd390 Couple world pos fixes/improvements. 2025-11-09 21:19:28 -06:00
aee06f51f0 Improve worldpos.h 2025-11-09 20:55:41 -06:00
d6c497731f Fix PSP compiled 2025-11-09 20:42:03 -06:00
f23e26d9da Add ragequit 2025-11-09 19:21:00 -06:00
ec324e02f2 Fix PSP Deadzones 2025-11-09 19:19:36 -06:00
e2ce809762 Add more debug 2025-11-09 19:04:40 -06:00
d054cf9e36 Fixed animation 2025-11-09 18:50:02 -06:00
943e775364 Time is better. 2025-11-09 18:32:33 -06:00
b9ec6523d6 Back to work 2025-11-09 16:41:54 -06:00
5206d47b43 PSP now reads data directly from EBOOT if requested 2025-11-09 15:42:26 -06:00
aaa8622956 Fixed PSP rendering 2025-11-09 14:54:33 -06:00
587d716aae Fix PSP build issues 2025-11-09 13:23:15 -06:00
5a8710cc76 Map loading and rendering 2025-11-09 13:00:43 -06:00
307f3a9dec Chunk loading example 2025-11-09 12:50:15 -06:00
eff5fc3d9a Fixed asset header compare incosistenty 2025-11-09 10:24:45 -06:00
5a3004f1d1 map 2025-11-09 09:52:47 -06:00
db589b7d91 Textbox example. 2025-11-08 17:26:25 -06:00
0a83175b66 Brought over microjrpg cutscene system (partially implemented) 2025-11-08 15:23:22 -06:00
bc4776f096 Language finished. 2025-11-08 11:12:04 -06:00
ab534bb998 Asset custom ready. 2025-11-08 08:41:32 -06:00
cf2aacd75b About to implement load strategy 2025-11-08 08:32:21 -06:00
9f88374627 Language chunking script done (untested) 2025-11-08 08:17:11 -06:00
b7d898b505 working on asset still 2025-11-07 23:04:40 -06:00
12c1fb6000 lang 2025-11-07 19:19:17 -06:00
1ce1fdff8d Refactor asset loading 2025-11-04 22:23:44 -06:00
7c11a7e5bc Started asset refact 2025-11-04 10:15:19 -06:00
7d46b98310 Ent movement 2025-11-04 09:03:36 -06:00
68c4834a62 Cursed input update 2025-11-04 08:46:47 -06:00
c9608ad7a7 Made input work on fixed timesteps primarily. 2025-11-04 08:41:18 -06:00
6ea4132ff9 Interact 2025-11-03 22:35:40 -06:00
be79356f42 Ent movement fixed. 2025-11-03 21:35:21 -06:00
d4a2e059d7 Entity 2025-11-03 19:50:23 -06:00
f3d985ecbc Starting ent stuff 2025-11-03 17:04:07 -06:00
bcba693afb Initial scene 2025-11-03 14:45:05 -06:00
b4fb7bf99f default binds 2025-11-03 14:33:42 -06:00
3ef6205ea3 Nuked console 2025-11-03 09:22:18 -06:00
3feb43fdad idk 2025-10-26 08:06:39 -05:00
d74226dab1 Ent 2025-10-25 21:15:13 -05:00
5c3db5d991 Fix vec3 copy 2025-10-24 11:30:09 -05:00
bcb8bea0fe Fixes build on the laptop? dunno why 2025-10-14 15:44:38 -05:00
0c0650a2c3 Fixed camera 2025-10-13 12:26:59 -05:00
2c0fd84c72 commit deez 2025-10-12 18:24:09 -05:00
81cd03e0c3 pixel? 2025-10-10 16:28:44 -05:00
349e6e7c94 Back to floats. 2025-10-10 09:16:08 -05:00
c4c43b23ad prog 2025-10-09 15:07:07 -05:00
7622f81309 Friction, velocity, rendering 2025-10-09 09:44:17 -05:00
c31bcf7f6a cam test 2025-10-08 23:06:39 -05:00
fef31b9102 Example rendering 2025-10-08 22:34:27 -05:00
20cf016b06 scene stuff 2025-10-08 15:18:38 -05:00
67604eca8d FPS 2025-10-08 14:17:58 -05:00
46f820690d Setup rpg stuff 2025-10-08 07:11:53 -05:00
e36256abe3 Scene fixing 2025-10-08 06:53:37 -05:00
b00ca3d48c scene crap 2025-10-06 23:16:19 -05:00
cf2e6bf382 cmd screen 2025-10-06 19:18:30 -05:00
fc52afdb00 Refator pass 1 2025-10-06 19:14:52 -05:00
85434b4edb aspect 2025-10-06 16:30:44 -05:00
f3a6c8df71 More screen modes. 2025-10-06 16:23:36 -05:00
6e5c5f61db screen mode 2025-10-06 16:18:53 -05:00
12c31ba9d1 screen 2025-10-06 15:43:27 -05:00
ea50d893d4 prog 2025-10-06 13:59:59 -05:00
bacd0e6e39 Minesweeper prog 2025-10-06 09:35:05 -05:00
c0cd4ead04 Frame 2025-10-02 18:53:47 -05:00
4b04fc65ad scerne stuff 2025-10-01 19:21:20 -05:00
83243ba32f Draw FPS 2025-10-01 17:59:41 -05:00
a734ecaa10 Game updates 2025-10-01 17:07:29 -05:00
22e2f703db Refact 2025-10-01 13:20:34 -05:00
28174b8dc8 Show Frame time 2025-09-24 19:33:10 -05:00
061352bcff load map first pass 2025-09-19 12:43:57 -05:00
2f40724258 Map saving first pass 2025-09-18 17:01:10 -05:00
a45a2a5bd7 Basic interaction 2025-09-17 18:54:12 -05:00
08221af3f8 entity dir 2025-09-17 18:38:14 -05:00
f799690d3c Physics I guess 2025-09-15 19:37:01 -05:00
07ab2b4b02 Gave up on editor 2025-09-15 09:53:34 -05:00
cafeda4368 Subdirize assets, add TSX support 2025-09-15 09:28:12 -05:00
517b39649c npc start 2025-09-14 08:58:54 -05:00
067b0d2e9f Added screen 2025-09-12 15:56:21 -05:00
9b98181d28 Process tileset. 2025-09-12 12:43:56 -05:00
46a94ecacd Renders on PSP again 2025-09-12 00:25:17 -05:00
964a9f64f2 Overworld render test. 2025-09-11 23:33:24 -05:00
b4d94c2cbe FPS toggle 2025-09-11 22:25:57 -05:00
268e9ffefd Render is color not uint8_t 2025-09-11 22:20:21 -05:00
c8f8170ec2 Command aliasing 2025-09-11 22:07:51 -05:00
8b20f0bf31 Allowed binds to execute commands. 2025-09-11 12:58:04 -05:00
fe9af039fc Add kb defs 2025-09-10 22:01:00 -05:00
6fad5bef4a Input bind complete. 2025-09-08 13:30:27 -05:00
16a0403fd4 Working on cmd bind 2025-09-07 23:53:21 -05:00
e32d1f0900 Add exec command 2025-09-07 23:24:21 -05:00
3f37b7cdb5 Beginning input refactor 2025-09-03 11:57:48 -05:00
059ccf41b6 draw text. 2025-09-02 23:01:15 -05:00
1af2b8f47b Render text working. 2025-09-02 22:47:07 -05:00
87f18d0e13 Rendering on PSP again. 2025-09-02 21:14:07 -05:00
8de12da1ec Palettized image test. 2025-09-02 18:57:28 -05:00
71080682cc Fixed an error bug 2025-09-02 09:23:46 -05:00
f915a4208b Roughly planning asset locking 2025-09-01 22:07:36 -05:00
14c41d33a7 Asset manager refactor begin. 2025-09-01 21:00:50 -05:00
3e61d6f84d Palette assets improved. 2025-09-01 20:07:12 -05:00
4541d5219b About to refator tools... again 2025-09-01 17:22:33 -05:00
3ce1566a2e Merge 2025-09-01 11:10:28 -05:00
368729f0f3 Fixed some camera bugs. 2025-09-01 11:02:30 -05:00
127392a1ae Added findzip 2025-09-01 09:39:38 -05:00
af1329710d Made a big mess of the codebase 2025-08-28 07:14:13 -05:00
30232d1275 Palette image (incomplete) 2025-08-27 22:55:47 -05:00
31fa4948d5 Image generation? 2025-08-27 22:43:38 -05:00
6c11096fd2 Palette loading done. 2025-08-27 19:59:55 -05:00
7a90d2d38f prog 2025-08-27 07:46:11 -05:00
a543bc7c00 commit asset prog 2025-08-26 16:23:08 -05:00
8af2f044ed Asset prog 2025-08-25 10:16:55 -05:00
947f21cac7 Relative 2025-08-24 15:18:01 -05:00
479aad2f06 Going to redo assets. 2025-08-24 13:57:12 -05:00
329925ea54 Asset 2025-08-23 17:43:22 -05:00
c8a3ebfcec FindSDL2 2025-08-23 15:53:08 -05:00
1dfde00317 Removed DUSK_ prefix 2025-08-23 10:13:49 -05:00
1bf6fe6eaf Add backbuffer 2025-08-22 23:30:23 -05:00
995bbe1acd Fixed time. 2025-08-22 22:42:38 -05:00
1fb9485ee8 Fixed PSP compiling 2025-08-22 20:52:59 -05:00
f9385ed233 ECS rendering 2025-08-22 16:15:42 -05:00
94ad64675d cleaned more stuff 2025-08-22 12:40:18 -05:00
b1be1deb79 ecs work 2025-08-21 22:58:39 -05:00
1b4c270ccb ECS improvements! 2025-08-21 18:58:34 -05:00
9f5894cdcb About to break scene tree. 2025-08-21 16:21:01 -05:00
4688358d6a Scene tree 2025-08-21 09:52:15 -05:00
c8390bdb12 Committing progress. 2025-08-21 07:33:08 -05:00
3a753b1299 sdl back 2025-08-20 21:56:55 -05:00
84f2735246 Console input 2025-08-20 21:23:12 -05:00
fbfcbe9578 archivemuh 2025-08-20 19:18:38 -05:00
1411c2e96b Allow different texture formats. 2025-08-18 22:51:42 -05:00
6f42a6e195 Moved entitydir to direction_t 2025-08-18 22:28:22 -05:00
1e57183ca1 Load is now rounded compile time. 2025-08-18 21:46:12 -05:00
d79e12ffaa Switched to using tile coordinates, losing my angled movement unfortunately. 2025-08-18 21:40:01 -05:00
bcb1616201 Matrices 2025-08-18 19:18:55 -05:00
38127d9260 Ortho re-added 2025-08-18 19:15:37 -05:00
d48d927202 textbox renders. 2025-08-17 17:58:53 -05:00
150a410f00 Is it really that time again? 2025-08-17 16:39:42 -05:00
fbbd2176fd Moved render UI code 2025-08-17 16:31:08 -05:00
3e19771d8f Time dr freeman? 2025-08-17 16:18:56 -05:00
3d4317260f Time stuff 2025-08-17 16:11:22 -05:00
6d6a0e4886 Technically rendering everything. 2025-08-17 14:34:51 -05:00
91b93b5b1e add quit command 2025-08-17 12:55:08 -05:00
c3310a036f Nothing really changed 2025-08-16 14:52:52 -05:00
69acbd017c Fixed framebuffer 2025-08-15 21:45:21 -05:00
81ed15b171 Builds on PSP again. 2025-08-15 21:37:44 -05:00
d1ab8b0cc8 Framebuffer done. 2025-08-15 18:50:43 -05:00
cbdc271a5e Framebuffer first 2025-08-15 18:39:19 -05:00
3c908dc1ed Camera 2025-08-15 15:56:30 -05:00
217f00ff4c Font texture. 2025-08-12 22:32:57 -05:00
610 changed files with 29858 additions and 6950 deletions

117
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,117 @@
name: Build Dusk
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Run tests in Docker
run: ./scripts/test-linux-docker.sh
build-linux:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build Linux
run: ./scripts/build-linux-docker.sh
- name: Upload Linux binary
uses: actions/upload-artifact@v6
with:
name: dusk-linux
path: build-linux/Dusk
if-no-files-found: error
build-psp:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build psp
run: ./scripts/build-psp-docker.sh
- name: Move EBOOT.PBP to Dusk subfolder
run: |
mkdir -p ./git-artifcats/Dusk/PSP/GAME/Dusk
cp build-psp/EBOOT.PBP ./git-artifcats/Dusk/PSP/GAME/Dusk/EBOOT.PBP
- name: Upload psp binary
uses: actions/upload-artifact@v6
with:
name: dusk-psp
path: ./git-artifcats/Dusk
if-no-files-found: error
build-knulli:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build knulli
run: ./scripts/build-knulli-docker.sh
- name: Move output to Dusk subfolder
run: |
mkdir -p ./git-artifcats/Dusk
cp -r build-knulli/dusk ./git-artifcats/Dusk
- name: Upload knulli binary
uses: actions/upload-artifact@v6
with:
name: dusk-knulli
path: ./git-artifcats/Dusk
if-no-files-found: error
build-gamecube:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build GameCube
run: ./scripts/build-gamecube-docker.sh
- name: Copy output files.
run: |
mkdir -p ./git-artifcats/Dusk
cp build-gamecube/Dusk.dol ./git-artifcats/Dusk/Dusk.dol
cp build-gamecube/dusk.dsk ./git-artifcats/Dusk/dusk.dsk
- name: Upload GameCube binary
uses: actions/upload-artifact@v6
with:
name: dusk-gamecube
path: ./git-artifcats/Dusk
if-no-files-found: error
build-wii:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build Wii
run: ./scripts/build-wii-docker.sh
- name: Copy output files.
run: |
mkdir -p ./git-artifcats/Dusk/apps/Dusk
cp build-wii/Dusk.dol ./git-artifcats/Dusk/apps/Dusk/boot.dol
cp build-wii/dusk.dsk ./git-artifcats/Dusk/apps/Dusk/dusk.dsk
cp docker/dolphin/meta.xml ./git-artifcats/Dusk/apps/Dusk/meta.xml
- name: Upload Wii binary
uses: actions/upload-artifact@v6
with:
name: dusk-wii
path: ./git-artifcats/Dusk
if-no-files-found: error

10
.gitignore vendored
View File

@@ -95,3 +95,13 @@ assets/borrowed
# /archive
__pycache__
package-lock.json
yarn-error.log
yarn.lock
.editor
.venv
/build2
/build*

View File

@@ -4,15 +4,13 @@
# https://opensource.org/licenses/MIT
# Setup
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 99)
cmake_minimum_required(VERSION 3.18)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
if(NOT DEFINED DUSK_TARGET_SYSTEM)
# set(DUSK_TARGET_SYSTEM "linux")
set(DUSK_TARGET_SYSTEM "psp")
endif()
option(DUSK_BUILD_TESTS "Enable tests" OFF)
# Prep cache
set(DUSK_CACHE_TARGET "dusk-target")
@@ -21,56 +19,96 @@ set(DUSK_CACHE_TARGET "dusk-target")
set(DUSK_ROOT_DIR "${CMAKE_SOURCE_DIR}")
set(DUSK_BUILD_DIR "${CMAKE_BINARY_DIR}")
set(DUSK_SOURCES_DIR "${DUSK_ROOT_DIR}/src")
set(DUSK_TEST_DIR "${DUSK_ROOT_DIR}/test")
set(DUSK_TEMP_DIR "${DUSK_BUILD_DIR}/temp")
set(DUSK_TOOLS_DIR "${DUSK_ROOT_DIR}/tools")
# set(DUSK_ASSETS_DIR "${DUSK_ROOT_DIR}/assets")
# set(DUSK_ASSETS_BUILD_DIR "${DUSK_BUILD_DIR}/assets")
set(DUSK_DATA_DIR "${DUSK_ROOT_DIR}/data")
set(DUSK_ASSETS_DIR "${DUSK_ROOT_DIR}/assets")
set(DUSK_BUILT_ASSETS_DIR "${DUSK_BUILD_DIR}/built_assets" CACHE INTERNAL ${DUSK_CACHE_TARGET})
set(DUSK_GENERATED_HEADERS_DIR "${DUSK_BUILD_DIR}/generated")
set(DUSK_TARGET_NAME "Dusk" CACHE INTERNAL ${DUSK_CACHE_TARGET})
set(DUSK_BUILD_BINARY ${DUSK_BUILD_DIR}/Dusk CACHE INTERNAL ${DUSK_CACHE_TARGET})
set(DUSK_ASSETS "" CACHE INTERNAL ${DUSK_CACHE_TARGET})
set(DUSK_LIBRARY_TARGET_NAME "DuskCore" CACHE INTERNAL ${DUSK_CACHE_TARGET})
set(DUSK_BINARY_TARGET_NAME "Dusk" CACHE INTERNAL ${DUSK_CACHE_TARGET})
set(DUSK_ASSETS_ZIP "${DUSK_BUILD_DIR}/dusk.dsk" CACHE INTERNAL ${DUSK_CACHE_TARGET})
# Toolchain
if(NOT DEFINED DUSK_TARGET_SYSTEM)
set(DUSK_TARGET_SYSTEM "linux")
endif()
# Create directories
file(MAKE_DIRECTORY ${DUSK_GENERATED_HEADERS_DIR})
file(MAKE_DIRECTORY ${DUSK_ASSETS_BUILD_DIR})
file(MAKE_DIRECTORY ${DUSK_TEMP_DIR})
file(MAKE_DIRECTORY ${DUSK_BUILT_ASSETS_DIR})
# Compilers
if(DUSK_TARGET_SYSTEM STREQUAL "psp")
find_package(pspsdk REQUIRED)
endif()
# Required build packages
find_package(Python3 COMPONENTS Interpreter REQUIRED)
# Init Project
project(${DUSK_TARGET_NAME}
# Init Project.
project(${DUSK_LIBRARY_TARGET_NAME}
VERSION 1.0.0
LANGUAGES C
)
# Executable
add_executable(${DUSK_TARGET_NAME})
# Either, create library and binary separately (used for tests), or make them
# one in the same so all code is in the binary only.
# Binary Executable
add_executable(${DUSK_BINARY_TARGET_NAME} ${DUSK_SOURCES_DIR}/dusk/null.c)
if(DUSK_BUILD_TESTS)
# MainLibrary
add_library(${DUSK_LIBRARY_TARGET_NAME} STATIC)
# Link library to binary
target_link_libraries(${DUSK_BINARY_TARGET_NAME}
PUBLIC
${DUSK_LIBRARY_TARGET_NAME}
)
else()
set(DUSK_LIBRARY_TARGET_NAME "${DUSK_BINARY_TARGET_NAME}" CACHE INTERNAL ${DUSK_CACHE_TARGET})
endif()
# Definitions
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
DUSK_TARGET_SYSTEM="${DUSK_TARGET_SYSTEM}"
)
# Toolchains
include(cmake/targets/${DUSK_TARGET_SYSTEM}.cmake)
# Add tools
add_subdirectory(tools)
# Add code
add_subdirectory(src)
# Include generated headers
target_include_directories(${DUSK_TARGET_NAME}
PUBLIC
# Include generated headers from tools.
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${DUSK_GENERATED_HEADERS_DIR}
)
# Postbuild, create PBP file for PSP.
if(DUSK_TARGET_SYSTEM STREQUAL "psp")
create_pbp_file(
TARGET "${DUSK_TARGET_NAME}"
ICON_PATH NULL
BACKGROUND_PATH NULL
PREVIEW_PATH NULL
TITLE "${DUSK_TARGET_NAME}"
VERSION 01.00
# Add main code
add_subdirectory(${DUSK_SOURCES_DIR})
# Include generated headers
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${DUSK_GENERATED_HEADERS_DIR}
)
# Handle tests
if(DUSK_BUILD_TESTS)
enable_testing()
add_subdirectory(test)
endif()
# Build assets
file(GLOB_RECURSE DUSK_ASSET_FILES CONFIGURE_DEPENDS "${DUSK_ASSETS_DIR}/*")
add_custom_command(
OUTPUT "${DUSK_ASSETS_ZIP}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${DUSK_ASSETS_DIR}"
COMMAND ${CMAKE_COMMAND} -E rm -f "${DUSK_ASSETS_ZIP}"
COMMAND ${CMAKE_COMMAND} -E tar "cf" "${DUSK_ASSETS_ZIP}" --format=zip -- .
WORKING_DIRECTORY "${DUSK_ASSETS_DIR}"
DEPENDS ${DUSK_ASSET_FILES}
VERBATIM
)
add_custom_target(DUSK_ASSETS_BUILT DEPENDS "${DUSK_ASSETS_ZIP}")
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} DUSK_ASSETS_BUILT)

23
README.md Normal file
View File

@@ -0,0 +1,23 @@
# Dusk
RPG Game Project, small and able to run on a PSP.
# Building
Each build target has different requirements. You can take a look at the git
workflow to see how the builds are done for each target. In addition, for
accessing the editor and building the game on your host system, install the
following packages, depending on your system;
Fedora;
```
sudo dnf install git make gcc cmake python python-polib python3-pillow python3-dotenv python3-numpy python-qt5 python3-pyopengl SDL2-devel zlib-devel libzip-devel bzip2-devel openssl-devel lzma-sdk-devel xz xz-devel lua-devel
```
Ubuntu;
```
sudo apt-get install git build-essential gcc python python-polib python3-pillow python3-dotenv python3-numpy python3-pyqt5 python3-opengl
```
Arch Linux;
```
sudo pacman -S git base-devel gcc python python-polib python-pillow python-dotenv python-numpy python-pyqt5 python-opengl
```

12
archive/asset/args.py Normal file
View File

@@ -0,0 +1,12 @@
import sys, os
import argparse
# Check if the script is run with the correct arguments
parser = argparse.ArgumentParser(description="Generate chunk header files")
parser.add_argument('--assets', required=True, help='Dir to output built assets')
parser.add_argument('--headers-dir', required=True, help='Directory to output individual asset headers (required for header build)')
parser.add_argument('--output-headers', help='Output header file for built assets (required for header build)')
parser.add_argument('--output-assets', required=True, help='Output directory for built assets')
parser.add_argument('--output-file', required=True, help='Output file for built assets (required for wad build)')
parser.add_argument('--input', required=True, help='Input assets to process', nargs='+')
args = parser.parse_args()

View File

@@ -0,0 +1,66 @@
import sys, os
from tools.asset.args import args
from tools.asset.process.asset import processAsset
from tools.asset.process.palette import processPaletteList
from tools.asset.process.tileset import processTilesetList
from tools.asset.process.language import processLanguageList
from tools.asset.path import getBuiltAssetsRelativePath
import zipfile
# Parse input file args.
inputAssets = []
for inputArg in args.input:
files = inputArg.split('$')
for file in files:
if str(file).strip() == '':
continue
pieces = file.split('#')
if len(pieces) < 2:
print(f"Error: Invalid input asset format '{file}'. Expected format: type#path[#option1%option2...]")
sys.exit(1)
options = {}
if len(pieces) > 2:
optionParts = pieces[2].split('%')
for part in optionParts:
partSplit = part.split('=')
if len(partSplit) < 1:
continue
if len(partSplit) == 2:
options[partSplit[0]] = partSplit[1]
else:
options[partSplit[0]] = True
inputAssets.append({
'type': pieces[0],
'path': pieces[1],
'options': options
})
if not inputAssets:
print("Error: No input assets provided.")
sys.exit(1)
# Process each asset.
files = []
for asset in inputAssets:
asset = processAsset(asset)
files.extend(asset['files'])
# Generate additional files
files.extend(processLanguageList()['files'])
# Take assets and add to a zip archive.
outputFileName = args.output_file
print(f"Creating output file: {outputFileName}")
with zipfile.ZipFile(outputFileName, 'w') as zipf:
for file in files:
relativeOutputPath = getBuiltAssetsRelativePath(file)
zipf.write(file, arcname=relativeOutputPath)
# Generate additional headers.
processPaletteList()
processTilesetList()

12
archive/asset/cache.py Normal file
View File

@@ -0,0 +1,12 @@
processedAssets = {}
def assetGetCache(assetPath):
if assetPath in processedAssets:
return processedAssets[assetPath]
return None
def assetCache(assetPath, processedData):
if assetPath in processedAssets:
return processedAssets[assetPath]
processedAssets[assetPath] = processedData
return processedData

10
archive/asset/path.py Normal file
View File

@@ -0,0 +1,10 @@
import os
from tools.asset.args import args
def getAssetRelativePath(fullPath):
# Get the relative path to the asset
return os.path.relpath(fullPath, start=args.assets).replace('\\', '/')
def getBuiltAssetsRelativePath(fullPath):
# Get the relative path to the built asset
return os.path.relpath(fullPath, start=args.output_assets).replace('\\', '/')

View File

@@ -0,0 +1,33 @@
import sys
# from processtileset import processTileset
from tools.asset.process.image import processImage
from tools.asset.process.palette import processPalette
from tools.asset.process.tileset import processTileset
from tools.asset.process.map import processMap
from tools.asset.process.language import processLanguage
from tools.asset.process.script import processScript
processedAssets = []
def processAsset(asset):
if asset['path'] in processedAssets:
return
processedAssets.append(asset['path'])
# Handle tiled tilesets
t = asset['type'].lower()
if t == 'palette':
return processPalette(asset)
elif t == 'image':
return processImage(asset)
elif t == 'tileset':
return processTileset(asset)
elif t == 'map':
return processMap(asset)
elif t == 'language':
return processLanguage(asset)
elif t == 'script':
return processScript(asset)
else:
print(f"Error: Unknown asset type '{asset['type']}' for path '{asset['path']}'")
sys.exit(1)

View File

@@ -0,0 +1,134 @@
import os
import sys
from PIL import Image
from tools.asset.process.palette import extractPaletteFromImage, palettes
from tools.asset.args import args
from tools.asset.path import getAssetRelativePath
from tools.asset.cache import assetGetCache, assetCache
images = []
def processImage(asset):
cache = assetGetCache(asset['path'])
if cache is not None:
return cache
type = None
if 'type' in asset['options']:
type = asset['options'].get('type', 'PALETTIZED').upper()
if type == 'PALETTIZED' or type is None:
return assetCache(asset['path'], processPalettizedImage(asset))
elif type == 'ALPHA':
return assetCache(asset['path'], processAlphaImage(asset))
else:
print(f"Error: Unknown image type {type} for asset {asset['path']}")
sys.exit(1)
def processPalettizedImage(asset):
assetPath = asset['path']
cache = assetGetCache(assetPath)
if cache is not None:
return cache
image = Image.open(assetPath)
imagePalette = extractPaletteFromImage(image)
# Find palette that contains every color
palette = None
for p in palettes:
hasAllColors = True
for color in imagePalette:
for palColor in p['pixels']:
if color[0] == palColor[0] and color[1] == palColor[1] and color[2] == palColor[2] and color[3] == palColor[3]:
break
elif color[3] == 0 and palColor[3] == 0:
break
else:
print('Pallete {} does not contain color #{}'.format(p['paletteName'], '{:02x}{:02x}{:02x}{:02x}'.format(color[0], color[1], color[2], color[3])))
hasAllColors = False
break
if hasAllColors:
palette = p
break
if palette is None:
palette = palettes[0] # Just to avoid reference error
print(f"No matching palette found for {assetPath}!")
# Find which pixel is missing
for color in imagePalette:
if color in palette['pixels']:
continue
# Convert to hex (with alpha)
hexColor = '#{:02x}{:02x}{:02x}{:02x}'.format(color[0], color[1], color[2], color[3])
print(f"Missing color: {hexColor} in palette {palette['paletteName']}")
sys.exit(1)
print(f"Converting image {assetPath} to use palette")
paletteIndexes = []
for pixel in list(image.getdata()):
if pixel[3] == 0:
pixel = (0, 0, 0, 0)
paletteIndex = palette['pixels'].index(pixel)
paletteIndexes.append(paletteIndex)
data = bytearray()
data.extend(b"DPI") # Dusk Palettized Image
data.extend(image.width.to_bytes(4, 'little')) # Width
data.extend(image.height.to_bytes(4, 'little')) # Height
data.append(palette['paletteIndex']) # Palette index
for paletteIndex in paletteIndexes:
if paletteIndex > 255 or paletteIndex < 0:
print(f"Error: Palette index {paletteIndex} exceeds 255!")
sys.exit(1)
data.append(paletteIndex.to_bytes(1, 'little')[0]) # Pixel index
relative = getAssetRelativePath(assetPath)
fileNameWithoutExt = os.path.splitext(os.path.basename(assetPath))[0]
outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dpi")
outputFilePath = os.path.join(args.output_assets, outputFileRelative)
os.makedirs(os.path.dirname(outputFilePath), exist_ok=True)
with open(outputFilePath, "wb") as f:
f.write(data)
outImage = {
"imagePath": outputFileRelative,
"files": [ outputFilePath ],
'width': image.width,
'height': image.height,
}
return assetCache(assetPath, outImage)
def processAlphaImage(asset):
assetPath = asset['path']
cache = assetGetCache(assetPath)
if cache is not None:
return cache
print(f"Processing alpha image: {assetPath}")
data = bytearray()
data.extend(b"DAI") # Dusk Alpha Image
image = Image.open(assetPath).convert("RGBA")
data.extend(image.width.to_bytes(4, 'little')) # Width
data.extend(image.height.to_bytes(4, 'little')) # Height
for pixel in list(image.getdata()):
# Only write alpha channel
data.append(pixel[3].to_bytes(1, 'little')[0]) # Pixel alpha
relative = getAssetRelativePath(assetPath)
fileNameWithoutExt = os.path.splitext(os.path.basename(assetPath))[0]
outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dai")
outputFilePath = os.path.join(args.output_assets, outputFileRelative)
os.makedirs(os.path.dirname(outputFilePath), exist_ok=True)
with open(outputFilePath, "wb") as f:
f.write(data)
outImage = {
"imagePath": outputFileRelative,
"files": [ outputFilePath ],
'width': image.width,
'height': image.height,
}
return assetCache(assetPath, outImage)

View File

@@ -0,0 +1,228 @@
import sys
import os
from tools.asset.args import args
from tools.asset.cache import assetCache, assetGetCache
from tools.asset.path import getAssetRelativePath
from tools.dusk.defs import defs
import polib
import re
LANGUAGE_CHUNK_CHAR_COUNT = int(defs.get('ASSET_LANG_CHUNK_CHAR_COUNT'))
LANGUAGE_DATA = {}
LANGUAGE_KEYS = []
def processLanguageList():
# Language keys header data
headerKeys = "// Auto-generated language keys header file.\n"
headerKeys += "#pragma once\n"
headerKeys += "#include \"dusk.h\"\n\n"
# This is the desired chunk groups list.. if a language key STARTS with any
# of the keys in this list we would "like to" put it in that chunk group.
# If there is no match, or the list is full then we will add it to the next
# available chunk group (that isn't a 'desired' one). If the chunk becomes
# full, then we attempt to make another chunk with the same prefix so that
# a second batching can occur.
desiredChunkGroups = {
'ui': 0
}
# Now, for each language key, create the header reference and index.
keyIndex = 0
languageKeyIndexes = {}
languageKeyChunk = {}
languageKeyChunkIndexes = {}
languageKeyChunkOffsets = {}
for key in LANGUAGE_KEYS:
headerKeys += f"#define {getLanguageVariableName(key)} {keyIndex}\n"
languageKeyIndexes[key] = keyIndex
keyIndex += 1
# Find desired chunk group
assignedChunk = None
for desiredKey in desiredChunkGroups:
if key.lower().startswith(desiredKey):
assignedChunk = desiredChunkGroups[desiredKey]
break
# If no desired chunk group matched, assign to -1
if assignedChunk is None:
assignedChunk = -1
languageKeyChunk[key] = assignedChunk
# Setup header.
for lang in LANGUAGE_DATA:
if key not in LANGUAGE_DATA[lang]:
print(f"Warning: Missing translation for key '{key}' in language '{lang}'")
sys.exit(1)
# Seal the header.
headerKeys += f"\n#define LANG_KEY_COUNT {len(LANGUAGE_KEYS)}\n"
# Now we can generate the language string chunks.
nextChunkIndex = max(desiredChunkGroups.values()) + 1
files = []
for lang in LANGUAGE_DATA:
langData = LANGUAGE_DATA[lang]
# Key = chunkIndex, value = chunkInfo
languageChunks = {}
for key in LANGUAGE_KEYS:
keyIndex = languageKeyIndexes[key]
chunkIndex = languageKeyChunk[key]
wasSetChunk = chunkIndex != -1
# This will keep looping until we find a chunk
while True:
# Determine the next chunkIndex IF chunkIndex is -1
if chunkIndex == -1:
chunkIndex = nextChunkIndex
# Is the chunk full?
curLen = languageChunks.get(chunkIndex, {'len': 0})['len']
newLen = curLen + len(langData[key])
if newLen > LANGUAGE_CHUNK_CHAR_COUNT:
# Chunk is full, need to create a new chunk.
chunkIndex = -1
if wasSetChunk:
wasSetChunk = False
else:
nextChunkIndex += 1
continue
# Chunk is not full, we can use it.
if chunkIndex not in languageChunks:
languageChunks[chunkIndex] = {
'len': 0,
'keys': []
}
languageChunks[chunkIndex]['len'] = newLen
languageChunks[chunkIndex]['keys'].append(key)
languageKeyChunkIndexes[key] = chunkIndex
languageKeyChunkOffsets[key] = curLen
break
# We have now chunked all the keys for this language!
langBuffer = b""
# Write header info
langBuffer += b'DLF' # Dusk Language File
for key in LANGUAGE_KEYS:
# Write the chunk that this key belongs to as uint32_t
chunkIndex = languageKeyChunkIndexes[key]
langBuffer += chunkIndex.to_bytes(4, byteorder='little')
# Write the offset for this key as uint32_t
offset = languageKeyChunkOffsets[key]
langBuffer += offset.to_bytes(4, byteorder='little')
# Write the length of the string as uint32_t
strData = langData[key].encode('utf-8')
langBuffer += len(strData).to_bytes(4, byteorder='little')
# Now write out each chunk's string data, packed tight and no null term.
for chunkIndex in sorted(languageChunks.keys()):
chunkInfo = languageChunks[chunkIndex]
for key in chunkInfo['keys']:
strData = langData[key].encode('utf-8')
langBuffer += strData
# Now pad the chunk to full size
curLen = chunkInfo['len']
if curLen < LANGUAGE_CHUNK_CHAR_COUNT:
padSize = LANGUAGE_CHUNK_CHAR_COUNT - curLen
langBuffer += b'\0' * padSize
# Write out the language data file
outputFile = os.path.join(args.output_assets, "language", f"{lang}.dlf")
files.append(outputFile)
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
with open(outputFile, "wb") as f:
f.write(langBuffer)
# Write out the language keys header file
outputFile = os.path.join(args.headers_dir, "locale", "language", "keys.h")
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
with open(outputFile, "w") as f:
f.write(headerKeys)
# Generate language list.
langValues = {}
headerLocale = "#pragma once\n#include \"locale/localeinfo.h\"\n\n"
headerLocale += "typedef enum {\n"
count = 0
headerLocale += f" DUSK_LOCALE_NULL = {count},\n"
count += 1
for lang in LANGUAGE_DATA:
langKey = lang.replace('-', '_').replace(' ', '_').upper()
langValues[lang] = count
headerLocale += f" DUSK_LOCALE_{langKey} = {count},\n"
count += 1
headerLocale += f" DUSK_LOCALE_COUNT = {count}\n"
headerLocale += "} dusklocale_t;\n\n"
headerLocale += f"static const localeinfo_t LOCALE_INFOS[DUSK_LOCALE_COUNT] = {{\n"
for lang in LANGUAGE_DATA:
langKey = lang.replace('-', '_').replace(' ', '_').upper()
headerLocale += f" [DUSK_LOCALE_{langKey}] = {{\n"
headerLocale += f" .file = \"{lang}\"\n"
headerLocale += f" }},\n"
headerLocale += "};\n"
headerLocale += f"static const char_t *LOCALE_SCRIPT = \n"
for lang in LANGUAGE_DATA:
langKey = lang.replace('-', '_').replace(' ', '_').upper()
langValue = langValues[lang]
headerLocale += f" \"DUSK_LOCALE_{langKey} = {langValue}\\n\"\n"
headerLocale += ";\n"
# Write out the locale enum header file
outputFile = os.path.join(args.headers_dir, "locale", "locale.h")
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
with open(outputFile, "w") as f:
f.write(headerLocale)
return {
'files': files
}
def getLanguageVariableName(languageKey):
# Take the language key, prepend LANG_, uppercase, replace any non symbols
# with _
key = languageKey.strip().upper()
key = re.sub(r'[^A-Z0-9]', '_', key)
return f"LANG_{key}"
def processLanguage(asset):
cache = assetGetCache(asset['path'])
if cache is not None:
return cache
# Load PO File
po = polib.pofile(asset['path'])
langName = po.metadata.get('Language')
if langName not in LANGUAGE_DATA:
LANGUAGE_DATA[langName] = {}
for entry in po:
key = entry.msgid
val = entry.msgstr
if key not in LANGUAGE_KEYS:
LANGUAGE_KEYS.append(key)
if key not in LANGUAGE_DATA[langName]:
LANGUAGE_DATA[langName][key] = val
else:
print(f"Error: Duplicate translation key '{key}' in language '{langName}'")
sys.exit(1)
outLanguageData = {
'data': po,
'path': asset['path'],
'files': []
}
return assetCache(asset['path'], outLanguageData)

View File

@@ -0,0 +1,154 @@
import struct
import sys
import os
import json
from tools.asset.args import args
from tools.asset.cache import assetCache, assetGetCache
from tools.asset.path import getAssetRelativePath
from tools.dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_TILE_COUNT
from tools.dusk.map import Map
from tools.dusk.chunk import Chunk
def convertModelData(modelData):
# TLDR; Model data stores things efficiently with indices, but we buffer it
# out to 6 vertex quads for simplicity.
outVertices = []
outUVs = []
outColors = []
for indice in modelData['indices']:
vertex = modelData['vertices'][indice]
uv = modelData['uvs'][indice]
color = modelData['colors'][indice]
outVertices.append(vertex)
outUVs.append(uv)
outColors.append(color)
return {
'vertices': outVertices,
'uvs': outUVs,
'colors': outColors
}
def processChunk(chunk):
cache = assetGetCache(chunk.getFilename())
if cache:
return cache
baseModel = {
'vertices': [],
'colors': [],
'uvs': []
}
models = [ baseModel ]
for tileIndex, tile in chunk.tiles.items():
tileBase = tile.getBaseTileModel()
convertedBase = convertModelData(tileBase)
baseModel['vertices'].extend(convertedBase['vertices'])
baseModel['colors'].extend(convertedBase['colors'])
baseModel['uvs'].extend(convertedBase['uvs'])
# Generate binary buffer for efficient output
buffer = bytearray()
buffer.extend(b'DMC')# Header
buffer.extend(len(chunk.tiles).to_bytes(4, 'little')) # Number of tiles
buffer.extend(len(models).to_bytes(1, 'little')) # Number of models
buffer.extend(len(chunk.entities).to_bytes(1, 'little')) # Number of entities
# Buffer tile data as array of uint8_t
for tileIndex, tile in chunk.tiles.items():
buffer.extend(tile.shape.to_bytes(1, 'little'))
# # For each model
for model in models:
vertexCount = len(model['vertices'])
buffer.extend(vertexCount.to_bytes(4, 'little'))
for i in range(vertexCount):
vertex = model['vertices'][i]
uv = model['uvs'][i]
color = model['colors'][i]
buffer.extend(color[0].to_bytes(1, 'little'))
buffer.extend(color[1].to_bytes(1, 'little'))
buffer.extend(color[2].to_bytes(1, 'little'))
buffer.extend(color[3].to_bytes(1, 'little'))
buffer.extend(bytearray(struct.pack('<f', uv[0])))
buffer.extend(bytearray(struct.pack('<f', uv[1])))
buffer.extend(bytearray(struct.pack('<f', vertex[0])))
buffer.extend(bytearray(struct.pack('<f', vertex[1])))
buffer.extend(bytearray(struct.pack('<f', vertex[2])))
# For each entity
for entity in chunk.entities.values():
buffer.extend(entity.type.to_bytes(1, 'little'))
buffer.extend(entity.localX.to_bytes(1, 'little'))
buffer.extend(entity.localY.to_bytes(1, 'little'))
buffer.extend(entity.localZ.to_bytes(1, 'little'))
pass
# Write out map file
relative = getAssetRelativePath(chunk.getFilename())
fileNameWithoutExt = os.path.splitext(os.path.basename(relative))[0]
outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dmc")
outputFilePath = os.path.join(args.output_assets, outputFileRelative)
os.makedirs(os.path.dirname(outputFilePath), exist_ok=True)
with open(outputFilePath, "wb") as f:
f.write(buffer)
outChunk = {
'files': [ outputFilePath ],
'chunk': chunk
}
return assetCache(chunk.getFilename(), outChunk)
def processMap(asset):
cache = assetGetCache(asset['path'])
if cache is not None:
return cache
map = Map(None)
map.load(asset['path'])
chunksDir = map.getChunkDirectory()
files = os.listdir(chunksDir)
if len(files) == 0:
print(f"Error: No chunk files found in {chunksDir}.")
sys.exit(1)
chunkFiles = []
for fileName in files:
if not fileName.endswith('.json'):
continue
fNameNoExt = os.path.splitext(fileName)[0]
fnPieces = fNameNoExt.split('_')
if len(fnPieces) != 3:
print(f"Error: Chunk filename {fileName} does not contain valid chunk coordinates.")
sys.exit(1)
chunk = Chunk(map, int(fnPieces[0]), int(fnPieces[1]), int(fnPieces[2]))
chunk.load()
result = processChunk(chunk)
chunkFiles.extend(result['files'])
# Map file
outBuffer = bytearray()
outBuffer.extend(b'DMF')
outBuffer.extend(len(chunkFiles).to_bytes(4, 'little'))
# DMF (Dusk Map file)
fileRelative = getAssetRelativePath(asset['path'])
fileNameWithoutExt = os.path.splitext(os.path.basename(fileRelative))[0]
outputMapRelative = os.path.join(os.path.dirname(fileRelative), f"{fileNameWithoutExt}.dmf")
outputMapPath = os.path.join(args.output_assets, outputMapRelative)
os.makedirs(os.path.dirname(outputMapPath), exist_ok=True)
with open(outputMapPath, "wb") as f:
f.write(outBuffer)
outMap = {
'files': chunkFiles
}
outMap['files'].append(outputMapPath)
return assetCache(asset['path'], outMap)

View File

@@ -0,0 +1,96 @@
import json
import os
from PIL import Image
import datetime
from tools.asset.args import args
from tools.asset.cache import assetCache, assetGetCache
palettes = []
def extractPaletteFromImage(image):
# goes through and finds all unique colors in the image
if image.mode != 'RGBA':
image = image.convert('RGBA')
pixels = list(image.getdata())
uniqueColors = []
for color in pixels:
# We treat all alpha 0 as rgba(0,0,0,0) for palette purposes
if color[3] == 0:
color = (0, 0, 0, 0)
if color not in uniqueColors:
uniqueColors.append(color)
return uniqueColors
def processPalette(asset):
print(f"Processing palette: {asset['path']}")
cache = assetGetCache(asset['path'])
if cache is not None:
return cache
paletteIndex = len(palettes)
image = Image.open(asset['path'])
pixels = extractPaletteFromImage(image)
fileNameWithoutExt = os.path.splitext(os.path.basename(asset['path']))[0]
fileNameWithoutPalette = os.path.splitext(fileNameWithoutExt)[0]
# PSP requires that the palette size be a power of two, so we will pad the
# palette with transparent colors if needed.
def mathNextPowTwo(x):
return 1 << (x - 1).bit_length()
nextPowTwo = mathNextPowTwo(len(pixels))
while len(pixels) < nextPowTwo:
pixels.append((0, 0, 0, 0))
# Header
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
data = f"// Palette Generated for {asset['path']} at {now}\n"
data += f"#include \"display/palette/palette.h\"\n\n"
data += f"#define PALETTE_{paletteIndex}_COLOR_COUNT {len(pixels)}\n\n"
data += f"#pragma pack(push, 1)\n"
data += f"static const color_t PALETTE_{paletteIndex}_COLORS[PALETTE_{paletteIndex}_COLOR_COUNT] = {{\n"
for pixel in pixels:
data += f" {{ 0x{pixel[0]:02X}, 0x{pixel[1]:02X}, 0x{pixel[2]:02X}, 0x{pixel[3]:02X} }},\n"
data += f"}};\n"
data += f"#pragma pack(pop)\n\n"
data += f"static const palette_t PALETTE_{paletteIndex} = {{\n"
data += f" .colorCount = PALETTE_{paletteIndex}_COLOR_COUNT,\n"
data += f" .colors = PALETTE_{paletteIndex}_COLORS,\n"
data += f"}};\n"
# Write Header
outputFile = os.path.join(args.headers_dir, "display", "palette", f"palette_{paletteIndex}.h")
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
with open(outputFile, "w") as f:
f.write(data)
palette = {
"paletteIndex": paletteIndex,
"paletteName": fileNameWithoutPalette,
"pixels": pixels,
"headerFile": os.path.relpath(outputFile, args.headers_dir),
"asset": asset,
"files": [ ],# No zippable files.
}
palettes.append(palette)
return assetCache(asset['path'], palette)
def processPaletteList():
data = f"// Auto-generated palette list\n"
print(f"Generating palette list with {len(palettes)} palettes.")
for palette in palettes:
data += f"#include \"{palette['headerFile']}\"\n"
data += f"\n"
data += f"#define PALETTE_LIST_COUNT {len(palettes)}\n\n"
data += f"static const palette_t* PALETTE_LIST[PALETTE_LIST_COUNT] = {{\n"
for palette in palettes:
data += f" &PALETTE_{palette['paletteIndex']},\n"
data += f"}};\n"
# Write the palette list to a header file
outputFile = os.path.join(args.headers_dir, "display", "palette", "palettelist.h")
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
with open(outputFile, "w") as f:
f.write(data)

View File

@@ -0,0 +1,43 @@
import sys
import os
from tools.asset.args import args
from tools.asset.cache import assetCache, assetGetCache
from tools.asset.path import getAssetRelativePath
from tools.dusk.defs import fileDefs
def processScript(asset):
cache = assetGetCache(asset['path'])
if cache is not None:
return cache
# Load the lua file as a string
with open(asset['path'], 'r', encoding='utf-8') as f:
luaCode = f.read()
# TODO: I will precompile or minify the Lua code here in the future
# Replace all definitions in the code
for key, val in fileDefs.items():
luaCode = luaCode.replace(key, str(val))
# Create output Dusk Script File (DSF) data
data = ""
data += "DSF"
data += luaCode
# Write to relative output file path.
relative = getAssetRelativePath(asset['path'])
fileNameWithoutExt = os.path.splitext(os.path.basename(asset['path']))[0]
outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dsf")
outputFilePath = os.path.join(args.output_assets, outputFileRelative)
os.makedirs(os.path.dirname(outputFilePath), exist_ok=True)
with open(outputFilePath, "wb") as f:
f.write(data.encode('utf-8'))
outScript = {
'data': data,
'path': asset['path'],
'files': [ outputFilePath ],
'scriptPath': outputFileRelative,
}
return assetCache(asset['path'], outScript)

View File

@@ -0,0 +1,178 @@
import json
import sys
import os
import datetime
from xml.etree import ElementTree
from tools.asset.process.image import processImage
from tools.asset.path import getAssetRelativePath
from tools.asset.args import args
from tools.asset.cache import assetGetCache, assetCache
tilesets = []
def loadTilesetFromTSX(asset):
# Load the TSX file
tree = ElementTree.parse(asset['path'])
root = tree.getroot()
# Expect tileheight, tilewidth, columns and tilecount attributes
if 'tilewidth' not in root.attrib or 'tileheight' not in root.attrib or 'columns' not in root.attrib or 'tilecount' not in root.attrib:
print(f"Error: TSX file {asset['path']} is missing required attributes (tilewidth, tileheight, columns, tilecount)")
sys.exit(1)
tileWidth = int(root.attrib['tilewidth'])
tileHeight = int(root.attrib['tileheight'])
columns = int(root.attrib['columns'])
tileCount = int(root.attrib['tilecount'])
rows = (tileCount + columns - 1) // columns # Calculate rows based on tileCount and columns
# Find the image element
imageElement = root.find('image')
if imageElement is None or 'source' not in imageElement.attrib:
print(f"Error: TSX file {asset['path']} is missing an image element with a source attribute")
sys.exit(1)
imagePath = imageElement.attrib['source']
# Image is relative to the TSX file
imageAssetPath = os.path.join(os.path.dirname(asset['path']), imagePath)
image = processImage({
'path': imageAssetPath,
'options': asset['options'],
})
return {
"image": image,
"tileWidth": tileWidth,
"tileHeight": tileHeight,
"columns": columns,
"rows": rows,
"originalWidth": tileWidth * columns,
"originalHeight": tileHeight * rows,
}
def loadTilesetFromArgs(asset):
# We need to determine how big each tile is. This can either be provided as
# an arg of tileWidth/tileHeight or as a count of rows/columns.
# Additionally, if the image has been factored, then the user can provide both
# tile sizes AND cols/rows to indicate the original size of the image.
image = processImage(asset)
tileWidth, tileHeight = None, None
columns, rows = None, None
originalWidth, originalHeight = image['width'], image['height']
if 'tileWidth' in asset['options'] and 'columns' in asset['options']:
tileWidth = int(asset['options']['tileWidth'])
columns = int(asset['options']['columns'])
originalWidth = tileWidth * columns
elif 'tileWidth' in asset['options']:
tileWidth = int(asset['options']['tileWidth'])
columns = image['width'] // tileWidth
elif 'columns' in asset['options']:
columns = int(asset['options']['columns'])
tileWidth = image['width'] // columns
else:
print(f"Error: Tileset {asset['path']} must specify either tileWidth or columns")
sys.exit(1)
if 'tileHeight' in asset['options'] and 'rows' in asset['options']:
tileHeight = int(asset['options']['tileHeight'])
rows = int(asset['options']['rows'])
originalHeight = tileHeight * rows
elif 'tileHeight' in asset['options']:
tileHeight = int(asset['options']['tileHeight'])
rows = image['height'] // tileHeight
elif 'rows' in asset['options']:
rows = int(asset['options']['rows'])
tileHeight = image['height'] // rows
else:
print(f"Error: Tileset {asset['path']} must specify either tileHeight or rows")
sys.exit(1)
return {
"image": image,
"tileWidth": tileWidth,
"tileHeight": tileHeight,
"columns": columns,
"rows": rows,
"originalWidth": originalWidth,
"originalHeight": originalHeight,
}
def processTileset(asset):
cache = assetGetCache(asset['path'])
if cache is not None:
return cache
print(f"Processing tileset: {asset['path']}")
tilesetData = None
if asset['path'].endswith('.tsx'):
tilesetData = loadTilesetFromTSX(asset)
else:
tilesetData = loadTilesetFromArgs(asset)
fileNameWithoutExtension = os.path.splitext(os.path.basename(asset['path']))[0]
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
tilesetName = fileNameWithoutExtension
tilesetNameUpper = tilesetName.upper()
widthScale = tilesetData['originalWidth'] / tilesetData['image']['width']
heightScale = tilesetData['originalHeight'] / tilesetData['image']['height']
# Create header
data = f"// Tileset Generated for {asset['path']} at {now}\n"
data += f"#pragma once\n"
data += f"#include \"display/tileset/tileset.h\"\n\n"
data += f"static const tileset_t TILESET_{tilesetNameUpper} = {{\n"
data += f" .name = {json.dumps(tilesetName)},\n"
data += f" .tileWidth = {tilesetData['tileWidth']},\n"
data += f" .tileHeight = {tilesetData['tileHeight']},\n"
data += f" .tileCount = {tilesetData['columns'] * tilesetData['rows']},\n"
data += f" .columns = {tilesetData['columns']},\n"
data += f" .rows = {tilesetData['rows']},\n"
data += f" .uv = {{ {widthScale / tilesetData['columns']}f, {heightScale / tilesetData['rows']}f }},\n"
data += f" .image = {json.dumps(tilesetData['image']['imagePath'])},\n"
data += f"}};\n"
# Write Header
outputFile = os.path.join(args.headers_dir, "display", "tileset", f"tileset_{tilesetName}.h")
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
with open(outputFile, 'w') as f:
f.write(data)
print(f"Write header for tileset: {outputFile}")
tileset = {
"files": [],
"image": tilesetData['image'],
"headerFile": os.path.relpath(outputFile, args.headers_dir),
"tilesetName": tilesetName,
"tilesetNameUpper": tilesetNameUpper,
"tilesetIndex": len(tilesets),
"tilesetData": tilesetData,
"files": tilesetData['image']['files'],
}
tilesets.append(tileset)
return assetCache(asset['path'], tileset)
def processTilesetList():
data = f"// Tileset List Generated at {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
data += f"#pragma once\n"
for tileset in tilesets:
data += f"#include \"{tileset['headerFile']}\"\n"
data += f"\n"
data += f"#define TILESET_LIST_COUNT {len(tilesets)}\n\n"
data += f"static const tileset_t* TILESET_LIST[TILESET_LIST_COUNT] = {{\n"
for tileset in tilesets:
data += f" &TILESET_{tileset['tilesetNameUpper']},\n"
data += f"}};\n"
# Write header.
outputFile = os.path.join(args.headers_dir, "display", "tileset", f"tilesetlist.h")
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
with open(outputFile, 'w') as f:
f.write(data)

47
archive/assetpalette.c Normal file
View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assetpalette.h"
#include "asset/assettype.h"
#include "assert/assert.h"
errorret_t assetPaletteLoad(assetentire_t entire) {
assertNotNull(entire.data, "Data pointer cannot be NULL.");
assertNotNull(entire.output, "Output pointer cannot be NULL.");
assetpalette_t *assetData = (assetpalette_t *)entire.data;
palette_t *palette = (palette_t *)entire.output;
// Read header and version (first 4 bytes)
if(
assetData->header[0] != 'D' ||
assetData->header[1] != 'P' ||
assetData->header[2] != 'F'
) {
errorThrow("Invalid palette header");
}
// Version (can only be 1 atm)
if(assetData->version != 0x01) {
errorThrow("Unsupported palette version");
}
// Check color count.
if(
assetData->colorCount == 0 ||
assetData->colorCount > PALETTE_COLOR_COUNT_MAX
) {
errorThrow("Invalid palette color count");
}
paletteInit(
palette,
assetData->colorCount,
assetData->colors
);
errorOk();
}

30
archive/assetpalette.h Normal file
View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "display/texture/palette.h"
typedef struct assetentire_s assetentire_t;
#pragma pack(push, 1)
typedef struct {
char_t header[3];
uint8_t version;
uint8_t colorCount;
color_t colors[PALETTE_COLOR_COUNT_MAX];
} assetpalette_t;
#pragma pack(pop)
/**
* Loads a palette from the given data pointer into the output palette.
*
* @param entire Data received from the asset loader system.
* @return An error code.
*/
errorret_t assetPaletteLoad(assetentire_t entire);

158
archive/dusk/chunk.py Normal file
View File

@@ -0,0 +1,158 @@
import json
import os
from tools.dusk.event import Event
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_VERTEX_COUNT_MAX, TILE_SHAPE_NULL
from tools.dusk.tile import Tile
from tools.dusk.entity import Entity
from tools.dusk.region import Region
from tools.editor.map.vertexbuffer import VertexBuffer
from OpenGL.GL import *
class Chunk:
def __init__(self, map, x, y, z):
self.map = map
self.x = x
self.y = y
self.z = z
self.current = {}
self.original = {}
self.entities = {}
self.regions = {}
self.onChunkData = Event()
self.dirty = False
self.tiles = {}
self.vertexBuffer = VertexBuffer()
# Test Region
region = self.regions[0] = Region(self)
region.minX = 0
region.minY = 0
region.minZ = 0
region.maxX = 32
region.maxY = 32
region.maxZ = 32
region.updateVertexs()
# Gen tiles.
tileIndex = 0
for tz in range(CHUNK_DEPTH):
for ty in range(CHUNK_HEIGHT):
for tx in range(CHUNK_WIDTH):
self.tiles[tileIndex] = Tile(self, tx, ty, tz, tileIndex)
tileIndex += 1
# Update vertices
self.tileUpdateVertices()
def reload(self, newX, newY, newZ):
self.x = newX
self.y = newY
self.z = newZ
self.entities = {}
for tile in self.tiles.values():
tile.chunkReload(newX, newY, newZ)
self.load()
def tileUpdateVertices(self):
self.vertexBuffer.clear()
for tile in self.tiles.values():
tile.buffer(self.vertexBuffer)
self.vertexBuffer.buildData()
def load(self):
fname = self.getFilename()
if not fname or not os.path.exists(fname):
self.new()
return
try:
with open(fname, 'r') as f:
data = json.load(f)
if not 'shapes' in data:
data['shapes'] = []
# For each tile.
for tile in self.tiles.values():
tile.load(data)
# For each entity.
self.entities = {}
if 'entities' in data:
for id, entData in enumerate(data['entities']):
ent = Entity(self)
ent.load(entData)
self.entities[id] = ent
self.tileUpdateVertices()
self.dirty = False
self.onChunkData.invoke(self)
self.map.onEntityData.invoke()
except Exception as e:
raise RuntimeError(f"Failed to load chunk file: {e}")
def save(self):
if not self.isDirty():
return
dataOut = {
'shapes': [],
'entities': []
}
for tile in self.tiles.values():
dataOut['shapes'].append(tile.shape)
for ent in self.entities.values():
entData = {}
ent.save(entData)
dataOut['entities'].append(entData)
fname = self.getFilename()
if not fname:
raise ValueError("No filename specified for saving chunk.")
try:
with open(fname, 'w') as f:
json.dump(dataOut, f)
self.dirty = False
self.onChunkData.invoke(self)
except Exception as e:
raise RuntimeError(f"Failed to save chunk file: {e}")
def new(self):
for tile in self.tiles.values():
tile.shape = TILE_SHAPE_NULL
self.tileUpdateVertices()
self.dirty = False
self.onChunkData.invoke(self)
def isDirty(self):
return self.dirty
def getFilename(self):
if not self.map or not hasattr(self.map, 'getChunkDirectory'):
return None
dirPath = self.map.getChunkDirectory()
if dirPath is None:
return None
return f"{dirPath}/{self.x}_{self.y}_{self.z}.json"
def draw(self):
self.vertexBuffer.draw()
def addEntity(self, localX=0, localY=0, localZ=0):
ent = Entity(self, localX, localY, localZ)
self.entities[len(self.entities)] = ent
self.map.onEntityData.invoke()
self.dirty = True
return ent
def removeEntity(self, entity):
for key, val in list(self.entities.items()):
if val == entity:
del self.entities[key]
self.map.onEntityData.invoke()
self.dirty = True
return True
return False

49
archive/dusk/defs.py Normal file
View File

@@ -0,0 +1,49 @@
from dotenv import load_dotenv, dotenv_values
import os
import sys
current_file_path = os.path.abspath(__file__)
duskDefsPath = os.path.join(os.path.dirname(current_file_path), "..", "..", "src", "duskdefs.env")
# Ensure the .env file exists
if not os.path.isfile(duskDefsPath):
print(f"Error: .env file not found at {duskDefsPath}")
sys.exit(1)
load_dotenv(dotenv_path=duskDefsPath)
defs = {key: os.getenv(key) for key in os.environ.keys()}
fileDefs = dotenv_values(dotenv_path=duskDefsPath)
# Parsed out definitions
CHUNK_WIDTH = int(defs.get('CHUNK_WIDTH'))
CHUNK_HEIGHT = int(defs.get('CHUNK_HEIGHT'))
CHUNK_DEPTH = int(defs.get('CHUNK_DEPTH'))
CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH
CHUNK_VERTEX_COUNT_MAX = int(defs.get('CHUNK_VERTEX_COUNT_MAX'))
TILE_WIDTH = float(defs.get('TILE_WIDTH'))
TILE_HEIGHT = float(defs.get('TILE_HEIGHT'))
TILE_DEPTH = float(defs.get('TILE_DEPTH'))
RPG_CAMERA_PIXELS_PER_UNIT = float(defs.get('RPG_CAMERA_PIXELS_PER_UNIT'))
RPG_CAMERA_Z_OFFSET = float(defs.get('RPG_CAMERA_Z_OFFSET'))
RPG_CAMERA_FOV = float(defs.get('RPG_CAMERA_FOV'))
MAP_WIDTH = 5
MAP_HEIGHT = 5
MAP_DEPTH = 3
MAP_CHUNK_COUNT = MAP_WIDTH * MAP_HEIGHT * MAP_DEPTH
TILE_SHAPES = {}
for key in defs.keys():
if key.startswith('TILE_SHAPE_'):
globals()[key] = int(defs.get(key))
TILE_SHAPES[key] = int(defs.get(key))
ENTITY_TYPES = {}
for key in defs.keys():
if key.startswith('ENTITY_TYPE_'):
globals()[key] = int(defs.get(key))
if key != 'ENTITY_TYPE_COUNT':
ENTITY_TYPES[key] = int(defs.get(key))

90
archive/dusk/entity.py Normal file
View File

@@ -0,0 +1,90 @@
from tools.dusk.defs import ENTITY_TYPE_NULL, ENTITY_TYPE_NPC, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
from tools.editor.map.vertexbuffer import VertexBuffer
class Entity:
def __init__(self, chunk, localX=0, localY=0, localZ=0):
self.type = ENTITY_TYPE_NPC
self.name = "Unititled"
self.localX = localX % CHUNK_WIDTH
self.localY = localY % CHUNK_HEIGHT
self.localZ = localZ % CHUNK_DEPTH
self.chunk = chunk
self.vertexBuffer = VertexBuffer()
pass
def load(self, obj):
self.type = obj.get('type', ENTITY_TYPE_NULL)
self.localX = obj.get('x', 0)
self.localY = obj.get('y', 0)
self.localZ = obj.get('z', 0)
self.name = obj.get('name', "Untitled")
pass
def save(self, obj):
obj['type'] = self.type
obj['name'] = self.name
obj['x'] = self.localX
obj['y'] = self.localY
obj['z'] = self.localZ
pass
def setType(self, entityType):
if self.type == entityType:
return
self.type = entityType
self.chunk.dirty = True
self.chunk.map.onEntityData.invoke()
def setName(self, name):
if self.name == name:
return
self.name = name
self.chunk.dirty = True
self.chunk.map.onEntityData.invoke()
def draw(self):
self.vertexBuffer.clear()
startX = (self.chunk.x * CHUNK_WIDTH + self.localX) * TILE_WIDTH
startY = (self.chunk.y * CHUNK_HEIGHT + self.localY) * TILE_HEIGHT
startZ = (self.chunk.z * CHUNK_DEPTH + self.localZ) * TILE_DEPTH
w = TILE_WIDTH
h = TILE_HEIGHT
d = TILE_DEPTH
# Center
startX -= w / 2
startY -= h / 2
startZ -= d / 2
# Offset upwards a little
startZ += 1
# Buffer simple quad at current position (need 6 positions)
self.vertexBuffer.vertices = [
startX, startY, startZ,
startX + w, startY, startZ,
startX + w, startY + h, startZ,
startX, startY, startZ,
startX + w, startY + h, startZ,
startX, startY + h, startZ,
]
self.vertexBuffer.colors = [
1.0, 0.0, 1.0, 1.0,
1.0, 0.0, 1.0, 1.0,
1.0, 0.0, 1.0, 1.0,
1.0, 0.0, 1.0, 1.0,
1.0, 0.0, 1.0, 1.0,
1.0, 0.0, 1.0, 1.0,
]
self.vertexBuffer.uvs = [
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 0.0,
1.0, 1.0,
0.0, 1.0,
]
self.vertexBuffer.buildData()
self.vertexBuffer.draw()

18
archive/dusk/event.py Normal file
View File

@@ -0,0 +1,18 @@
class Event:
def __init__(self):
self._subscribers = []
def sub(self, callback):
"""Subscribe a callback to the event."""
if callback not in self._subscribers:
self._subscribers.append(callback)
def unsub(self, callback):
"""Unsubscribe a callback from the event."""
if callback in self._subscribers:
self._subscribers.remove(callback)
def invoke(self, *args, **kwargs):
"""Invoke all subscribers with the given arguments."""
for callback in self._subscribers:
callback(*args, **kwargs)

259
archive/dusk/map.py Normal file
View File

@@ -0,0 +1,259 @@
import json
import sys
from tools.dusk.event import Event
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtCore import QTimer
import os
from tools.dusk.chunk import Chunk
from tools.dusk.defs import MAP_WIDTH, MAP_HEIGHT, MAP_DEPTH, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH
import traceback
MAP_DEFAULT_PATH = os.path.join(os.path.dirname(__file__), '../../assets/map/')
EDITOR_CONFIG_PATH = os.path.join(os.path.dirname(__file__), '.editor')
class Map:
def __init__(self, parent):
self.parent = parent
self.data = {}
self.dataOriginal = {}
self.position = [None, None, None] # x, y, z
self.topLeftX = None
self.topLeftY = None
self.topLeftZ = None
self.chunks = {}
self.onMapData = Event()
self.onPositionChange = Event()
self.onEntityData = Event()
self.mapFileName = None
self.lastFile = None
self.firstLoad = True
index = 0
for x in range(MAP_WIDTH):
for y in range(MAP_HEIGHT):
for z in range(MAP_DEPTH):
self.chunks[index] = Chunk(self, x, y, z)
index += 1
# Only in editor instances:
self.moveTo(0, 0, 0)
if parent is not None:
QTimer.singleShot(16, self.loadLastFile)
def loadLastFile(self):
if not os.path.exists(EDITOR_CONFIG_PATH):
return
try:
with open(EDITOR_CONFIG_PATH, 'r') as f:
config = json.load(f)
lastFile = config.get('lastFile')
lastPosition = config.get('lastPosition')
leftPanelIndex = config.get('leftPanelIndex')
if lastFile and os.path.exists(lastFile):
self.load(lastFile)
if lastPosition and isinstance(lastPosition, list) and len(lastPosition) == 3:
self.moveTo(*lastPosition)
if leftPanelIndex is not None:
self.parent.leftPanel.tabs.setCurrentIndex(leftPanelIndex)
except Exception:
traceback.print_exc()
def updateEditorConfig(self):
if self.parent is None:
return
try:
mapFileName = self.getMapFilename()
config = {
'lastFile': mapFileName if mapFileName else "",
'lastPosition': self.position,
'leftPanelIndex': self.parent.leftPanel.tabs.currentIndex()
}
config_dir = os.path.dirname(EDITOR_CONFIG_PATH)
if not os.path.exists(config_dir):
os.makedirs(config_dir, exist_ok=True)
with open(EDITOR_CONFIG_PATH, 'w') as f:
json.dump(config, f, indent=2)
except Exception:
traceback.print_exc()
def newFile(self):
self.data = {}
self.dataOriginal = {}
self.mapFileName = None
self.lastFile = None
for chunk in self.chunks.values():
chunk.new()
self.moveTo(0, 0, 0)
self.onMapData.invoke(self.data)
self.updateEditorConfig()
def save(self, fname=None):
if not self.getMapFilename() and fname is None:
filePath, _ = QFileDialog.getSaveFileName(None, "Save Map File", MAP_DEFAULT_PATH, "Map Files (*.json)")
if not filePath:
return
self.mapFileName = filePath
if fname:
self.mapFileName = fname
try:
with open(self.getMapFilename(), 'w') as f:
json.dump(self.data, f, indent=2)
self.dataOriginal = json.loads(json.dumps(self.data)) # Deep copy
for chunk in self.chunks.values():
chunk.save()
self.updateEditorConfig()
except Exception as e:
traceback.print_exc()
QMessageBox.critical(None, "Save Error", f"Failed to save map file:\n{e}")
def load(self, fileName):
try:
with open(fileName, 'r') as f:
self.data = json.load(f)
self.mapFileName = fileName
self.dataOriginal = json.loads(json.dumps(self.data)) # Deep copy
for chunk in self.chunks.values():
chunk.load()
self.onMapData.invoke(self.data)
self.updateEditorConfig()
except Exception as e:
traceback.print_exc()
QMessageBox.critical(None, "Load Error", f"Failed to load map file:\n{e}")
def isMapFileDirty(self):
return json.dumps(self.data, sort_keys=True) != json.dumps(self.dataOriginal, sort_keys=True)
def isDirty(self):
return self.isMapFileDirty() or self.anyChunksDirty()
def getMapFilename(self):
return self.mapFileName if self.mapFileName and os.path.exists(self.mapFileName) else None
def getMapDirectory(self):
if self.mapFileName is None:
return None
dirname = os.path.dirname(self.mapFileName)
return dirname
def getChunkDirectory(self):
dirName = self.getMapDirectory()
if dirName is None:
return None
return os.path.join(dirName, 'chunks')
def anyChunksDirty(self):
for chunk in self.chunks.values():
if chunk.isDirty():
return True
return False
def moveTo(self, x, y, z):
if self.position == [x, y, z]:
return
# We need to decide if the chunks should be unloaded here or not.
newTopLeftChunkX = x // CHUNK_WIDTH - (MAP_WIDTH // 2)
newTopLeftChunkY = y // CHUNK_HEIGHT - (MAP_HEIGHT // 2)
newTopLeftChunkZ = z // CHUNK_DEPTH - (MAP_DEPTH // 2)
if(newTopLeftChunkX != self.topLeftX or
newTopLeftChunkY != self.topLeftY or
newTopLeftChunkZ != self.topLeftZ):
chunksToUnload = []
chunksToKeep = []
for chunk in self.chunks.values():
chunkWorldX = chunk.x
chunkWorldY = chunk.y
chunkWorldZ = chunk.z
if(chunkWorldX < newTopLeftChunkX or
chunkWorldX >= newTopLeftChunkX + MAP_WIDTH or
chunkWorldY < newTopLeftChunkY or
chunkWorldY >= newTopLeftChunkY + MAP_HEIGHT or
chunkWorldZ < newTopLeftChunkZ or
chunkWorldZ >= newTopLeftChunkZ + MAP_DEPTH):
chunksToUnload.append(chunk)
else:
chunksToKeep.append(chunk)
# Unload chunks that are out of the new bounds.
for chunk in chunksToUnload:
if chunk.isDirty():
print(f"Can't move map, some chunks are dirty: ({chunk.x}, {chunk.y}, {chunk.z})")
return
# Now we can safely unload the chunks.
chunkIndex = 0
newChunks = {}
for chunk in chunksToKeep:
newChunks[chunkIndex] = chunk
chunkIndex += 1
for xPos in range(newTopLeftChunkX, newTopLeftChunkX + MAP_WIDTH):
for yPos in range(newTopLeftChunkY, newTopLeftChunkY + MAP_HEIGHT):
for zPos in range(newTopLeftChunkZ, newTopLeftChunkZ + MAP_DEPTH):
# Check if we already have this chunk.
found = False
for chunk in chunksToKeep:
if chunk.x == xPos and chunk.y == yPos and chunk.z == zPos:
found = True
break
if not found:
# Create a new chunk.
newChunk = chunksToUnload.pop()
newChunk.reload(xPos, yPos, zPos)
newChunks[chunkIndex] = newChunk
chunkIndex += 1
self.chunks = newChunks
self.topLeftX = newTopLeftChunkX
self.topLeftY = newTopLeftChunkY
self.topLeftZ = newTopLeftChunkZ
self.position = [x, y, z]
self.onPositionChange.invoke(self.position)
if not self.firstLoad:
self.updateEditorConfig()
self.firstLoad = False
def moveRelative(self, x, y, z):
self.moveTo(
self.position[0] + x,
self.position[1] + y,
self.position[2] + z
)
def draw(self):
for chunk in self.chunks.values():
chunk.draw()
for chunk in self.chunks.values():
for entity in chunk.entities.values():
entity.draw()
# Only render on Region tab
if self.parent.leftPanel.tabs.currentWidget() == self.parent.leftPanel.regionPanel:
for chunk in self.chunks.values():
for region in chunk.regions.values():
region.draw()
def getChunkAtWorldPos(self, x, y, z):
chunkX = x // CHUNK_WIDTH
chunkY = y // CHUNK_HEIGHT
chunkZ = z // CHUNK_DEPTH
for chunk in self.chunks.values():
if chunk.x == chunkX and chunk.y == chunkY and chunk.z == chunkZ:
return chunk
return None
def getTileAtWorldPos(self, x, y, z):
chunk = self.getChunkAtWorldPos(x, y, z)
if not chunk:
print("No chunk found at position:", (x, y, z))
return None
tileX = x % CHUNK_WIDTH
tileY = y % CHUNK_HEIGHT
tileZ = z % CHUNK_DEPTH
tileIndex = tileX + tileY * CHUNK_WIDTH + tileZ * CHUNK_WIDTH * CHUNK_HEIGHT
return chunk.tiles.get(tileIndex)

141
archive/dusk/region.py Normal file
View File

@@ -0,0 +1,141 @@
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
from tools.editor.map.vertexbuffer import VertexBuffer
from OpenGL.GL import *
from OpenGL.GLU import *
class Region:
def __init__(self, chunk):
self.minX = 0
self.minY = 0
self.minZ = 0
self.maxX = 0
self.maxY = 0
self.maxZ = 0
self.chunk = chunk
self.vertexBuffer = VertexBuffer()
self.color = (1.0, 0.0, 0.0)
self.updateVertexs()
pass
def updateVertexs(self):
# Draw a quad, semi transparent with solid outlines
vminX = (self.minX * CHUNK_WIDTH) * TILE_WIDTH
vminY = (self.minY * CHUNK_HEIGHT) * TILE_HEIGHT
vminZ = (self.minZ * CHUNK_DEPTH) * TILE_DEPTH
vmaxX = (self.maxX * CHUNK_WIDTH) * TILE_WIDTH
vmaxY = (self.maxY * CHUNK_HEIGHT) * TILE_HEIGHT
vmaxZ = (self.maxZ * CHUNK_DEPTH) * TILE_DEPTH
alpha = 0.25
# Move back half a tile width
vminX -= TILE_WIDTH / 2
vmaxX -= TILE_WIDTH / 2
vminY -= TILE_HEIGHT / 2
vmaxY -= TILE_HEIGHT / 2
vminZ -= TILE_DEPTH / 2
vmaxZ -= TILE_DEPTH / 2
# Cube (6 verts per face)
self.vertexBuffer.vertices = [
# Front face
vminX, vminY, vmaxZ,
vmaxX, vminY, vmaxZ,
vmaxX, vmaxY, vmaxZ,
vminX, vminY, vmaxZ,
vmaxX, vmaxY, vmaxZ,
vminX, vmaxY, vmaxZ,
# Back face
vmaxX, vminY, vminZ,
vminX, vminY, vminZ,
vminX, vmaxY, vminZ,
vmaxX, vminY, vminZ,
vminX, vmaxY, vminZ,
vmaxX, vmaxY, vminZ,
# Left face
vminX, vminY, vminZ,
vminX, vminY, vmaxZ,
vminX, vmaxY, vmaxZ,
vminX, vminY, vminZ,
vminX, vmaxY, vmaxZ,
vminX, vmaxY, vminZ,
# Right face
vmaxX, vminY, vmaxZ,
vmaxX, vminY, vminZ,
vmaxX, vmaxY, vminZ,
vmaxX, vminY, vmaxZ,
vmaxX, vmaxY, vminZ,
vmaxX, vmaxY, vmaxZ,
# Top face
vminX, vmaxY, vmaxZ,
vmaxX, vmaxY, vmaxZ,
vmaxX, vmaxY, vminZ,
vminX, vmaxY, vmaxZ,
vmaxX, vmaxY, vminZ,
vminX, vmaxY, vminZ,
# Bottom face
vminX, vminY, vminZ,
vmaxX, vminY, vminZ,
vmaxX, vminY, vmaxZ,
vminX, vminY, vminZ,
vmaxX, vminY, vmaxZ,
vminX, vminY, vmaxZ,
]
self.vertexBuffer.colors = [
# Front face
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
# Back face
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
# Left face
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
# Right face
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
# Top face
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
# Bottom face
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
self.color[0], self.color[1], self.color[2], alpha,
]
self.vertexBuffer.buildData()
def draw(self):
self.vertexBuffer.draw()

206
archive/dusk/tile.py Normal file
View File

@@ -0,0 +1,206 @@
from OpenGL.GL import *
from tools.dusk.defs import (
TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH,
CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH,
TILE_SHAPE_NULL, TILE_SHAPE_FLOOR,
TILE_SHAPE_RAMP_NORTH, TILE_SHAPE_RAMP_SOUTH,
TILE_SHAPE_RAMP_EAST, TILE_SHAPE_RAMP_WEST,
TILE_SHAPE_RAMP_SOUTHWEST, TILE_SHAPE_RAMP_SOUTHEAST,
TILE_SHAPE_RAMP_NORTHWEST, TILE_SHAPE_RAMP_NORTHEAST
)
def getItem(arr, index, default):
if index < len(arr):
return arr[index]
return default
class Tile:
def __init__(self, chunk, x, y, z, tileIndex):
self.shape = TILE_SHAPE_NULL
self.chunk = chunk
self.x = x
self.y = y
self.z = z
self.index = tileIndex
self.posX = x * TILE_WIDTH + chunk.x * CHUNK_WIDTH * TILE_WIDTH
self.posY = y * TILE_HEIGHT + chunk.y * CHUNK_HEIGHT * TILE_HEIGHT
self.posZ = z * TILE_DEPTH + chunk.z * CHUNK_DEPTH * TILE_DEPTH
def chunkReload(self, newX, newY, newZ):
self.posX = self.x * TILE_WIDTH + newX * CHUNK_WIDTH * TILE_WIDTH
self.posY = self.y * TILE_HEIGHT + newY * CHUNK_HEIGHT * TILE_HEIGHT
self.posZ = self.z * TILE_DEPTH + newZ * CHUNK_DEPTH * TILE_DEPTH
def load(self, chunkData):
self.shape = getItem(chunkData['shapes'], self.index, TILE_SHAPE_NULL)
def setShape(self, shape):
if shape == self.shape:
return
self.shape = shape
self.chunk.dirty = True
self.chunk.tileUpdateVertices()
self.chunk.onChunkData.invoke(self.chunk)
def getBaseTileModel(self):
vertices = []
indices = []
uvs = []
colors = []
if self.shape == TILE_SHAPE_NULL:
pass
elif self.shape == TILE_SHAPE_FLOOR:
vertices = [
(self.posX, self.posY, self.posZ),
(self.posX + TILE_WIDTH, self.posY, self.posZ),
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ),
(self.posX, self.posY + TILE_HEIGHT, self.posZ)
]
indices = [0, 1, 2, 0, 2, 3]
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
colors = [ (255, 255, 255, 255) ] * 4
elif self.shape == TILE_SHAPE_RAMP_NORTH:
vertices = [
(self.posX, self.posY, self.posZ + TILE_DEPTH),
(self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH),
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ),
(self.posX, self.posY + TILE_HEIGHT, self.posZ)
]
indices = [0, 1, 2, 0, 2, 3]
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
colors = [ (255, 0, 0, 255) ] * 4
elif self.shape == TILE_SHAPE_RAMP_SOUTH:
vertices = [
(self.posX, self.posY, self.posZ),
(self.posX + TILE_WIDTH, self.posY, self.posZ),
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH),
(self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH)
]
indices = [0, 1, 2, 0, 2, 3]
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
colors = [ (0, 255, 0, 255) ] * 4
elif self.shape == TILE_SHAPE_RAMP_EAST:
vertices = [
(self.posX, self.posY, self.posZ),
(self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH),
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH),
(self.posX, self.posY + TILE_HEIGHT, self.posZ)
]
indices = [0, 1, 2, 0, 2, 3]
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
colors = [ (0, 0, 255, 255) ] * 4
elif self.shape == TILE_SHAPE_RAMP_WEST:
vertices = [
(self.posX, self.posY, self.posZ + TILE_DEPTH),
(self.posX + TILE_WIDTH, self.posY, self.posZ),
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ),
(self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH)
]
indices = [0, 1, 2, 0, 2, 3]
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
colors = [ (255, 255, 0, 255) ] * 4
elif self.shape == TILE_SHAPE_RAMP_SOUTHWEST:
vertices = [
(self.posX, self.posY, self.posZ + TILE_DEPTH),
(self.posX + TILE_WIDTH, self.posY, self.posZ),
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH),
(self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH)
]
indices = [0, 1, 2, 0, 2, 3]
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
colors = [ (255, 128, 0, 255) ] * 4
elif self.shape == TILE_SHAPE_RAMP_NORTHWEST:
vertices = [
(self.posX, self.posY, self.posZ + TILE_DEPTH),
(self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH),
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ),
(self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH)
]
indices = [0, 1, 2, 0, 2, 3]
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
colors = [ (128, 255, 0, 255) ] * 4
elif self.shape == TILE_SHAPE_RAMP_NORTHEAST:
vertices = [
(self.posX, self.posY, self.posZ + TILE_DEPTH),
(self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH),
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH),
(self.posX, self.posY + TILE_HEIGHT, self.posZ)
]
indices = [0, 1, 2, 0, 2, 3]
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
colors = [ (0, 255, 128, 255) ] * 4
elif self.shape == TILE_SHAPE_RAMP_SOUTHEAST:
vertices = [
(self.posX, self.posY, self.posZ),
(self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH),
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH),
(self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH)
]
indices = [0, 1, 2, 0, 2, 3]
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
colors = [ (255, 128, 255, 255) ] * 4
else:
# Solid black cube for unknown shape
x0, y0, z0 = self.posX, self.posY, self.posZ
x1, y1, z1 = self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH
vertices = [
(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0), # bottom
(x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1) # top
]
indices = [
0,1,2, 0,2,3, # bottom
4,5,6, 4,6,7, # top
0,1,5, 0,5,4, # front
2,3,7, 2,7,6, # back
1,2,6, 1,6,5, # right
3,0,4, 3,4,7 # left
]
uvs = [ (0,0) ] * 8
colors = [ (0,0,0,255) ] * 8
return {
'vertices': vertices,
'indices': indices,
'uvs': uvs,
'colors': colors
}
def buffer(self, vertexBuffer):
if self.shape == TILE_SHAPE_NULL:
return
# New code:
baseData = self.getBaseTileModel()
# Base data is indiced but we need to buffer unindiced data
for index in baseData['indices']:
verts = baseData['vertices'][index]
uv = baseData['uvs'][index]
color = baseData['colors'][index]
vertexBuffer.vertices.extend([
verts[0] - (TILE_WIDTH / 2.0),
verts[1] - (TILE_HEIGHT / 2.0),
verts[2] - (TILE_DEPTH / 2.0)
])
vertexBuffer.colors.extend([
color[0] / 255.0,
color[1] / 255.0,
color[2] / 255.0,
color[3] / 255.0
])

57
archive/editor/__main__.py Executable file
View File

@@ -0,0 +1,57 @@
#!/usr/bin/env python3
import sys
from PyQt5.QtWidgets import (
QApplication, QVBoxLayout, QPushButton,
QDialog
)
from OpenGL.GL import *
from OpenGL.GLU import *
from tools.editor.maptool import MapWindow
from tools.editor.langtool import LangToolWindow
from tools.editor.cutscenetool import CutsceneToolWindow
DEFAULT_TOOL = None
DEFAULT_TOOL = "map"
# DEFAULT_TOOL = "cutscene"
TOOLS = [
("Map Editor", "map", MapWindow),
("Language Editor", "language", LangToolWindow),
("Cutscene Editor", "cutscene", CutsceneToolWindow),
]
class EditorChoiceDialog(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("Choose Tool")
layout = QVBoxLayout(self)
self.selected = None
for label, key, win_cls in TOOLS:
btn = QPushButton(label)
btn.clicked.connect(lambda checked, w=win_cls: self.choose_tool(w))
layout.addWidget(btn)
def choose_tool(self, win_cls):
self.selected = win_cls
self.accept()
def get_choice(self):
return self.selected
def main():
app = QApplication(sys.argv)
tool_map = { key: win_cls for _, key, win_cls in TOOLS }
if DEFAULT_TOOL in tool_map:
win_cls = tool_map[DEFAULT_TOOL]
else:
choice_dialog = EditorChoiceDialog()
if choice_dialog.exec_() == QDialog.Accepted:
win_cls = choice_dialog.get_choice()
else:
sys.exit(0)
win = win_cls()
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,58 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLineEdit, QLabel, QSizePolicy, QComboBox, QHBoxLayout, QSpacerItem
from PyQt5.QtCore import Qt, pyqtSignal
from .cutscenewait import CutsceneWaitEditor
from .cutscenetext import CutsceneTextEditor
EDITOR_MAP = (
( "wait", "Wait", CutsceneWaitEditor ),
( "text", "Text", CutsceneTextEditor )
)
class CutsceneItemEditor(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.layout = QVBoxLayout(self)
self.layout.setAlignment(Qt.AlignTop | Qt.AlignLeft)
self.layout.addWidget(QLabel("Item Properties:"))
rowLayout = QHBoxLayout()
rowLayout.setSpacing(8)
rowLayout.addWidget(QLabel("Name:"))
self.nameInput = QLineEdit()
rowLayout.addWidget(self.nameInput)
rowLayout.addWidget(QLabel("Type:"))
self.typeDropdown = QComboBox()
self.typeDropdown.addItems([typeName for typeKey, typeName, editorClass in EDITOR_MAP])
rowLayout.addWidget(self.typeDropdown)
self.layout.addLayout(rowLayout)
self.activeEditor = None
# Events
self.nameInput.textChanged.connect(self.onNameChanged)
self.typeDropdown.currentTextChanged.connect(self.onTypeChanged)
# First load
self.onNameChanged(self.nameInput.text())
self.onTypeChanged(self.typeDropdown.currentText())
def onNameChanged(self, nameText):
pass
def onTypeChanged(self, typeText):
typeKey = typeText.lower()
# Remove existing editor
if self.activeEditor:
self.layout.removeWidget(self.activeEditor)
self.activeEditor.deleteLater()
self.activeEditor = None
# Create new editor
for key, name, editorClass in EDITOR_MAP:
if key == typeKey:
self.activeEditor = editorClass()
self.layout.addWidget(self.activeEditor)
break

View File

@@ -0,0 +1,54 @@
from PyQt5.QtWidgets import QMenuBar, QAction, QFileDialog, QMessageBox
from PyQt5.QtGui import QKeySequence
class CutsceneMenuBar(QMenuBar):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
fileMenu = self.addMenu("File")
self.newAction = QAction("New", self)
self.newAction.setShortcut(QKeySequence.New)
self.newAction.triggered.connect(self.newFile)
fileMenu.addAction(self.newAction)
self.openAction = QAction("Open", self)
self.openAction.setShortcut(QKeySequence.Open)
self.openAction.triggered.connect(self.openFile)
fileMenu.addAction(self.openAction)
self.saveAction = QAction("Save", self)
self.saveAction.setShortcut(QKeySequence.Save)
self.saveAction.triggered.connect(self.saveFile)
fileMenu.addAction(self.saveAction)
self.saveAsAction = QAction("Save As", self)
self.saveAsAction.setShortcut(QKeySequence.SaveAs)
self.saveAsAction.triggered.connect(self.saveFileAs)
fileMenu.addAction(self.saveAsAction)
def newFile(self):
self.parent.clearCutscene()
def openFile(self):
path, _ = QFileDialog.getOpenFileName(self.parent, "Open Cutscene File", "", "JSON Files (*.json);;All Files (*)")
if not path:
return
# TODO: Load file contents into timeline
self.parent.currentFile = path
pass
def saveFile(self):
if not self.parent.currentFile:
self.saveFileAs()
return
# TODO: Save timeline to self.parent.currentFile
pass
def saveFileAs(self):
path, _ = QFileDialog.getSaveFileName(self.parent, "Save Cutscene File As", "", "JSON Files (*.json);;All Files (*)")
if path:
self.parent.currentFile = path
self.saveFile()

View File

@@ -0,0 +1,21 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QTextEdit
from PyQt5.QtCore import Qt
class CutsceneTextEditor(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
label = QLabel("Text:")
label.setSizePolicy(label.sizePolicy().Expanding, label.sizePolicy().Fixed)
layout.addWidget(label)
self.textInput = QTextEdit()
self.textInput.setSizePolicy(self.textInput.sizePolicy().Expanding, self.textInput.sizePolicy().Expanding)
layout.addWidget(self.textInput, stretch=1)
def setText(self, text):
self.textInput.setPlainText(text)
def getText(self):
return self.textInput.toPlainText()

View File

@@ -0,0 +1,20 @@
from PyQt5.QtWidgets import QWidget, QFormLayout, QDoubleSpinBox, QLabel
class CutsceneWaitEditor(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout = QFormLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
self.waitTimeInput = QDoubleSpinBox()
self.waitTimeInput.setMinimum(0.0)
self.waitTimeInput.setMaximum(9999.0)
self.waitTimeInput.setDecimals(2)
self.waitTimeInput.setSingleStep(0.1)
layout.addRow(QLabel("Wait Time (seconds):"), self.waitTimeInput)
def setWaitTime(self, value):
self.waitTimeInput.setValue(value)
def getWaitTime(self):
return self.waitTimeInput.value()

View File

@@ -0,0 +1,101 @@
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListWidget, QListWidgetItem, QMenuBar, QAction, QFileDialog, QMessageBox
from PyQt5.QtGui import QKeySequence
from tools.editor.cutscene.cutsceneitemeditor import CutsceneItemEditor
from tools.editor.cutscene.cutscenemenubar import CutsceneMenuBar
import sys
class CutsceneToolWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Dusk Cutscene Editor")
self.setGeometry(100, 100, 800, 600)
self.nextItemNumber = 1 # Track next available number
self.currentFile = None
self.dirty = False # Track unsaved changes
# File menu (handled by CutsceneMenuBar)
menubar = CutsceneMenuBar(self)
self.setMenuBar(menubar)
# Main layout: horizontal split
central = QWidget()
mainLayout = QHBoxLayout(central)
self.setCentralWidget(central)
# Timeline
leftPanel = QWidget()
leftLayout = QVBoxLayout(leftPanel)
self.timelineList = QListWidget()
self.timelineList.setSelectionMode(QListWidget.SingleSelection)
leftLayout.addWidget(QLabel("Cutscene Timeline"))
leftLayout.addWidget(self.timelineList)
btnLayout = QHBoxLayout()
self.addBtn = QPushButton("Add")
self.removeBtn = QPushButton("Remove")
btnLayout.addWidget(self.addBtn)
btnLayout.addWidget(self.removeBtn)
leftLayout.addLayout(btnLayout)
mainLayout.addWidget(leftPanel, 2)
# Property editor
self.editorPanel = QWidget()
self.editorLayout = QVBoxLayout(self.editorPanel)
self.itemEditor = None # Only create when needed
mainLayout.addWidget(self.editorPanel, 3)
# Events
self.timelineList.currentItemChanged.connect(self.onItemSelected)
self.addBtn.clicked.connect(self.addCutsceneItem)
self.removeBtn.clicked.connect(self.removeCutsceneItem)
def addCutsceneItem(self):
name = f"Cutscene item {self.nextItemNumber}"
timelineItem = QListWidgetItem(name)
self.timelineList.addItem(timelineItem)
self.timelineList.setCurrentItem(timelineItem)
self.nextItemNumber += 1
self.dirty = True
def removeCutsceneItem(self):
row = self.timelineList.currentRow()
if row < 0:
return
self.timelineList.takeItem(row)
self.dirty = True
# Remove editor if nothing selected
if self.timelineList.currentItem() is None or self.itemEditor is None:
return
self.editorLayout.removeWidget(self.itemEditor)
self.itemEditor.deleteLater()
self.itemEditor = None
def clearCutscene(self):
self.timelineList.clear()
self.nextItemNumber = 1
self.currentFile = None
self.dirty = False
if self.itemEditor:
self.editorLayout.removeWidget(self.itemEditor)
def onItemSelected(self, current, previous):
if current:
if not self.itemEditor:
self.itemEditor = CutsceneItemEditor()
self.editorLayout.addWidget(self.itemEditor)
return
if not self.itemEditor:
return
self.editorLayout.removeWidget(self.itemEditor)
self.itemEditor.deleteLater()
self.itemEditor = None
if __name__ == "__main__":
app = QApplication(sys.argv)
window = CutsceneToolWindow()
window.show()
sys.exit(app.exec_())

202
archive/editor/langtool.py Normal file
View File

@@ -0,0 +1,202 @@
#!/usr/bin/env python3
from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, QMenuBar, QMessageBox, QFileDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QTableWidget, QTableWidgetItem, QHeaderView, QPushButton, QTabWidget, QFormLayout
from PyQt5.QtCore import Qt
import sys
import os
import polib
class LangToolWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Dusk Language Editor")
self.setGeometry(100, 100, 800, 600)
self.current_file = None
self.dirty = False
self.init_menu()
self.init_ui()
def init_ui(self):
central = QWidget()
layout = QVBoxLayout(central)
tabs = QTabWidget()
# Header Tab
header_tab = QWidget()
header_layout = QFormLayout(header_tab)
self.language_edit = QLineEdit()
self.language_edit.setMaximumWidth(220)
header_layout.addRow(QLabel("Language:"), self.language_edit)
self.plural_edit = QLineEdit()
self.plural_edit.setMaximumWidth(320)
header_layout.addRow(QLabel("Plural-Forms:"), self.plural_edit)
self.content_type_edit = QLineEdit("text/plain; charset=UTF-8")
self.content_type_edit.setMaximumWidth(320)
header_layout.addRow(QLabel("Content-Type:"), self.content_type_edit)
tabs.addTab(header_tab, "Header")
# Strings Tab
strings_tab = QWidget()
strings_layout = QVBoxLayout(strings_tab)
self.po_table = QTableWidget()
self.po_table.setColumnCount(2)
self.po_table.setHorizontalHeaderLabels(["msgid", "msgstr"])
self.po_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.po_table.verticalHeader().setMinimumWidth(22)
self.po_table.verticalHeader().setDefaultAlignment(Qt.AlignRight | Qt.AlignVCenter)
strings_layout.addWidget(self.po_table)
row_btn_layout = QHBoxLayout()
add_row_btn = QPushButton("Add Row")
remove_row_btn = QPushButton("Remove Row")
row_btn_layout.addWidget(add_row_btn)
row_btn_layout.addWidget(remove_row_btn)
strings_layout.addLayout(row_btn_layout)
tabs.addTab(strings_tab, "Strings")
layout.addWidget(tabs)
add_row_btn.clicked.connect(self.add_row)
remove_row_btn.clicked.connect(self.remove_row)
self.add_row_btn = add_row_btn
self.remove_row_btn = remove_row_btn
self.setCentralWidget(central)
# Connect edits to dirty flag
self.language_edit.textChanged.connect(self.set_dirty)
self.plural_edit.textChanged.connect(self.set_dirty)
self.content_type_edit.textChanged.connect(self.set_dirty)
self.po_table.itemChanged.connect(self.set_dirty)
def set_dirty(self):
self.dirty = True
self.update_save_action()
def init_menu(self):
menubar = self.menuBar()
file_menu = menubar.addMenu("File")
new_action = QAction("New", self)
open_action = QAction("Open", self)
save_action = QAction("Save", self)
save_as_action = QAction("Save As", self)
new_action.triggered.connect(lambda: self.handle_file_action("new"))
open_action.triggered.connect(lambda: self.handle_file_action("open"))
save_action.triggered.connect(self.handle_save)
save_as_action.triggered.connect(self.handle_save_as)
file_menu.addAction(new_action)
file_menu.addAction(open_action)
file_menu.addAction(save_action)
file_menu.addAction(save_as_action)
self.save_action = save_action # Store reference for enabling/disabling
self.update_save_action()
def update_save_action(self):
self.save_action.setEnabled(self.current_file is not None)
def handle_file_action(self, action):
if self.dirty:
reply = QMessageBox.question(self, "Save Changes?", "Do you want to save changes before proceeding?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
if reply == QMessageBox.Cancel:
return
elif reply == QMessageBox.Yes:
self.handle_save()
if action == "new":
self.new_file()
elif action == "open":
default_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../assets/locale"))
file_path, _ = QFileDialog.getOpenFileName(self, "Open Language File", default_dir, "PO Files (*.po)")
if file_path:
self.open_file(file_path)
def new_file(self):
self.current_file = None
self.dirty = False
self.language_edit.setText("")
self.plural_edit.setText("")
self.po_table.setRowCount(0)
self.update_save_action()
def open_file(self, file_path):
self.current_file = file_path
self.dirty = False
self.update_save_action()
self.load_po_file(file_path)
def load_po_file(self, file_path):
po = polib.pofile(file_path)
language = po.metadata.get('Language', '')
plural = po.metadata.get('Plural-Forms', '')
content_type = po.metadata.get('Content-Type', 'text/plain; charset=UTF-8')
self.language_edit.setText(language)
self.plural_edit.setText(plural)
self.content_type_edit.setText(content_type)
self.po_table.setRowCount(len(po))
for row, entry in enumerate(po):
self.po_table.setItem(row, 0, QTableWidgetItem(entry.msgid))
self.po_table.setItem(row, 1, QTableWidgetItem(entry.msgstr))
def save_file(self, file_path):
po = polib.POFile()
po.metadata = {
'Language': self.language_edit.text(),
'Content-Type': self.content_type_edit.text(),
'Plural-Forms': self.plural_edit.text(),
}
for row in range(self.po_table.rowCount()):
msgid_item = self.po_table.item(row, 0)
msgstr_item = self.po_table.item(row, 1)
msgid = msgid_item.text() if msgid_item else ''
msgstr = msgstr_item.text() if msgstr_item else ''
if msgid or msgstr:
entry = polib.POEntry(msgid=msgid, msgstr=msgstr)
po.append(entry)
po.save(file_path)
self.dirty = False
self.update_save_action()
def handle_save(self):
if self.current_file:
self.save_file(self.current_file)
else:
self.handle_save_as()
def handle_save_as(self):
default_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../assets/locale"))
file_path, _ = QFileDialog.getSaveFileName(self, "Save Language File As", default_dir, "PO Files (*.po)")
if file_path:
self.current_file = file_path
self.update_save_action()
self.save_file(file_path)
def add_row(self):
row = self.po_table.rowCount()
self.po_table.insertRow(row)
self.po_table.setItem(row, 0, QTableWidgetItem(""))
self.po_table.setItem(row, 1, QTableWidgetItem(""))
self.po_table.setCurrentCell(row, 0)
self.set_dirty()
def remove_row(self):
row = self.po_table.currentRow()
if row >= 0:
self.po_table.removeRow(row)
self.set_dirty()
def closeEvent(self, event):
if self.dirty:
reply = QMessageBox.question(self, "Save Changes?", "Do you want to save changes before exiting?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
if reply == QMessageBox.Cancel:
event.ignore()
return
elif reply == QMessageBox.Yes:
self.handle_save()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = LangToolWindow()
window.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1,55 @@
import math
import time
from OpenGL.GL import *
from OpenGL.GLU import *
from tools.dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH, RPG_CAMERA_PIXELS_PER_UNIT, RPG_CAMERA_Z_OFFSET, RPG_CAMERA_FOV
class Camera:
def __init__(self, parent):
self.parent = parent
self.pixelsPerUnit = RPG_CAMERA_PIXELS_PER_UNIT
self.yOffset = RPG_CAMERA_Z_OFFSET
self.fov = RPG_CAMERA_FOV
self.scale = 8.0
self.lastTime = time.time()
self.lookAtTarget = [0.0, 0.0, 0.0]
def setup(self, vw, vh, duration=0.1):
now = time.time()
delta = now - self.lastTime
self.lastTime = now
# Calculate ease factor for exponential smoothing over 'duration' seconds
ease = 1 - math.exp(-delta / duration)
z = (vh / 2.0) / (
(self.pixelsPerUnit * self.scale) * math.tan(math.radians(self.fov / 2.0))
)
lookAt = [
self.parent.map.position[0] * TILE_WIDTH,
self.parent.map.position[1] * TILE_HEIGHT,
self.parent.map.position[2] * TILE_DEPTH,
]
aspectRatio = vw / vh
# Ease the lookAt target
for i in range(3):
self.lookAtTarget[i] += (lookAt[i] - self.lookAtTarget[i]) * ease
# Camera position is now based on the eased lookAtTarget
cameraPosition = (
self.lookAtTarget[0],
self.lookAtTarget[1] + self.yOffset,
self.lookAtTarget[2] + z
)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(self.fov, aspectRatio, 0.1, 1000.0)
glScalef(1.0, -1.0, 1.0) # Flip the projection matrix upside down
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
gluLookAt(
cameraPosition[0], cameraPosition[1], cameraPosition[2],
self.lookAtTarget[0], self.lookAtTarget[1], self.lookAtTarget[2],
0.0, 1.0, 0.0
)

View File

@@ -0,0 +1,80 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QGridLayout, QTreeWidget, QTreeWidgetItem, QComboBox
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_SHAPES
class ChunkPanel(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
layout = QVBoxLayout(self)
# Tile shape dropdown
self.tileShapeDropdown = QComboBox()
self.tileShapeDropdown.addItems(TILE_SHAPES.keys())
self.tileShapeDropdown.setToolTip("Tile Shape")
layout.addWidget(self.tileShapeDropdown)
# Add expandable tree list
self.tree = QTreeWidget()
self.tree.setHeaderLabel("Chunks")
self.tree.expandAll() # Expand by default, remove if you want collapsed
layout.addWidget(self.tree) # Removed invalid stretch factor
# Add stretch so tree expands
layout.setStretchFactor(self.tree, 1)
# Event subscriptions
self.parent.map.onMapData.sub(self.onMapData)
self.parent.map.onPositionChange.sub(self.onPositionChange)
self.tileShapeDropdown.currentTextChanged.connect(self.onTileShapeChanged)
# For each chunk
for chunk in self.parent.map.chunks.values():
# Create tree element
item = QTreeWidgetItem(self.tree, ["Chunk ({}, {}, {})".format(chunk.x, chunk.y, chunk.z)])
chunk.chunkPanelTree = item
chunk.chunkPanelTree.setExpanded(True)
item.setData(0, 0, chunk) # Store chunk reference
chunk.onChunkData.sub(self.onChunkData)
def onMapData(self, data):
pass
def onPositionChange(self, pos):
self.updateChunkList()
tile = self.parent.map.getTileAtWorldPos(*self.parent.map.position)
if tile is None:
return
key = "TILE_SHAPE_NULL"
for k, v in TILE_SHAPES.items():
if v != tile.shape:
continue
key = k
break
self.tileShapeDropdown.setCurrentText(key)
def onTileShapeChanged(self, shape_key):
tile = self.parent.map.getTileAtWorldPos(*self.parent.map.position)
if tile is None or shape_key not in TILE_SHAPES:
return
tile.setShape(TILE_SHAPES[shape_key])
def updateChunkList(self):
# Clear existing items
currentChunk = self.parent.map.getChunkAtWorldPos(*self.parent.map.position)
# Example tree items
for chunk in self.parent.map.chunks.values():
title = "Chunk ({}, {}, {})".format(chunk.x, chunk.y, chunk.z)
if chunk == currentChunk:
title += " [C]"
if chunk.isDirty():
title += " *"
item = chunk.chunkPanelTree
item.setText(0, title)
def onChunkData(self, chunk):
self.updateChunkList()

View File

@@ -0,0 +1,154 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QComboBox, QHBoxLayout, QPushButton, QLineEdit, QListWidget, QListWidgetItem
from PyQt5.QtCore import Qt
from tools.dusk.entity import Entity
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, ENTITY_TYPES, ENTITY_TYPE_NULL
class EntityPanel(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
layout = QVBoxLayout(self)
self.setLayout(layout)
# Top panel placeholder
topWidget = QLabel("Entity Editor")
layout.addWidget(topWidget)
# Name input
nameLayout = QHBoxLayout()
nameLabel = QLabel("Name:")
self.nameInput = QLineEdit()
nameLayout.addWidget(nameLabel)
nameLayout.addWidget(self.nameInput)
layout.addLayout(nameLayout)
# Entity Type dropdown (single selection)
typeLayout = QHBoxLayout()
typeLabel = QLabel("Type:")
self.typeDropdown = QComboBox()
self.typeDropdown.addItems(ENTITY_TYPES)
typeLayout.addWidget(typeLabel)
typeLayout.addWidget(self.typeDropdown)
layout.addLayout(typeLayout)
# Entity list and buttons
self.entityList = QListWidget()
self.entityList.addItems([])
layout.addWidget(self.entityList, stretch=1)
btnLayout = QHBoxLayout()
self.btnAdd = QPushButton("Add")
self.btnRemove = QPushButton("Remove")
btnLayout.addWidget(self.btnAdd)
btnLayout.addWidget(self.btnRemove)
layout.addLayout(btnLayout)
# Events
self.btnAdd.clicked.connect(self.onAddEntity)
self.btnRemove.clicked.connect(self.onRemoveEntity)
self.parent.map.onEntityData.sub(self.onEntityData)
self.parent.map.onPositionChange.sub(self.onPositionChange)
self.entityList.itemClicked.connect(self.onEntityClicked)
self.entityList.itemDoubleClicked.connect(self.onEntityDoubleClicked)
self.typeDropdown.currentIndexChanged.connect(self.onTypeSelected)
self.nameInput.textChanged.connect(self.onNameChanged)
# Call once to populate
self.onEntityData()
self.onEntityUnselect()
def onEntityUnselect(self):
self.entityList.setCurrentItem(None)
self.nameInput.setText("")
self.typeDropdown.setCurrentIndex(ENTITY_TYPE_NULL)
def onEntitySelect(self, entity):
self.entityList.setCurrentItem(entity.item)
self.nameInput.setText(entity.name)
self.typeDropdown.setCurrentIndex(entity.type)
def onEntityDoubleClicked(self, item):
entity = item.data(Qt.UserRole)
chunk = entity.chunk
worldX = (chunk.x * CHUNK_WIDTH) + entity.localX
worldY = (chunk.y * CHUNK_HEIGHT) + entity.localY
worldZ = (chunk.z * CHUNK_DEPTH) + entity.localZ
self.parent.map.moveTo(worldX, worldY, worldZ)
def onEntityClicked(self, item):
pass
def onAddEntity(self):
chunk = self.parent.map.getChunkAtWorldPos(*self.parent.map.position)
if chunk is None:
return
localX = (self.parent.map.position[0] - (chunk.x * CHUNK_WIDTH)) % CHUNK_WIDTH
localY = (self.parent.map.position[1] - (chunk.y * CHUNK_HEIGHT)) % CHUNK_HEIGHT
localZ = (self.parent.map.position[2] - (chunk.z * CHUNK_DEPTH)) % CHUNK_DEPTH
# Make sure there's not already an entity here
for ent in chunk.entities.values():
if ent.localX == localX and ent.localY == localY and ent.localZ == localZ:
print("Entity already exists at this location")
return
ent = chunk.addEntity(localX, localY, localZ)
def onRemoveEntity(self):
item = self.entityList.currentItem()
if item is None:
return
entity = item.data(Qt.UserRole)
if entity:
chunk = entity.chunk
chunk.removeEntity(entity)
pass
def onEntityData(self):
self.onEntityUnselect()
self.entityList.clear()
for chunk in self.parent.map.chunks.values():
for id, entity in chunk.entities.items():
item = QListWidgetItem(entity.name)
item.setData(Qt.UserRole, entity) # Store the entity object
entity.item = item
self.entityList.addItem(item)
# Select if there is something at current position
self.onPositionChange(self.parent.map.position)
def onPositionChange(self, position):
self.onEntityUnselect()
# Get Entity at..
chunk = self.parent.map.getChunkAtWorldPos(*position)
if chunk is None:
return
localX = (position[0] - (chunk.x * CHUNK_WIDTH)) % CHUNK_WIDTH
localY = (position[1] - (chunk.y * CHUNK_HEIGHT)) % CHUNK_HEIGHT
localZ = (position[2] - (chunk.z * CHUNK_DEPTH)) % CHUNK_DEPTH
for ent in chunk.entities.values():
if ent.localX != localX or ent.localY != localY or ent.localZ != localZ:
continue
self.onEntitySelect(ent)
self.entityList.setCurrentItem(ent.item)
break
def onTypeSelected(self, index):
item = self.entityList.currentItem()
if item is None:
return
entity = item.data(Qt.UserRole)
if entity:
entity.setType(index)
def onNameChanged(self, text):
item = self.entityList.currentItem()
if item is None:
return
entity = item.data(Qt.UserRole)
if entity:
entity.setName(text)

View File

@@ -0,0 +1,41 @@
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QOpenGLWidget
from OpenGL.GL import *
from OpenGL.GLU import *
class GLWidget(QOpenGLWidget):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.timer = QTimer(self)
self.timer.timeout.connect(self.update)
self.timer.start(16) # ~60 FPS
def initializeGL(self):
glClearColor(0.392, 0.584, 0.929, 1.0)
glEnable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable(GL_POLYGON_OFFSET_FILL)
glPolygonOffset(1.0, 1.0)
glDisable(GL_POLYGON_OFFSET_FILL)
def resizeGL(self, w, h):
pass
def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
w = self.width()
h = self.height()
if h <= 0:
h = 1
if w <= 0:
w = 1
glViewport(0, 0, w, h)
self.parent.camera.setup(w, h)
self.parent.grid.draw()
self.parent.map.draw()
self.parent.selectBox.draw()

View File

@@ -0,0 +1,46 @@
from OpenGL.GL import *
from tools.dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
class Grid:
def __init__(self, lines=1000):
self.cellWidth = TILE_WIDTH
self.cellHeight = TILE_HEIGHT
self.lines = lines
self.enabled = True
def draw(self):
if not self.enabled:
return
center = [0.01,0.01,0.01]
halfWidth = self.cellWidth * self.lines // 2
halfHeight = self.cellHeight * self.lines // 2
# Draw origin axes
glBegin(GL_LINES)
# X axis - RED
glColor3f(1.0, 0.0, 0.0)
glVertex3f(center[0] - halfWidth, center[1], center[2])
glVertex3f(center[0] + halfWidth, center[1], center[2])
# Y axis - GREEN
glColor3f(0.0, 1.0, 0.0)
glVertex3f(center[0], center[1] - halfHeight, center[2])
glVertex3f(center[0], center[1] + halfHeight, center[2])
# Z axis - BLUE
glColor3f(0.0, 0.0, 1.0)
glVertex3f(center[0], center[1], center[2] - halfWidth)
glVertex3f(center[0], center[1], center[2] + halfWidth)
glEnd()
# Draw grid
glColor3f(0.8, 0.8, 0.8)
glBegin(GL_LINES)
for i in range(-self.lines // 2, self.lines // 2 + 1):
# Vertical lines
glVertex3f(center[0] + i * self.cellWidth, center[1] - halfHeight, center[2])
glVertex3f(center[0] + i * self.cellWidth, center[1] + halfHeight, center[2])
# Horizontal lines
glVertex3f(center[0] - halfWidth, center[1] + i * self.cellHeight, center[2])
glVertex3f(center[0] + halfWidth, center[1] + i * self.cellHeight, center[2])
glEnd()

View File

@@ -0,0 +1,83 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton, QHBoxLayout, QMessageBox
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH
class MapInfoPanel(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
# Components
layout = QVBoxLayout()
mapTitleLabel = QLabel("Map Title")
self.mapTitleInput = QLineEdit()
layout.addWidget(mapTitleLabel)
layout.addWidget(self.mapTitleInput)
tilePositionLabel = QLabel("Tile Position")
layout.addWidget(tilePositionLabel)
tilePositionRow = QHBoxLayout()
self.tileXInput = QLineEdit()
self.tileXInput.setPlaceholderText("X")
tilePositionRow.addWidget(self.tileXInput)
self.tileYInput = QLineEdit()
self.tileYInput.setPlaceholderText("Y")
tilePositionRow.addWidget(self.tileYInput)
self.tileZInput = QLineEdit()
self.tileZInput.setPlaceholderText("Z")
tilePositionRow.addWidget(self.tileZInput)
self.tileGo = QPushButton("Go")
tilePositionRow.addWidget(self.tileGo)
layout.addLayout(tilePositionRow)
self.chunkPosLabel = QLabel()
layout.addWidget(self.chunkPosLabel)
self.chunkLabel = QLabel()
layout.addWidget(self.chunkLabel)
layout.addStretch()
self.setLayout(layout)
# Events
self.tileGo.clicked.connect(self.goToPosition)
self.tileXInput.returnPressed.connect(self.goToPosition)
self.tileYInput.returnPressed.connect(self.goToPosition)
self.tileZInput.returnPressed.connect(self.goToPosition)
self.parent.map.onPositionChange.sub(self.updatePositionLabels)
self.parent.map.onMapData.sub(self.onMapData)
self.mapTitleInput.textChanged.connect(self.onMapNameChange)
# Initial label setting
self.updatePositionLabels(self.parent.map.position)
def goToPosition(self):
try:
x = int(self.tileXInput.text())
y = int(self.tileYInput.text())
z = int(self.tileZInput.text())
self.parent.map.moveTo(x, y, z)
except ValueError:
QMessageBox.warning(self, "Invalid Input", "Please enter valid integer coordinates.")
def updatePositionLabels(self, pos):
self.tileXInput.setText(str(pos[0]))
self.tileYInput.setText(str(pos[1]))
self.tileZInput.setText(str(pos[2]))
chunkTileX = pos[0] % CHUNK_WIDTH
chunkTileY = pos[1] % CHUNK_HEIGHT
chunkTileZ = pos[2] % CHUNK_DEPTH
chunkTileIndex = chunkTileX + chunkTileY * CHUNK_WIDTH + chunkTileZ * CHUNK_WIDTH * CHUNK_HEIGHT
self.chunkPosLabel.setText(f"Chunk Position: {chunkTileX}, {chunkTileY}, {chunkTileZ} ({chunkTileIndex})")
chunkX = pos[0] // CHUNK_WIDTH
chunkY = pos[1] // CHUNK_HEIGHT
chunkZ = pos[2] // CHUNK_DEPTH
self.chunkLabel.setText(f"Chunk: {chunkX}, {chunkY}, {chunkZ}")
def onMapData(self, data):
self.updatePositionLabels(self.parent.map.position)
self.mapTitleInput.setText(data.get("mapName", ""))
def onMapNameChange(self, text):
self.parent.map.data['mapName'] = text

View File

@@ -0,0 +1,51 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QGridLayout, QPushButton, QTabWidget, QLabel
from tools.editor.map.chunkpanel import ChunkPanel
from tools.editor.map.entitypanel import EntityPanel
from tools.editor.map.regionpanel import RegionPanel
class MapLeftPanel(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
layout = QVBoxLayout(self)
self.setLayout(layout)
# Nav buttons
self.chunkInfoLabel = QLabel("Tile Information")
layout.addWidget(self.chunkInfoLabel)
grid = QGridLayout()
self.btnUp = QPushButton("U")
self.btnN = QPushButton("N")
self.btnDown = QPushButton("D")
self.btnW = QPushButton("W")
self.btnS = QPushButton("S")
self.btnE = QPushButton("E")
grid.addWidget(self.btnUp, 0, 0)
grid.addWidget(self.btnN, 0, 1)
grid.addWidget(self.btnDown, 0, 2)
grid.addWidget(self.btnW, 1, 0)
grid.addWidget(self.btnS, 1, 1)
grid.addWidget(self.btnE, 1, 2)
layout.addLayout(grid)
# Panels
self.chunkPanel = ChunkPanel(self.parent)
self.entityPanel = EntityPanel(self.parent)
self.regionPanel = RegionPanel(self.parent)
# Tabs
self.tabs = QTabWidget()
self.tabs.addTab(self.chunkPanel, "Tiles")
self.tabs.addTab(self.entityPanel, "Entities")
self.tabs.addTab(self.regionPanel, "Regions")
self.tabs.addTab(None, "Triggers")
layout.addWidget(self.tabs)
# Event subscriptions
self.btnN.clicked.connect(lambda: self.parent.map.moveRelative(0, -1, 0))
self.btnS.clicked.connect(lambda: self.parent.map.moveRelative(0, 1, 0))
self.btnE.clicked.connect(lambda: self.parent.map.moveRelative(1, 0, 0))
self.btnW.clicked.connect(lambda: self.parent.map.moveRelative(-1, 0, 0))
self.btnUp.clicked.connect(lambda: self.parent.map.moveRelative(0, 0, 1))
self.btnDown.clicked.connect(lambda: self.parent.map.moveRelative(0, 0, -1))

View File

@@ -0,0 +1,46 @@
import os
from PyQt5.QtWidgets import QAction, QMenuBar, QFileDialog
from PyQt5.QtGui import QKeySequence
from tools.dusk.map import MAP_DEFAULT_PATH
class MapMenubar:
def __init__(self, parent):
self.parent = parent
self.menubar = QMenuBar(parent)
parent.setMenuBar(self.menubar)
self.fileMenu = self.menubar.addMenu("File")
self.actionNew = QAction("New", parent)
self.actionOpen = QAction("Open", parent)
self.actionSave = QAction("Save", parent)
self.actionSaveAs = QAction("Save As", parent)
self.actionNew.setShortcut(QKeySequence("Ctrl+N"))
self.actionOpen.setShortcut(QKeySequence("Ctrl+O"))
self.actionSave.setShortcut(QKeySequence("Ctrl+S"))
self.actionSaveAs.setShortcut(QKeySequence("Ctrl+Shift+S"))
self.actionNew.triggered.connect(self.newFile)
self.actionOpen.triggered.connect(self.openFile)
self.actionSave.triggered.connect(self.saveFile)
self.actionSaveAs.triggered.connect(self.saveAsFile)
self.fileMenu.addAction(self.actionNew)
self.fileMenu.addAction(self.actionOpen)
self.fileMenu.addAction(self.actionSave)
self.fileMenu.addAction(self.actionSaveAs)
def newFile(self):
self.parent.map.newFile()
def openFile(self):
filePath, _ = QFileDialog.getOpenFileName(self.menubar, "Open Map File", MAP_DEFAULT_PATH, "Map Files (*.json)")
if filePath:
self.parent.map.load(filePath)
def saveFile(self):
self.parent.map.save()
def saveAsFile(self):
filePath, _ = QFileDialog.getSaveFileName(self.menubar, "Save Map File As", MAP_DEFAULT_PATH, "Map Files (*.json)")
if filePath:
self.parent.map.save(filePath)

View File

@@ -0,0 +1,15 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QComboBox, QHBoxLayout, QPushButton, QLineEdit, QListWidget, QListWidgetItem
from PyQt5.QtCore import Qt
from tools.dusk.entity import Entity
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, ENTITY_TYPES, ENTITY_TYPE_NULL
class RegionPanel(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
layout = QVBoxLayout(self)
self.setLayout(layout)
# Top panel placeholder
topWidget = QLabel("Region Editor")
layout.addWidget(topWidget)

View File

@@ -0,0 +1,44 @@
import OpenGL.GL as gl
from tools.dusk.defs import defs
import colorsys
from tools.dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
class SelectBox:
def __init__(self, parent):
self.parent = parent
self.hue = 0.0
def draw(self):
position = [
self.parent.map.position[0] * TILE_WIDTH - (TILE_WIDTH / 2.0),
self.parent.map.position[1] * TILE_HEIGHT - (TILE_HEIGHT / 2.0),
self.parent.map.position[2] * TILE_DEPTH - (TILE_DEPTH / 2.0)
]
vertices = [
(position[0], position[1], position[2]),
(position[0]+TILE_WIDTH, position[1], position[2]),
(position[0]+TILE_WIDTH, position[1]+TILE_HEIGHT, position[2]),
(position[0], position[1]+TILE_HEIGHT, position[2]),
(position[0], position[1], position[2]+TILE_DEPTH),
(position[0]+TILE_WIDTH, position[1], position[2]+TILE_DEPTH),
(position[0]+TILE_WIDTH, position[1]+TILE_HEIGHT, position[2]+TILE_DEPTH),
(position[0], position[1]+TILE_HEIGHT, position[2]+TILE_DEPTH)
]
edges = [
(0, 1), (1, 2), (2, 3), (3, 0), # bottom face
(4, 5), (5, 6), (6, 7), (7, 4), # top face
(4, 5), (5, 6), (6, 7), (7, 4), # top face
(0, 4), (1, 5), (2, 6), (3, 7) # vertical edges
]
# Cycle hue
self.hue = (self.hue + 0.01) % 1.0
r, g, b = colorsys.hsv_to_rgb(self.hue, 1.0, 1.0)
gl.glColor3f(r, g, b)
gl.glLineWidth(2.0)
gl.glBegin(gl.GL_LINES)
for edge in edges:
for vertex in edge:
gl.glVertex3f(*vertices[vertex])
gl.glEnd()

View File

@@ -0,0 +1,18 @@
from PyQt5.QtWidgets import QStatusBar, QLabel
class StatusBar(QStatusBar):
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent
self.leftLabel = QLabel("")
self.rightLabel = QLabel("")
self.addWidget(self.leftLabel, 1)
self.addPermanentWidget(self.rightLabel)
parent.map.onMapData.sub(self.onMapData)
def setStatus(self, message):
self.leftLabel.setText(message)
def onMapData(self, data):
self.rightLabel.setText(self.parent.map.mapFileName if self.parent.map.mapFileName else "Untitled.json")

View File

@@ -0,0 +1,80 @@
from OpenGL.GL import *
import array
class VertexBuffer:
def __init__(self, componentsPerVertex=3):
self.componentsPerVertex = componentsPerVertex
self.vertices = []
self.colors = []
self.uvs = []
self.data = None
self.colorData = None
self.uvData = None
def buildData(self):
hasColors = len(self.colors) > 0
hasUvs = len(self.uvs) > 0
vertexCount = len(self.vertices) // self.componentsPerVertex
dataList = []
colorList = []
uvList = []
for i in range(vertexCount):
vStart = i * self.componentsPerVertex
dataList.extend(self.vertices[vStart:vStart+self.componentsPerVertex])
if hasColors:
cStart = i * 4 # Assuming RGBA
colorList.extend(self.colors[cStart:cStart+4])
if hasUvs:
uStart = i * 2 # Assuming UV
uvList.extend(self.uvs[uStart:uStart+2])
self.data = array.array('f', dataList)
if hasColors:
self.colorData = array.array('f', colorList)
else:
self.colorData = None
if hasUvs:
self.uvData = array.array('f', uvList)
else:
self.uvData = None
def draw(self, mode=GL_TRIANGLES, count=-1):
if count == -1:
count = len(self.data) // self.componentsPerVertex
if count == 0:
return
glEnableClientState(GL_VERTEX_ARRAY)
glVertexPointer(self.componentsPerVertex, GL_FLOAT, 0, self.data.tobytes())
if self.colorData:
glEnableClientState(GL_COLOR_ARRAY)
glColorPointer(4, GL_FLOAT, 0, self.colorData.tobytes())
if self.uvData:
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
glTexCoordPointer(2, GL_FLOAT, 0, self.uvData.tobytes())
glDrawArrays(mode, 0, count)
glDisableClientState(GL_VERTEX_ARRAY)
if self.colorData:
glDisableClientState(GL_COLOR_ARRAY)
if self.uvData:
glDisableClientState(GL_TEXTURE_COORD_ARRAY)
def clear(self):
self.vertices = []
self.colors = []
self.uvs = []
self.data = array.array('f')
self.colorData = None
self.uvData = None

143
archive/editor/maptool.py Normal file
View File

@@ -0,0 +1,143 @@
import os
from PyQt5.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QMessageBox
from PyQt5.QtCore import Qt
from tools.editor.map.glwidget import GLWidget
from tools.editor.map.mapleftpanel import MapLeftPanel
from tools.editor.map.mapinfopanel import MapInfoPanel
from tools.editor.map.menubar import MapMenubar
from tools.editor.map.statusbar import StatusBar
from tools.dusk.map import Map
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_SHAPE_NULL, TILE_SHAPE_FLOOR
from tools.editor.map.selectbox import SelectBox
from tools.editor.map.camera import Camera
from tools.editor.map.grid import Grid
class MapWindow(QMainWindow):
def __init__(self):
super().__init__()
self.insertPressed = False
self.deletePressed = False
# Subclasses
self.map = Map(self)
self.camera = Camera(self)
self.grid = Grid()
self.selectBox = SelectBox(self)
# Window setup
self.setWindowTitle("Dusk Map Editor")
self.resize(1600, 900)
# Menubar (TESTING)
self.menubar = MapMenubar(self)
central = QWidget()
self.setCentralWidget(central)
mainLayout = QHBoxLayout(central)
# Left panel (tabs + nav buttons)
self.leftPanel = MapLeftPanel(self)
self.leftPanel.setFixedWidth(350)
mainLayout.addWidget(self.leftPanel)
# Center panel (GLWidget + controls)
self.glWidget = GLWidget(self)
mainLayout.addWidget(self.glWidget, stretch=3)
# Right panel (MapInfoPanel)
self.mapInfoPanel = MapInfoPanel(self)
rightWidget = self.mapInfoPanel
rightWidget.setFixedWidth(250)
mainLayout.addWidget(rightWidget)
# Status bar
self.statusBar = StatusBar(self)
self.setStatusBar(self.statusBar)
self.installEventFilter(self)
self.installEventFilterRecursively(self)
def installEventFilterRecursively(self, widget):
for child in widget.findChildren(QWidget):
child.installEventFilter(self)
self.installEventFilterRecursively(child)
def closeEvent(self, event):
if not self.map.isDirty():
event.accept()
return
reply = QMessageBox.question(
self,
"Unsaved Changes",
"You have unsaved changes. Do you want to save before closing?",
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel,
QMessageBox.Save
)
if reply == QMessageBox.Save:
self.map.save()
elif reply == QMessageBox.Cancel:
event.ignore()
return
event.accept()
def eventFilter(self, obj, event):
if event.type() == event.KeyPress:
amtX, amtY, amtZ = 0, 0, 0
key = event.key()
if key == Qt.Key_Left:
amtX = -1
elif key == Qt.Key_Right:
amtX = 1
elif key == Qt.Key_Up:
amtY = -1
elif key == Qt.Key_Down:
amtY = 1
elif key == Qt.Key_PageUp:
amtZ = 1
elif key == Qt.Key_PageDown:
amtZ = -1
if event.modifiers() & Qt.ShiftModifier:
amtX *= CHUNK_WIDTH
amtY *= CHUNK_HEIGHT
amtZ *= CHUNK_DEPTH
if amtX != 0 or amtY != 0 or amtZ != 0:
self.map.moveRelative(amtX, amtY, amtZ)
if self.insertPressed:
tile = self.map.getTileAtWorldPos(*self.map.position)
if tile is not None and tile.shape == TILE_SHAPE_NULL:
tile.setShape(TILE_SHAPE_FLOOR)
if self.deletePressed:
tile = self.map.getTileAtWorldPos(*self.map.position)
if tile is not None:
tile.setShape(TILE_SHAPE_NULL)
event.accept()
return True
if key == Qt.Key_Delete:
tile = self.map.getTileAtWorldPos(*self.map.position)
self.deletePressed = True
if tile is not None:
tile.setShape(TILE_SHAPE_NULL)
event.accept()
return True
if key == Qt.Key_Insert:
tile = self.map.getTileAtWorldPos(*self.map.position)
self.insertPressed = True
if tile is not None and tile.shape == TILE_SHAPE_NULL:
tile.setShape(TILE_SHAPE_FLOOR)
event.accept()
elif event.type() == event.KeyRelease:
key = event.key()
if key == Qt.Key_Delete:
self.deletePressed = False
event.accept()
return True
if key == Qt.Key_Insert:
self.insertPressed = False
event.accept()
return super().eventFilter(obj, event)

View File

@@ -1,257 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "fixed.h"
#include "assert/assert.h"
float_t fx248Fromi32(const int32_t b) {
return (float_t)b << FIXED248_FRACTION_BITS;
}
float_t fx248Fromu32(const uint32_t b) {
return (float_t)((int32_t)b << FIXED248_FRACTION_BITS);
}
float_t fx248Fromf32(const float_t b) {
return (float_t)(b * (1 << FIXED248_FRACTION_BITS));
}
float_t fx248Fromu16(const uint16_t b) {
return (float_t)((int32_t)b << FIXED248_FRACTION_BITS);
}
float_t fx248Fromu8(const uint8_t b) {
return (float_t)((int32_t)b << FIXED248_FRACTION_BITS);
}
int32_t fx248Toi32(const float_t a) {
return a >> FIXED248_FRACTION_BITS;
}
uint32_t fx248Tou32(const float_t a) {
return (uint32_t)(a >> FIXED248_FRACTION_BITS);
}
float_t fx248Tof32(const float_t a) {
return (float_t)a / (1 << FIXED248_FRACTION_BITS);
}
uint16_t fx248Tou16(const float_t a) {
return (uint16_t)(a >> FIXED248_FRACTION_BITS);
}
uint8_t fx248Tou8(const float_t a) {
return (uint8_t)(a >> FIXED248_FRACTION_BITS);
}
float_t fx248Addfx248(const float_t a, const float_t b) {
return a + b;
}
float_t fx248Addi32(const float_t a, const int32_t b) {
return fx248Addfx248(a, fx248Fromi32(b));
}
float_t fx248Addu32(const float_t a, const uint32_t b) {
return fx248Addfx248(a, fx248Fromu32(b));
}
float_t fx248Addf32(const float_t a, const float_t b) {
return fx248Addfx248(a, fx248Fromf32(b));
}
float_t fx248Subfx248(const float_t a, const float_t b) {
return a - b;
}
float_t fx248Subi32(const float_t a, const int32_t b) {
return fx248Subfx248(a, fx248Fromi32(b));
}
float_t fx248Subu32(const float_t a, const uint32_t b) {
return fx248Subfx248(a, fx248Fromu32(b));
}
float_t fx248Subf32(const float_t a, const float_t b) {
return fx248Subfx248(a, fx248Fromf32(b));
}
float_t fx248Mulfx248(const float_t a, const float_t b) {
return (float_t)(((int64_t)a * (int64_t)b) >> FIXED248_FRACTION_BITS);
}
float_t fx248Muli32(const float_t a, const int32_t b) {
return (float_t)(((int64_t)a * (int64_t)b) >> FIXED248_FRACTION_BITS);
}
float_t fx248Mulu32(const float_t a, const uint32_t b) {
return (float_t)(
((int64_t)a * (int64_t)(int32_t)b
) >> FIXED248_FRACTION_BITS);
}
float_t fx248Mulf32(const float_t a, const float_t b) {
return (float_t)((
(int64_t)a * (int64_t)(b * (1 << FIXED248_FRACTION_BITS))
) >> FIXED248_FRACTION_BITS);
}
float_t fx248Divfx248(const float_t a, const float_t b) {
assertFalse(b == 0, "Division by zero in fx248Divfx248");
return (float_t)(((int64_t)a << FIXED248_FRACTION_BITS) / (int64_t)b);
}
float_t fx248Divi32(const float_t a, const int32_t b) {
assertFalse(b == 0, "Division by zero in fx248Divi32");
return (float_t)(((int64_t)a << FIXED248_FRACTION_BITS) / (int64_t)b);
}
float_t fx248Divu32(const float_t a, const uint32_t b) {
assertFalse(b == 0, "Division by zero in fx248Divu32");
return (float_t)(
((int64_t)a << FIXED248_FRACTION_BITS
) / (int64_t)(int32_t)b);
}
float_t fx248Divf32(const float_t a, const float_t b) {
assertFalse(b == 0, "Division by zero in fx248Divf32");
return (float_t)((
(int64_t)a << FIXED248_FRACTION_BITS
) / (int64_t)(b * (1 << FIXED248_FRACTION_BITS)));
}
float_t fx248Floor(const float_t a) {
return a & ~((1 << FIXED248_FRACTION_BITS) - 1);
}
float_t fx248Ceil(const float_t a) {
if(a & ((1 << FIXED248_FRACTION_BITS) - 1)) {
return (a & ~((1 << FIXED248_FRACTION_BITS) - 1)) + (1 << FIXED248_FRACTION_BITS);
}
return a;
}
float_t fx248Round(const float_t a) {
if(a & ((1 << (FIXED248_FRACTION_BITS - 1)) - 1)) {
return (a & ~((1 << FIXED248_FRACTION_BITS) - 1)) + (1 << FIXED248_FRACTION_BITS);
}
return a & ~((1 << FIXED248_FRACTION_BITS) - 1);
}
uint32_t fx248Flooru32(const float_t a) {
return (uint32_t)((a >> FIXED248_FRACTION_BITS) & 0xFFFFFFFF);
}
uint32_t fx248Ceilu32(const float_t a) {
return (uint32_t)(((a + ((1 << FIXED248_FRACTION_BITS) - 1)) >> FIXED248_FRACTION_BITS) & 0xFFFFFFFF);
}
uint32_t fx248Roundu32(const float_t a) {
return (uint32_t)(((a + (1 << (FIXED248_FRACTION_BITS - 1))) >> FIXED248_FRACTION_BITS) & 0xFFFFFFFF);
}
float_t fx248Sqrt(const float_t a) {
if(a == 0) return 0;
float_t y = a > FIXED248(1, 0) ? a : FIXED248(1, 0);
float_t last = 0;
int max_iter = 16;
while(y != last && max_iter-- > 0) {
last = y;
int32_t div = (int32_t)(((int64_t)a << FIXED248_FRACTION_BITS) / y);
y = (y + div) >> 1;
}
return y;
}
float_t fx248Max(const float_t a, const float_t b) {
return (a > b) ? a : b;
}
float_t fx248Min(const float_t a, const float_t b) {
return (a < b) ? a : b;
}
float_t fx248Clamp(
const float_t a,
const float_t min,
const float_t max
) {
return (a < min) ? min : (a > max) ? max : a;
}
float_t fx248Abs(const float_t a) {
return (a < 0) ? -a : a;
}
float_t fx248Atan2(
const float_t y,
const float_t x
) {
// Handle special cases
if (x == 0) {
if (y > 0) return FX248_HALF_PI;
if (y < 0) return -FX248_HALF_PI;
return 0;
}
// Use absolute values for quadrant correction
float_t abs_y = y;
if (abs_y < 0) abs_y = -abs_y;
float_t angle;
if (abs_y < fx248Abs(x)) {
float_t z = fx248Divfx248(y, x);
float_t z2 = fx248Mulfx248(z, z);
float_t z3 = fx248Mulfx248(z2, z);
float_t z5 = fx248Mulfx248(z3, z2);
angle = fx248Subfx248(
fx248Addfx248(z, fx248Divfx248(z5, fx248Fromi32(5))),
fx248Divfx248(z3, fx248Fromi32(3))
);
if (x < 0) {
if (y < 0) {
angle -= FX248_PI;
} else {
angle += FX248_PI;
}
}
} else {
float_t z = fx248Divfx248(x, y);
float_t z2 = fx248Mulfx248(z, z);
float_t z3 = fx248Mulfx248(z2, z);
float_t z5 = fx248Mulfx248(z3, z2);
angle = fx248Subfx248(
fx248Addfx248(z, fx248Divfx248(z5, fx248Fromi32(5))),
fx248Divfx248(z3, fx248Fromi32(3))
);
if (y > 0) {
angle = FX248_HALF_PI - angle;
} else {
angle = -FX248_HALF_PI - angle;
}
}
return angle;
}

View File

@@ -1,379 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef int32_t float_t;
#define FIXED248_FRACTION_BITS 8
#define FIXED248_HIGH_MULTIPLIER (1 << FIXED248_FRACTION_BITS)
#define FIXED248_MIN INT32_MIN
#define FIXED248_MAX INT32_MAX
#define FIXED248(i, f) ((float_t)( \
((i) << FIXED248_FRACTION_BITS) + \
(((f) * FIXED248_HIGH_MULTIPLIER) / 100) \
))
#define FIXED248_ONE (FIXED248(1, 0))
#define FIXED248_ZERO (FIXED248(0, 0))
#define FX248_PI 804
#define FX248_HALF_PI 402
#define FX248_3PI_4 603
#define FX248_NEG_PI -804
/**
* Convert an int32_t value to a float_t value.
*
* @param b The int32_t value to convert.
* @return The converted float_t value.
*/
float_t fx248Fromi32(const int32_t b);
/**
* Convert a uint32_t value to a float_t value.
*
* @param b The uint32_t value to convert.
* @return The converted float_t value.
*/
float_t fx248Fromu32(const uint32_t b);
/**
* Convert a float_t value to a float_t value.
*
* @param b The float_t value to convert.
* @return The converted float_t value.
*/
float_t fx248Fromf32(const float_t b);
/**
* Convert a uint16_t value to a float_t value.
*
* @param b The uint16_t value to convert.
* @return The converted float_t value.
*/
float_t fx248Fromu16(const uint16_t b);
/**
* Convert a uint8_t value to a float_t value.
*
* @param b The uint8_t value to convert.
* @return The converted float_t value.
*/
float_t fx248Fromu8(const uint8_t b);
/**
* Convert a float_t value to an int32_t value.
*
* @param a The float_t value to convert.
* @return The converted int32_t value.
*/
int32_t fx248Toi32(const float_t a);
/**
* Convert a float_t value to a uint32_t value.
*
* @param a The float_t value to convert.
* @return The converted uint32_t value.
*/
uint32_t fx248Tou32(const float_t a);
/**
* Convert a float_t value to a float_t value.
*
* @param a The float_t value to convert.
* @return The converted float_t value.
*/
float_t fx248Tof32(const float_t a);
/**
* Convert a float_t value to a uint16_t value.
*
* @param a The float_t value to convert.
* @return The converted uint16_t value.
*/
uint16_t fx248Tou16(const float_t a);
/**
* Convert a float_t value to an uint8_t value.
*
* @param a The float_t value to convert.
* @return The converted uint8_t value.
*/
uint8_t fx248Tou8(const float_t a);
/**
* Add a float_t value to another float_t value.
*
* @param a First float_t value.
* @param b Second float_t value to add to the first value.
* @return The result of the addition as a float_t value.
*/
float_t fx248Addfx248(const float_t a, const float_t b);
/**
* Add an int32_t value to a float_t value.
*
* @param a The float_t value to which the int32_t will be added.
* @param b The int32_t value to add to the float_t value.
* @return The result of the addition as a float_t value.
*/
float_t fx248Addi32(const float_t a, const int32_t b);
/**
* Add a uint32_t value to a float_t value.
*
* @param a The float_t value to which the uint32_t will be added.
* @param b The uint32_t value to add to the float_t value.
* @return The result of the addition as a float_t value.
*/
float_t fx248Addu32(const float_t a, const uint32_t b);
/**
* Add a float_t value to a float_t value.
*
* @param a Pointer to the float_t value (will be modified).
* @param b The float_t value to add to the float_t value.
* @return The result of the addition as a float_t value.
*/
float_t fx248Addf32(const float_t a, const float_t b);
/**
* Subtract a float_t value from another float_t value.
*
* @param a First float_t value.
* @param b The float_t value to subtract from the first value.
* @return The result of the subtraction as a float_t value.
*/
float_t fx248Subfx248(const float_t a, const float_t b);
/**
* Subtract an int32_t value from a float_t value.
*
* @param a The float_t value from which the int32_t will be subtracted.
* @param b The int32_t value to subtract from the float_t value.
* @return The result of the subtraction as a float_t value.
*/
float_t fx248Subi32(const float_t a, const int32_t b);
/**
* Subtract a uint32_t value from a float_t value.
*
* @param a The float_t value from which the uint32_t will be subtracted.
* @param b The uint32_t value to subtract from the float_t value.
* @return The result of the subtraction as a float_t value.
*/
float_t fx248Subu32(const float_t a, const uint32_t b);
/**
* Subtract a float_t value from a float_t value.
*
* @param a The float_t value from which the float_t will be subtracted.
* @param b The float_t value to subtract from the float_t value.
* @return The result of the subtraction as a float_t value.
*/
float_t fx248Subf32(const float_t a, const float_t b);
/**
* Multiply two float_t values.
*
* @param a First float_t value.
* @param b Second float_t value to multiply with the first value.
* @return The result of the multiplication as a float_t value.
*/
float_t fx248Mulfx248(const float_t a, const float_t b);
/**
* Multiply a float_t value by an int32_t value.
*
* @param a The float_t value to multiply.
* @param b The int32_t value to multiply with the float_t value.
* @return The result of the multiplication as a float_t value.
*/
float_t fx248Muli32(const float_t a, const int32_t b);
/**
* Multiply a float_t value by a uint32_t value.
*
* @param a The float_t value to multiply.
* @param b The uint32_t value to multiply with the float_t value.
* @return The result of the multiplication as a float_t value.
*/
float_t fx248Mulu32(const float_t a, const uint32_t b);
/**
* Multiply a float_t value by a float_t value.
*
* @param a The float_t value to multiply.
* @param b The float_t value to multiply with the float_t value.
* @return The result of the multiplication as a float_t value.
*/
float_t fx248Mulf32(const float_t a, const float_t b);
/**
* Divide two float_t values.
*
* @param a The float_t value to be divided.
* @param b The float_t value to divide by.
* @return The result of the division as a float_t value.
*/
float_t fx248Divfx248(const float_t a, const float_t b);
/**
* Divide a float_t value by an int32_t value.
*
* @param a The float_t value to be divided.
* @param b The int32_t value to divide by.
* @return The result of the division as a float_t value.
*/
float_t fx248Divi32(const float_t a, const int32_t b);
/**
* Divide a float_t value by a uint32_t value.
*
* @param a The float_t value to be divided.
* @param b The uint32_t value to divide by.
* @return The result of the division as a float_t value.
*/
float_t fx248Divu32(const float_t a, const uint32_t b);
/**
* Divide a float_t value by a float_t value.
*
* @param a The float_t value to be divided.
* @param b The float_t value to divide by.
* @return The result of the division as a float_t value.
*/
float_t fx248Divf32(const float_t a, const float_t b);
/**
* Convert a float_t value to an int32_t value, rounding towards zero.
*
* @param a The float_t value to convert.
* @return The converted int32_t value.
*/
float_t fx248Floor(const float_t a);
/**
* Convert a float_t value to an int32_t value, rounding towards positive
* infinity.
*
* @param a The float_t value to convert.
* @return The converted int32_t value.
*/
float_t fx248Ceil(const float_t a);
/**
* Convert a float_t value to an int32_t value, rounding to the nearest
* integer.
*
* @param a The float_t value to convert.
* @return The converted int32_t value.
*/
float_t fx248Round(const float_t a);
/**
* Convert a float_t value to a uint32_t value, rounding towards zero.
*
* @param a The float_t value to convert.
* @return The converted uint32_t value.
*/
uint32_t fx248Flooru32(const float_t a);
/**
* Convert a float_t value to a uint32_t value, rounding towards positive
* infinity.
*
* @param a The float_t value to convert.
* @return The converted uint32_t value.
*/
uint32_t fx248Ceilu32(const float_t a);
/**
* Convert a float_t value to a uint32_t value, rounding to the nearest
* integer.
*
* @param a The float_t value to convert.
* @return The converted uint32_t value.
*/
uint32_t fx248Roundu32(const float_t a);
/**
* Returns the square root of a float_t value.
*
* @param a The float_t value to calculate the square root of.
*/
float_t fx248Sqrt(const float_t a);
/**
* Returns the maximum of two float_t values.
*
* @param a First float_t value.
* @param b Second float_t value.
* @return The maximum of the two values.
*/
float_t fx248Max(const float_t a, const float_t b);
/**
* Returns the minimum of two float_t values.
*
* @param a First float_t value.
* @param b Second float_t value.
* @return The minimum of the two values.
*/
float_t fx248Min(const float_t a, const float_t b);
/**
* Clamp a float_t value between a minimum and maximum value.
*
* @param a The float_t value to clamp.
* @param min The minimum value to clamp to.
* @param max The maximum value to clamp to.
* @return The clamped float_t value.
*/
float_t fx248Clamp(
const float_t a,
const float_t min,
const float_t max
);
/**
* Returns the absolute value of a float_t value.
*
* @param a The float_t value to calculate the absolute value of.
* @return The absolute value as a float_t value.
*/
float_t fx248Abs(const float_t a);
/**
* Calculate the arctangent of a float_t value.
*
* @param y Y coordinate value.
* @param x X coordinate value.
* @return The arctangent of the value as a float_t value.
*/
float_t fx248Atan2(
const float_t y,
const float_t x
);

View File

@@ -0,0 +1,6 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
add_subdirectory(testmap)

View File

@@ -0,0 +1,6 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
add_asset(MAP testmap.json)

View File

@@ -0,0 +1 @@
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}

View File

@@ -0,0 +1 @@
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}

View File

@@ -0,0 +1 @@
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}

View File

@@ -0,0 +1 @@
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}

View File

@@ -0,0 +1 @@
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}

View File

@@ -0,0 +1 @@
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}

View File

@@ -0,0 +1 @@
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}

View File

@@ -0,0 +1 @@
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}

View File

@@ -0,0 +1 @@
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}

View File

@@ -0,0 +1 @@
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}

View File

@@ -0,0 +1 @@
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}

View File

@@ -0,0 +1 @@
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}

View File

@@ -0,0 +1,3 @@
{
"name": "Test"
}

49
archive/modulemap.h Normal file
View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
#include "debug/debug.h"
#include "assert/assert.h"
#include "rpg/overworld/map.h"
int moduleMapLoad(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
if(!lua_isstring(L, 1)) {
luaL_error(L, "Expected string map filename");
return 0;
}
// Potentially provide up to 3 params
chunkpos_t initial = { .x = 0, .y = 0, .z = 0 };
if(lua_isnumber(L, 2)) {
initial.x = (chunkunit_t)lua_tonumber(L, 2);
}
if(lua_isnumber(L, 3)) {
initial.y = (chunkunit_t)lua_tonumber(L, 3);
}
if(lua_isnumber(L, 4)) {
initial.z = (chunkunit_t)lua_tonumber(L, 4);
}
// Load the map.
errorret_t ret = mapLoad(luaL_checkstring(L, 1), initial);
if(ret.code != ERROR_OK) {
luaL_error(L, "Failed to load map");
errorCatch(errorPrint(ret));
return 0;
}
return 0;
}
void moduleMapSystem(scriptcontext_t *context) {
assertNotNull(context, "Script context cannot be NULL");
lua_register(context->luaState, "mapLoad", moduleMapLoad);
}

View File

@@ -0,0 +1,12 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
sdl2.c
psp.c
dolphin.c
)

View File

@@ -0,0 +1,92 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "dolphin.h"
void displayInitDolphin(void) {
VIDEO_Init();
DISPLAY.screenMode = VIDEO_GetPreferredMode(NULL);
DISPLAY.frameBuffer[0] = MEM_K0_TO_K1(
SYS_AllocateFramebuffer(DISPLAY.screenMode)
);
DISPLAY.frameBuffer[1] = MEM_K0_TO_K1(
SYS_AllocateFramebuffer(DISPLAY.screenMode)
);
VIDEO_Configure(DISPLAY.screenMode);
VIDEO_SetNextFramebuffer(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer]);
// VIDEO_SetPostRetraceCallback(copy_buffers);
VIDEO_SetBlack(FALSE);
VIDEO_Flush();
VIDEO_WaitVSync();
if(DISPLAY.screenMode->viTVMode & VI_NON_INTERLACE) VIDEO_WaitVSync();
DISPLAY.fifoBuffer = memalign(32, DISPLAY_FIFO_SIZE);
memoryZero(DISPLAY.fifoBuffer, DISPLAY_FIFO_SIZE);
GX_Init(DISPLAY.fifoBuffer, DISPLAY_FIFO_SIZE);
// This seems to be mostly related to interlacing vs progressive
GX_SetViewport(
0, 0,
DISPLAY.screenMode->fbWidth, DISPLAY.screenMode->efbHeight,
0, 1
);
float_t yscale = GX_GetYScaleFactor(
DISPLAY.screenMode->efbHeight, DISPLAY.screenMode->xfbHeight
);
uint32_t xfbHeight = GX_SetDispCopyYScale(yscale);
GX_SetScissor(
0, 0,
DISPLAY.screenMode->fbWidth, DISPLAY.screenMode->efbHeight
);
GX_SetDispCopySrc(
0, 0,
DISPLAY.screenMode->fbWidth, DISPLAY.screenMode->efbHeight
);
GX_SetDispCopyDst(DISPLAY.screenMode->fbWidth, xfbHeight);
GX_SetCopyFilter(
DISPLAY.screenMode->aa,
DISPLAY.screenMode->sample_pattern,
GX_TRUE,
DISPLAY.screenMode->vfilter
);
GX_SetFieldMode(
DISPLAY.screenMode->field_rendering,
(
(DISPLAY.screenMode->viHeight == 2 * DISPLAY.screenMode->xfbHeight) ?
GX_ENABLE :
GX_DISABLE
)
);
// Setup cull modes
GX_SetCullMode(GX_CULL_NONE);
GX_SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);
GX_CopyDisp(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer], GX_TRUE);
GX_SetDispCopyGamma(GX_GM_1_0);
GX_ClearVtxDesc();
GX_SetVtxDesc(GX_VA_POS, GX_INDEX16);
GX_SetVtxDesc(GX_VA_CLR0, GX_INDEX16);
GX_SetVtxDesc(GX_VA_TEX0, GX_INDEX16);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_U8, 0);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
}
void displayDolphinSwap(void) {
GX_DrawDone();
DISPLAY.whichFrameBuffer ^= 1;
GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
GX_SetColorUpdate(GX_TRUE);
GX_CopyDisp(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer], GX_TRUE);
VIDEO_SetNextFramebuffer(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer]);
VIDEO_Flush();
VIDEO_WaitVSync();
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "display/displaydefs.h"
typedef struct {
void *frameBuffer[2];// Double-Bufferred
int whichFrameBuffer;
GXRModeObj *screenMode;
void *fifoBuffer;
} displaydolphin_t;
/**
* Initializes the display for Dolphin.
*/
void displayDolphinInit(void);
/**
* Swaps the back buffer to the front for Dolphin.
*/
void displayDolphinSwap(void);

12
archive/platform/psp.c Normal file
View File

@@ -0,0 +1,12 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "psp.h"
void displayInitPSP(void) {
DISPLAY.usingShaderedPalettes = false;
}

13
archive/platform/psp.h Normal file
View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
/**
* Initializes the display for PSP.
*/
void displayInitPSP(void);

32
archive/platform/sdl2.c Normal file
View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "sdl2.h"
void displaySDL2Update(void) {
}
void displaySDL2Swap(void) {
SDL_GL_SwapWindow(DISPLAY.window);
GLenum err;
while((err = glGetError()) != GL_NO_ERROR) {
debugPrint("GL Error: %d\n", err);
}
}
void displaySDL2Dispose(void) {
if(DISPLAY.glContext) {
SDL_GL_DeleteContext(DISPLAY.glContext);
DISPLAY.glContext = NULL;
}
if(DISPLAY.window) {
SDL_DestroyWindow(DISPLAY.window);
DISPLAY.window = NULL;
}
SDL_Quit();
}

35
archive/platform/sdl2.h Normal file
View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct {
SDL_Window *window;
SDL_GLContext glContext;
bool_t usingShaderedPalettes;
} displaysdl2_t;
/**
* Initializes the display for SDL2.
*/
void displaySDL2Init(void);
/**
* Updates the display for SDL2.
*/
void displaySDL2Update(void);
/**
* Swaps the display buffers for SDL2.
*/
void displaySDL2Swap(void);
/**
* Disposes of the display for SDL2.
*/
void displaySDL2Dispose(void);

View File

@@ -0,0 +1,17 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
rpg.c
rpgcamera.c
rpgtextbox.c
)
# Subdirs
add_subdirectory(cutscene)
add_subdirectory(entity)
add_subdirectory(overworld)

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
cutscenesystem.c
cutscenemode.c
)
# Subdirs
add_subdirectory(item)

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "rpg/cutscene/item/cutsceneitem.h"
typedef struct cutscene_s {
const cutsceneitem_t *items;
uint8_t itemCount;
} cutscene_t;

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "rpg/cutscene/cutscenesystem.h"
bool_t cutsceneModeIsInputAllowed() {
switch(CUTSCENE_SYSTEM.mode) {
case CUTSCENE_MODE_FULL_FREEZE:
case CUTSCENE_MODE_INPUT_FREEZE:
return false;
default:
return true;
}
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef enum {
CUTSCENE_MODE_NONE,
CUTSCENE_MODE_FULL_FREEZE,
CUTSCENE_MODE_INPUT_FREEZE,
CUTSCENE_MODE_GAMEPLAY
} cutscenemode_t;
// Default mode for all cutscenes.
#define CUTSCENE_MODE_INITIAL CUTSCENE_MODE_INPUT_FREEZE
/**
* Check if input is allowed in the current cutscene mode.
*
* @return true if input is allowed, false otherwise.
*/
bool_t cutsceneModeIsInputAllowed();

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "cutscenesystem.h"
#include "util/memory.h"
cutscenesystem_t CUTSCENE_SYSTEM;
void cutsceneSystemInit() {
memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t));
}
void cutsceneSystemStartCutscene(const cutscene_t *cutscene) {
CUTSCENE_SYSTEM.scene = cutscene;
CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_INITIAL;
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so start wraps.
cutsceneSystemNext();
}
void cutsceneSystemUpdate() {
if(CUTSCENE_SYSTEM.scene == NULL) return;
const cutsceneitem_t *item = cutsceneSystemGetCurrentItem();
cutsceneItemUpdate(item, &CUTSCENE_SYSTEM.data);
}
void cutsceneSystemNext() {
if(CUTSCENE_SYSTEM.scene == NULL) return;
CUTSCENE_SYSTEM.currentItem++;
// End of the cutscene?
if(
CUTSCENE_SYSTEM.currentItem >= CUTSCENE_SYSTEM.scene->itemCount
) {
CUTSCENE_SYSTEM.scene = NULL;
CUTSCENE_SYSTEM.currentItem = 0xFF;
CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_NONE;
return;
}
// Start item.
const cutsceneitem_t *item = cutsceneSystemGetCurrentItem();
memset(&CUTSCENE_SYSTEM.data, 0, sizeof(CUTSCENE_SYSTEM.data));
cutsceneItemStart(item, &CUTSCENE_SYSTEM.data);
}
const cutsceneitem_t * cutsceneSystemGetCurrentItem() {
if(CUTSCENE_SYSTEM.scene == NULL) return NULL;
return &CUTSCENE_SYSTEM.scene->items[CUTSCENE_SYSTEM.currentItem];
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "cutscene.h"
#include "cutscenemode.h"
typedef struct {
const cutscene_t *scene;
uint8_t currentItem;
// Data (used by the current item).
cutsceneitemdata_t data;
cutscenemode_t mode;
} cutscenesystem_t;
extern cutscenesystem_t CUTSCENE_SYSTEM;
/**
* Initialize the cutscene system.
*/
void cutsceneSystemInit();
/**
* Start a cutscene.
*
* @param cutscene Pointer to the cutscene to start.
*/
void cutsceneSystemStartCutscene(const cutscene_t *cutscene);
/**
* Advance to the next item in the cutscene.
*/
void cutsceneSystemNext();
/**
* Update the cutscene system for one frame.
*/
void cutsceneSystemUpdate();
/**
* Get the current cutscene item.
*
* @return Pointer to the current cutscene item.
*/
const cutsceneitem_t * cutsceneSystemGetCurrentItem();

View File

@@ -4,8 +4,7 @@
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
chunk.c
overworld.c
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
cutsceneitem.c
)

View File

@@ -6,4 +6,6 @@
*/
#pragma once
#include "ui/fontdata.h"
#include "dusk.h"
typedef void (*cutscenecallback_t)(void);

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "rpg/cutscene/cutscenesystem.h"
#include "input/input.h"
#include "time/time.h"
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
switch(item->type) {
case CUTSCENE_ITEM_TYPE_TEXT: {
rpgTextboxShow(
item->text.position,
item->text.text
);
break;
}
case CUTSCENE_ITEM_TYPE_WAIT:
data->wait = item->wait;
break;
case CUTSCENE_ITEM_TYPE_CALLBACK:
if(item->callback != NULL) item->callback();
break;
case CUTSCENE_ITEM_TYPE_CUTSCENE:
if(item->cutscene != NULL) cutsceneSystemStartCutscene(item->cutscene);
break;
default:
break;
}
}
void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
switch(item->type) {
case CUTSCENE_ITEM_TYPE_TEXT:
if(rpgTextboxIsVisible()) return;
cutsceneSystemNext();
break;
case CUTSCENE_ITEM_TYPE_WAIT:
data->wait -= TIME.delta;
if(data->wait <= 0) cutsceneSystemNext();
break;
default:
break;
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "cutscenewait.h"
#include "cutscenecallback.h"
#include "cutscenetext.h"
typedef struct cutscene_s cutscene_t;
typedef enum {
CUTSCENE_ITEM_TYPE_NULL,
CUTSCENE_ITEM_TYPE_TEXT,
CUTSCENE_ITEM_TYPE_CALLBACK,
CUTSCENE_ITEM_TYPE_WAIT,
CUTSCENE_ITEM_TYPE_CUTSCENE
} cutsceneitemtype_t;
typedef struct cutsceneitem_s {
cutsceneitemtype_t type;
// Arguments/Data that will be used when this item is invoked.
union {
cutscenetext_t text;
cutscenecallback_t callback;
cutscenewait_t wait;
const cutscene_t *cutscene;
};
} cutsceneitem_t;
typedef union {
cutscenewaitdata_t wait;
} cutsceneitemdata_t;
/**
* Start the given cutscene item.
*
* @param item The cutscene item to start.
* @param data The cutscene item data storage.
*/
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data);
/**
* Tick the given cutscene item (one frame).
*
* @param item The cutscene item to tick.
* @param data The cutscene item data storage.
*/
void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data);

View File

@@ -6,9 +6,9 @@
*/
#pragma once
#include "eventitem.h"
#include "rpg/rpgtextbox.h"
typedef struct {
uint8_t itemCount;
eventitem_t items[EVENT_ITEM_COUNT_MAX];
} eventdata_t;
char_t text[RPG_TEXTBOX_MAX_CHARS];
rpgtextboxpos_t position;
} cutscenetext_t;

View File

@@ -7,7 +7,6 @@
#pragma once
#include "dusk.h"
#include <pspkernel.h>
#include <pspdebug.h>
#include <pspdisplay.h>
#include <pspctrl.h>
typedef float_t cutscenewait_t;
typedef float_t cutscenewaitdata_t;

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "rpg/cutscene/cutscenesystem.h"
static const cutsceneitem_t TEST_CUTSCENE_ONE_ITEMS[] = {
{ .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = "This is a test cutscene.", .position = RPG_TEXTBOX_POS_BOTTOM } },
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 2.0f },
{ .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = "It has multiple lines of text.\nAnd waits in between.", .position = RPG_TEXTBOX_POS_TOP } },
};
static const cutscene_t TEST_CUTSCENE_ONE = {
.items = TEST_CUTSCENE_ONE_ITEMS,
.itemCount = sizeof(TEST_CUTSCENE_ONE_ITEMS) / sizeof(cutsceneitem_t)
};
static const cutsceneitem_t TEST_CUTSCENE_TWO_ITEMS[] = {
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f },
{ .type = CUTSCENE_ITEM_TYPE_CUTSCENE, .cutscene = &TEST_CUTSCENE_ONE },
};
static const cutscene_t TEST_CUTSCENE = {
.items = TEST_CUTSCENE_TWO_ITEMS,
.itemCount = sizeof(TEST_CUTSCENE_TWO_ITEMS) / sizeof(cutsceneitem_t)
};

View File

@@ -4,9 +4,11 @@
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
entity.c
player.c
entityanim.c
npc.c
player.c
entitydir.c
)

200
archive/rpg/entity/entity.c Normal file
View File

@@ -0,0 +1,200 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entity.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "time/time.h"
#include "util/math.h"
#include "rpg/cutscene/cutscenemode.h"
#include "rpg/overworld/map.h"
entity_t ENTITIES[ENTITY_COUNT];
void entityInit(entity_t *entity, const entitytype_t type) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(type < ENTITY_TYPE_COUNT, "Invalid entity type");
assertTrue(type != ENTITY_TYPE_NULL, "Cannot have NULL entity type");
assertTrue(
entity >= ENTITIES && entity < ENTITIES + ENTITY_COUNT,
"Entity pointer is out of bounds"
);
memoryZero(entity, sizeof(entity_t));
entity->id = (uint8_t)(entity - ENTITIES);
entity->type = type;
if(ENTITY_CALLBACKS[type].init != NULL) ENTITY_CALLBACKS[type].init(entity);
}
void entityUpdate(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Invalid entity type");
assertTrue(entity->type != ENTITY_TYPE_NULL, "Cannot have NULL entity type");
// What state is the entity in?
if(entity->animation != ENTITY_ANIM_IDLE) {
// Entity is mid animation, tick it (down).
entity->animTime -= TIME.delta;
if(entity->animTime <= 0) {
entity->animation = ENTITY_ANIM_IDLE;
entity->animTime = 0;
}
return;
}
// Movement code.
if(
cutsceneModeIsInputAllowed() &&
ENTITY_CALLBACKS[entity->type].movement != NULL
) {
ENTITY_CALLBACKS[entity->type].movement(entity);
}
}
void entityTurn(entity_t *entity, const entitydir_t direction) {
entity->direction = direction;
entity->animation = ENTITY_ANIM_TURN;
entity->animTime = ENTITY_ANIM_TURN_DURATION;
}
void entityWalk(entity_t *entity, const entitydir_t direction) {
// TODO: Animation, delay, etc.
entity->direction = direction;
// Where are we moving?
worldpos_t newPos = entity->position;
{
worldunits_t relX, relY;
entityDirGetRelative(direction, &relX, &relY);
newPos.x += relX;
newPos.y += relY;
}
// Get tile under foot
tile_t tileCurrent = mapGetTile(entity->position);
tile_t tileNew = mapGetTile(newPos);
bool_t fall = false;
bool_t raise = false;
// Are we walking up a ramp?
if(
tileIsRamp(tileCurrent) &&
(
// Can only walk UP the direction the ramp faces.
(direction+TILE_SHAPE_RAMP_SOUTH) == tileCurrent ||
// If diagonal ramp, can go up one of two ways only.
(
(
tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST &&
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
) ||
(
tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST &&
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
) ||
(
tileCurrent == TILE_SHAPE_RAMP_NORTHEAST &&
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
) ||
(
tileCurrent == TILE_SHAPE_RAMP_NORTHWEST &&
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
)
)
// Must be able to walk up.
)
) {
tileNew = TILE_SHAPE_NULL;// Force check for ramp above.
worldpos_t abovePos = newPos;
abovePos.z += 1;
tile_t tileAbove = mapGetTile(abovePos);
if(tileAbove != TILE_SHAPE_NULL && tileIsWalkable(tileAbove)) {
// We can go up the ramp.
raise = true;
}
} else if(tileNew == TILE_SHAPE_NULL && newPos.z > 0) {
// Falling down?
worldpos_t belowPos = newPos;
belowPos.z -= 1;
tile_t tileBelow = mapGetTile(belowPos);
if(
tileBelow != TILE_SHAPE_NULL &&
tileIsRamp(tileBelow) &&
(
// This handles regular cardinal ramps
(entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_SOUTH) == tileBelow ||
// This handles diagonal ramps
(
(
tileBelow == TILE_SHAPE_RAMP_SOUTHEAST &&
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
) ||
(
tileBelow == TILE_SHAPE_RAMP_SOUTHWEST &&
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
) ||
(
tileBelow == TILE_SHAPE_RAMP_NORTHEAST &&
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
) ||
(
tileBelow == TILE_SHAPE_RAMP_NORTHWEST &&
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
)
)
)
) {
// We will fall to this tile.
fall = true;
}
}
// Can we walk here?
if(!raise && !fall && !tileIsWalkable(tileNew)) return;// Blocked
// Entity in way?
entity_t *other = ENTITIES;
do {
if(other == entity) continue;
if(other->type == ENTITY_TYPE_NULL) continue;
if(!worldPosIsEqual(other->position, newPos)) continue;
return;// Blocked
} while(++other, other < &ENTITIES[ENTITY_COUNT]);
entity->lastPosition = entity->position;
entity->position = newPos;
entity->animation = ENTITY_ANIM_WALK;
entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking
if(raise) {
entity->position.z += 1;
} else if(fall) {
entity->position.z -= 1;
}
}
entity_t * entityGetAt(const worldpos_t position) {
entity_t *ent = ENTITIES;
do {
if(ent->type == ENTITY_TYPE_NULL) continue;
if(!worldPosIsEqual(ent->position, position)) continue;
return ent;
} while(++ent, ent < &ENTITIES[ENTITY_COUNT]);
return NULL;
}
uint8_t entityGetAvailable() {
entity_t *ent = ENTITIES;
do {
if(ent->type == ENTITY_TYPE_NULL) return ent - ENTITIES;
} while(++ent, ent < &ENTITIES[ENTITY_COUNT]);
return 0xFF;
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "entitydir.h"
#include "entityanim.h"
#include "entitytype.h"
#include "npc.h"
typedef struct map_s map_t;
typedef struct entity_s {
uint8_t id;
entitytype_t type;
entitytypedata_t data;
// Movement
entitydir_t direction;
worldpos_t position;
worldpos_t lastPosition;
entityanim_t animation;
float_t animTime;
} entity_t;
extern entity_t ENTITIES[ENTITY_COUNT];
/**
* Initializes an entity structure.
*
* @param entity Pointer to the entity structure to initialize.
* @param type The type of the entity.
*/
void entityInit(entity_t *entity, const entitytype_t type);
/**
* Updates an entity.
*
* @param entity Pointer to the entity structure to update.
*/
void entityUpdate(entity_t *entity);
/**
* Turn an entity to face a new direction.
*
* @param entity Pointer to the entity to turn.
* @param direction The direction to face.
*/
void entityTurn(entity_t *entity, const entitydir_t direction);
/**
* Make an entity walk in a direction.
*
* @param entity Pointer to the entity to make walk.
* @param direction The direction to walk in.
*/
void entityWalk(entity_t *entity, const entitydir_t direction);
/**
* Gets the entity at a specific world position.
*
* @param map Pointer to the map to check.
* @param pos The world position to check.
* @return Pointer to the entity at the position, or NULL if none.
*/
entity_t *entityGetAt(const worldpos_t pos);
/**
* Gets an available entity index.
*
* @return The index of an available entity, or 0xFF if none are available.
*/
uint8_t entityGetAvailable();

View File

@@ -5,4 +5,5 @@
* https://opensource.org/licenses/MIT
*/
#include "display/render.h"
#include "entityanim.h"

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define ENTITY_ANIM_TURN_DURATION 0.06f
#define ENTITY_ANIM_WALK_DURATION 0.1f
typedef enum {
ENTITY_ANIM_IDLE,
ENTITY_ANIM_TURN,
ENTITY_ANIM_WALK,
} entityanim_t;

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entitydir.h"
#include "assert/assert.h"
entitydir_t entityDirGetOpposite(const entitydir_t dir) {
switch(dir) {
case ENTITY_DIR_NORTH: return ENTITY_DIR_SOUTH;
case ENTITY_DIR_SOUTH: return ENTITY_DIR_NORTH;
case ENTITY_DIR_EAST: return ENTITY_DIR_WEST;
case ENTITY_DIR_WEST: return ENTITY_DIR_EAST;
default: return dir;
}
}
void entityDirGetRelative(
const entitydir_t from,
worldunits_t *outX,
worldunits_t *outY
) {
assertValidEntityDir(from, "Invalid direction provided");
assertNotNull(outX, "Output X pointer cannot be NULL");
assertNotNull(outY, "Output Y pointer cannot be NULL");
switch(from) {
case ENTITY_DIR_NORTH:
*outX = 0;
*outY = -1;
break;
case ENTITY_DIR_EAST:
*outX = 1;
*outY = 0;
break;
case ENTITY_DIR_SOUTH:
*outX = 0;
*outY = 1;
break;
case ENTITY_DIR_WEST:
*outX = -1;
*outY = 0;
break;
}
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "rpg/overworld/worldpos.h"
typedef enum {
ENTITY_DIR_UP = ENTITY_DIR_NORTH,
ENTITY_DIR_DOWN = ENTITY_DIR_SOUTH,
ENTITY_DIR_LEFT = ENTITY_DIR_WEST,
ENTITY_DIR_RIGHT = ENTITY_DIR_EAST,
} entitydir_t;
/**
* Gets the opposite direction of a given direction.
*
* @param dir The direction to get the opposite of.
* @return entitydir_t The opposite direction.
*/
entitydir_t entityDirGetOpposite(const entitydir_t dir);
/**
* Asserts a given direction is valid.
*
* @param dir The direction to validate.
* @param msg The message to display if the assertion fails.
*/
#define assertValidEntityDir(dir, msg) \
assertTrue( \
(dir) == ENTITY_DIR_NORTH || \
(dir) == ENTITY_DIR_EAST || \
(dir) == ENTITY_DIR_SOUTH || \
(dir) == ENTITY_DIR_WEST, \
msg \
)
/**
* Gets the relative x and y offsets for a given direction.
*
* @param dir The direction to get offsets for.
* @param relX Pointer to store the relative x offset.
* @param relY Pointer to store the relative y offset.
*/
void entityDirGetRelative(
const entitydir_t dir, worldunits_t *relX, worldunits_t *relY
);

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "duskdefs.h"
#include "rpg/entity/player.h"
#include "npc.h"
typedef uint8_t entitytype_t;
typedef union {
player_t player;
npc_t npc;
} entitytypedata_t;
typedef struct {
/**
* Initialization callback for the entity type.
* @param entity Pointer to the entity to initialize.
*/
void (*init)(entity_t *entity);
/**
* Movement callback for the entity type.
* @param entity Pointer to the entity to move.
*/
void (*movement)(entity_t *entity);
/**
* Interaction callback for the entity type.
* @param player Pointer to the player entity.
* @param entity Pointer to the entity to interact with.
* @return True if the entity handled the interaction, false otherwise.
*/
bool_t (*interact)(entity_t *player, entity_t *entity);
} entitycallback_t;
static const entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
[ENTITY_TYPE_NULL] = { NULL },
[ENTITY_TYPE_PLAYER] = {
.init = playerInit,
.movement = playerInput
},
[ENTITY_TYPE_NPC] = {
.init = npcInit,
.movement = npcMovement,
.interact = npcInteract
}
};

31
archive/rpg/entity/npc.c Normal file
View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entity.h"
#include "assert/assert.h"
#include "rpg/cutscene/scene/testcutscene.h"
#include "rpg/rpgtextbox.h"
void npcInit(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
}
void npcMovement(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
}
bool_t npcInteract(entity_t *player, entity_t *npc) {
assertNotNull(player, "Player entity pointer cannot be NULL");
assertNotNull(npc, "NPC entity pointer cannot be NULL");
cutsceneSystemStartCutscene(&TEST_CUTSCENE);
// rpgTextboxShow(RPG_TEXTBOX_POS_BOTTOM, "Hello World!");
return false;
};

37
archive/rpg/entity/npc.h Normal file
View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct entity_s entity_t;
typedef struct {
void *nothing;
} npc_t;
/**
* Initializes an NPC entity.
*
* @param entity Pointer to the entity structure to initialize.
*/
void npcInit(entity_t *entity);
/**
* Updates an NPC entity.
*
* @param entity Pointer to the entity structure to update.
*/
void npcMovement(entity_t *entity);
/**
* Handles interaction with an NPC entity.
*
* @param player Pointer to the player entity.
* @param npc Pointer to the NPC entity.
*/
bool_t npcInteract(entity_t *player, entity_t *npc);

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entity.h"
#include "assert/assert.h"
#include "rpg/rpgcamera.h"
#include "util/memory.h"
#include "time/time.h"
void playerInit(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
}
void playerInput(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
// Turn
const playerinputdirmap_t *dirMap = PLAYER_INPUT_DIR_MAP;
do {
if(!inputIsDown(dirMap->action)) continue;
if(entity->direction == dirMap->direction) continue;
return entityTurn(entity, dirMap->direction);
} while((++dirMap)->action != 0xFF);
// Walk
dirMap = PLAYER_INPUT_DIR_MAP;
do {
if(!inputIsDown(dirMap->action)) continue;
if(entity->direction != dirMap->direction) continue;
return entityWalk(entity, dirMap->direction);
} while((++dirMap)->action != 0xFF);
// Interaction
if(inputPressed(INPUT_ACTION_ACCEPT)) {
worldunit_t x, y, z;
{
worldunits_t relX, relY;
entityDirGetRelative(entity->direction, &relX, &relY);
x = entity->position.x + relX;
y = entity->position.y + relY;
z = entity->position.z;
}
entity_t *interact = entityGetAt((worldpos_t){ x, y, z });
if(interact != NULL && ENTITY_CALLBACKS[interact->type].interact != NULL) {
if(ENTITY_CALLBACKS[interact->type].interact(entity, interact)) return;
}
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "input/input.h"
typedef struct entity_s entity_t;
typedef struct {
void *nothing;
} player_t;
typedef struct {
inputaction_t action;
entitydir_t direction;
} playerinputdirmap_t;
static const playerinputdirmap_t PLAYER_INPUT_DIR_MAP[] = {
{ INPUT_ACTION_UP, ENTITY_DIR_NORTH },
{ INPUT_ACTION_DOWN, ENTITY_DIR_SOUTH },
{ INPUT_ACTION_LEFT, ENTITY_DIR_WEST },
{ INPUT_ACTION_RIGHT, ENTITY_DIR_EAST },
{ 0xFF, 0xFF }
};
/**
* Initializes a player entity.
*
* @param entity Pointer to the entity structure to initialize.
*/
void playerInit(entity_t *entity);
/**
* Handles movement logic for the player entity.
*
* @param entity Pointer to the player entity structure.
*/
void playerInput(entity_t *entity);

View File

@@ -6,9 +6,8 @@
*/
#pragma once
#include "itemtype.h"
#include "dusk.h"
typedef struct {
itemtype_t type;
uint8_t count;
} itemstack_t;
void *nothing;
} inventory_t;

View File

@@ -0,0 +1,13 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
chunk.c
map.c
worldpos.c
tile.c
)

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "chunk.h"
uint32_t chunkGetTileIndex(const chunkpos_t position) {
return (
(position.z * CHUNK_WIDTH * CHUNK_HEIGHT) +
(position.y * CHUNK_WIDTH) +
position.x
);
}
bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b) {
return (a.x == b.x) && (a.y == b.y) && (a.z == b.z);
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "rpg/overworld/tile.h"
#include "worldpos.h"
#include "display/mesh/quad.h"
typedef struct chunk_s {
chunkpos_t position;
tile_t tiles[CHUNK_TILE_COUNT];
uint8_t meshCount;
meshvertex_t vertices[CHUNK_VERTEX_COUNT_MAX];
mesh_t meshes[CHUNK_MESH_COUNT_MAX];
uint8_t entities[CHUNK_ENTITY_COUNT_MAX];
} chunk_t;
/**
* Gets the tile index for a tile position within a chunk.
*
* @param position The position within the chunk.
* @return The tile index within the chunk.
*/
uint32_t chunkGetTileIndex(const chunkpos_t position);
/**
* Checks if two chunk positions are equal.
*
* @param a The first chunk position.
* @param b The second chunk position.
* @return true if equal, false otherwise.
*/
bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b);

269
archive/rpg/overworld/map.c Normal file
View File

@@ -0,0 +1,269 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "map.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "asset/asset.h"
#include "rpg/entity/entity.h"
#include "util/string.h"
#include "script/scriptcontext.h"
map_t MAP;
errorret_t mapInit() {
memoryZero(&MAP, sizeof(map_t));
errorOk();
}
bool_t mapIsLoaded() {
return MAP.filePath[0] != '\0';
}
errorret_t mapLoad(const char_t *path, const chunkpos_t position) {
assertStrLenMin(path, 1, "Map file path cannot be empty");
assertStrLenMax(path, MAP_FILE_PATH_MAX - 1, "Map file path too long");
if(stringCompare(MAP.filePath, path) == 0) {
// Same map, no need to reload
errorOk();
}
chunkindex_t i;
// Unload all loaded chunks
if(mapIsLoaded()) {
for(i = 0; i < MAP_CHUNK_COUNT; i++) {
mapChunkUnload(&MAP.chunks[i]);
}
}
// Store the map file path
stringCopy(MAP.filePath, path, MAP_FILE_PATH_MAX);
// Determine directory path (it is dirname)
stringCopy(MAP.dirPath, path, MAP_FILE_PATH_MAX);
char_t *last = stringFindLastChar(MAP.dirPath, '/');
if(last == NULL) errorThrow("Invalid map file path");
// Store filename, sans extension
stringCopy(MAP.fileName, last + 1, MAP_FILE_PATH_MAX);
*last = '\0'; // Terminate to get directory path
last = stringFindLastChar(MAP.fileName, '.');
if(last == NULL) errorThrow("Map file name has no extension");
*last = '\0'; // Terminate to remove extension
// Reset map position
MAP.chunkPosition = position;
// Perform "initial load"
i = 0;
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) {
chunk_t *chunk = &MAP.chunks[i];
chunk->position.x = x + position.x;
chunk->position.y = y + position.y;
chunk->position.z = z + position.z;
MAP.chunkOrder[i] = chunk;
errorChain(mapChunkLoad(chunk));
i++;
}
}
}
// Execute map script.
char_t scriptPath[MAP_FILE_PATH_MAX + 16];
stringFormat(
scriptPath, sizeof(scriptPath), "%s/%s.dsf",
MAP.dirPath, MAP.fileName
);
if(assetFileExists(scriptPath)) {
scriptcontext_t ctx;
errorChain(scriptContextInit(&ctx));
errorChain(scriptContextExecFile(&ctx, scriptPath));
scriptContextDispose(&ctx);
}
errorOk();
}
errorret_t mapPositionSet(const chunkpos_t newPos) {
if(!mapIsLoaded()) errorThrow("No map loaded");
const chunkpos_t curPos = MAP.chunkPosition;
if(chunkPositionIsEqual(curPos, newPos)) {
errorOk();
}
// Determine which chunks remain loaded
chunkindex_t chunksRemaining[MAP_CHUNK_COUNT] = {0};
chunkindex_t chunksFreed[MAP_CHUNK_COUNT] = {0};
uint32_t remainingCount = 0;
uint32_t freedCount = 0;
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
// Will this chunk remain loaded?
chunk_t *chunk = &MAP.chunks[i];
if(
chunk->position.x >= newPos.x &&
chunk->position.x < newPos.x + MAP_CHUNK_WIDTH &&
chunk->position.y >= newPos.y &&
chunk->position.y < newPos.y + MAP_CHUNK_HEIGHT &&
chunk->position.z >= newPos.z &&
chunk->position.z < newPos.z + MAP_CHUNK_DEPTH
) {
// Stays loaded
chunksRemaining[remainingCount++] = i;
continue;
}
// Not remaining loaded
chunksFreed[freedCount++] = i;
}
// Unload the freed chunks
for(chunkindex_t i = 0; i < freedCount; i++) {
chunk_t *chunk = &MAP.chunks[chunksFreed[i]];
mapChunkUnload(chunk);
}
// This can probably be optimized later, for now we check each chunk and see
// if it needs loading or not, and update the chunk order
chunkindex_t orderIndex = 0;
for(chunkunit_t zOff = 0; zOff < MAP_CHUNK_DEPTH; zOff++) {
for(chunkunit_t yOff = 0; yOff < MAP_CHUNK_HEIGHT; yOff++) {
for(chunkunit_t xOff = 0; xOff < MAP_CHUNK_WIDTH; xOff++) {
const chunkpos_t newChunkPos = {
newPos.x + xOff, newPos.y + yOff, newPos.z + zOff
};
// Is this chunk already loaded (was not unloaded earlier)?
chunkindex_t chunkIndex = -1;
for(chunkindex_t i = 0; i < remainingCount; i++) {
chunk_t *chunk = &MAP.chunks[chunksRemaining[i]];
if(!chunkPositionIsEqual(chunk->position, newChunkPos)) continue;
chunkIndex = chunksRemaining[i];
break;
}
// Need to load this chunk
if(chunkIndex == -1) {
// Find a freed chunk to reuse
chunkIndex = chunksFreed[--freedCount];
chunk_t *chunk = &MAP.chunks[chunkIndex];
chunk->position = newChunkPos;
errorChain(mapChunkLoad(chunk));
}
MAP.chunkOrder[orderIndex++] = &MAP.chunks[chunkIndex];
}
}
}
// Update map position
MAP.chunkPosition = newPos;
errorOk();
}
void mapUpdate() {
}
void mapDispose() {
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
mapChunkUnload(&MAP.chunks[i]);
}
}
void mapChunkUnload(chunk_t* chunk) {
for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) {
if(chunk->entities[i] == 0xFF) break;
entity_t *entity = &ENTITIES[chunk->entities[i]];
entity->type = ENTITY_TYPE_NULL;
}
for(uint8_t i = 0; i < chunk->meshCount; i++) {
if(chunk->meshes[i].vertexCount == 0) continue;
meshDispose(&chunk->meshes[i]);
}
}
errorret_t mapChunkLoad(chunk_t* chunk) {
if(!mapIsLoaded()) errorThrow("No map loaded");
char_t buffer[64];
// TODO: Can probably move this to asset load logic?
chunk->meshCount = 0;
memoryZero(chunk->meshes, sizeof(chunk->meshes));
memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
// Get chunk filepath.
snprintf(buffer, sizeof(buffer), "%s/chunks/%d_%d_%d.dmc",
MAP.dirPath,
chunk->position.x,
chunk->position.y,
chunk->position.z
);
// Chunk available?
if(!assetFileExists(buffer)) {
memoryZero(chunk->tiles, sizeof(chunk->tiles));
errorOk();
}
// Load.
errorChain(assetLoad(buffer, chunk));
errorOk();
}
chunkindex_t mapGetChunkIndexAt(const chunkpos_t position) {
if(!mapIsLoaded()) return -1;
chunkpos_t relPos = {
position.x - MAP.chunkPosition.x,
position.y - MAP.chunkPosition.y,
position.z - MAP.chunkPosition.z
};
if(
relPos.x < 0 || relPos.y < 0 || relPos.z < 0 ||
relPos.x >= MAP_CHUNK_WIDTH ||
relPos.y >= MAP_CHUNK_HEIGHT ||
relPos.z >= MAP_CHUNK_DEPTH
) {
return -1;
}
return chunkPosToIndex(&relPos);
}
chunk_t* mapGetChunk(const uint8_t index) {
if(index >= MAP_CHUNK_COUNT) return NULL;
if(!mapIsLoaded()) return NULL;
return MAP.chunkOrder[index];
}
tile_t mapGetTile(const worldpos_t position) {
if(!mapIsLoaded()) return TILE_SHAPE_NULL;
chunkpos_t chunkPos;
worldPosToChunkPos(&position, &chunkPos);
chunkindex_t chunkIndex = mapGetChunkIndexAt(chunkPos);
if(chunkIndex == -1) return TILE_SHAPE_NULL;
chunk_t *chunk = mapGetChunk(chunkIndex);
assertNotNull(chunk, "Chunk pointer cannot be NULL");
chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position);
return chunk->tiles[tileIndex];
}

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