Compare commits

...

163 Commits

Author SHA1 Message Date
c4be97e0de added forge addon
Some checks are pending
Create tag and build when new code gets to main / Test (push) Waiting to run
Create tag and build when new code gets to main / Export (push) Blocked by required conditions
Create tag and build when new code gets to main / BumpTag (push) Successful in 21s
2026-02-08 15:16:01 +01:00
2b74c9e70c added CSG toolkit
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Test (push) Successful in 7m54s
Create tag and build when new code gets to main / Export (push) Successful in 9m52s
2026-02-06 18:35:38 +01:00
77d405687c more lights in tutorial and better wall run detection I believe
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Test (push) Successful in 6m19s
Create tag and build when new code gets to main / Export (push) Successful in 7m55s
2026-02-06 16:42:01 +01:00
4c1831762b added a max velocity for player and fixed the multiple back inputs in menus 2026-02-06 15:12:36 +01:00
6d967bf2bf extended enemy dashthrough improvement to aimed dash behaviour
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Test (push) Successful in 7m24s
Create tag and build when new code gets to main / Export (push) Successful in 9m32s
2026-02-06 12:10:21 +01:00
e87a228dd2 fixed dashing through terrain so easily when targeting enemy 2026-02-06 11:52:29 +01:00
e8ff01e097 fixed toolbox menu issue 2026-02-06 11:02:38 +01:00
66a71067cc fixed a few issues and also tuto triggers
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 21s
Create tag and build when new code gets to main / Test (push) Successful in 7m20s
Create tag and build when new code gets to main / Export (push) Successful in 9m42s
2026-02-05 17:26:20 +01:00
7a938b245e refresh level selection
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 18s
Create tag and build when new code gets to main / Test (push) Successful in 6m11s
Create tag and build when new code gets to main / Export (push) Successful in 7m35s
2026-02-05 17:06:32 +01:00
52ebc68a02 put the player back in its place
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 21s
Create tag and build when new code gets to main / Test (push) Has been cancelled
Create tag and build when new code gets to main / Export (push) Has been cancelled
2026-02-05 17:04:45 +01:00
2a604fb06a tutorial levels with enemies
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Test (push) Successful in 8m35s
Create tag and build when new code gets to main / Export (push) Has been cancelled
2026-02-05 16:52:53 +01:00
1f904cdb13 final tuto movement
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Test (push) Successful in 7m16s
Create tag and build when new code gets to main / Export (push) Successful in 9m29s
2026-02-05 15:26:40 +01:00
db93e8f68e base movement tutorial done
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 19s
Create tag and build when new code gets to main / Test (push) Successful in 6m48s
Create tag and build when new code gets to main / Export (push) Successful in 8m56s
2026-02-05 10:44:32 +01:00
4c302be16b more tutorial 2026-02-05 10:06:25 +01:00
013545af8a more tutorial 2026-02-05 10:06:12 +01:00
9f5769bb76 testing ci
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 19s
Create tag and build when new code gets to main / Test (push) Successful in 7m30s
Create tag and build when new code gets to main / Export (push) Successful in 9m35s
2026-02-05 08:36:41 +01:00
8a7ad9d418 test ci 2026-02-05 08:32:16 +01:00
f589ef8dec basic movement tutorial 2026-02-05 08:30:35 +01:00
55a12ec7cd new sky and greybox materials 2026-02-04 13:11:00 +01:00
fad528faa1 refactored the way levels are listed in the toolbox
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 19s
Create tag and build when new code gets to main / Export (push) Has been cancelled
Create tag and build when new code gets to main / Test (push) Has been cancelled
2026-02-04 11:57:30 +01:00
cd519e528f complete project reorganization
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 55s
Create tag and build when new code gets to main / Test (push) Successful in 7m7s
Create tag and build when new code gets to main / Export (push) Successful in 9m56s
2026-02-04 11:20:00 +01:00
b6e8d0b590 fixed jolt issue 2026-02-04 10:34:43 +01:00
03f72f3715 name change 2026-02-04 10:33:45 +01:00
15ab9edab1 fixed a wallhanging bug and explosion shader precompilation to alleviate stuttering 2026-02-04 10:32:44 +01:00
0e412c2a42 fixed level loading and starting to setup proper gyms, zoos and playtest levels
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Test (push) Successful in 8m23s
Create tag and build when new code gets to main / Export (push) Successful in 10m40s
2026-02-03 16:31:37 +01:00
8a3faff7d0 small refacto of dash actions 2026-02-03 15:10:22 +01:00
3525f0e3eb added a parry button and animation that lets player chose their enemy hit behaviour
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Test (push) Successful in 5m57s
Create tag and build when new code gets to main / Export (push) Successful in 7m43s
2026-01-30 13:19:28 +01:00
cc973b9f0d fixed spawners
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Test (push) Successful in 6m4s
Create tag and build when new code gets to main / Export (push) Successful in 8m2s
2026-01-28 19:14:05 +01:00
8a552f7993 #minor CI FIXED
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Test (push) Successful in 6m0s
Create tag and build when new code gets to main / Export (push) Successful in 7m58s
2026-01-28 18:28:40 +01:00
93841bc85d manually uploading to itch
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 21s
Create tag and build when new code gets to main / Test (push) Successful in 5m58s
Create tag and build when new code gets to main / Export (push) Successful in 7m52s
2026-01-28 18:15:15 +01:00
9ba8847626 changing itch action
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Test (push) Failing after 5m21s
Create tag and build when new code gets to main / Export (push) Failing after 7m11s
2026-01-28 17:56:59 +01:00
51b7328310 changing itch action
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 24s
Create tag and build when new code gets to main / Export (push) Failing after 4s
Create tag and build when new code gets to main / Test (push) Has been cancelled
2026-01-28 17:54:06 +01:00
fdc352596d were getting there...
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 37s
Create tag and build when new code gets to main / Test (push) Successful in 5m43s
Create tag and build when new code gets to main / Export (push) Failing after 8m23s
2026-01-28 17:39:23 +01:00
89ba5cc985 trying the the fix on export again and trying to cache lfs objects again
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 40s
Create tag and build when new code gets to main / Test (push) Successful in 5m45s
Create tag and build when new code gets to main / Export (push) Failing after 7m56s
2026-01-28 17:25:28 +01:00
fdc79166a0 testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Test (push) Successful in 4m39s
Create tag and build when new code gets to main / Export (push) Failing after 5m16s
2026-01-28 16:22:38 +01:00
b84487336b testing ci 2026-01-28 16:20:44 +01:00
e4ab103c4d testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Export (push) Failing after 1m15s
Create tag and build when new code gets to main / Test (push) Successful in 4m43s
2026-01-28 15:33:56 +01:00
3b6cf0252b testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 13s
Create tag and build when new code gets to main / BumpTag (push) Successful in 30s
Create tag and build when new code gets to main / Export (push) Failing after 45s
2026-01-28 15:29:31 +01:00
e908cd3085 testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 14s
Create tag and build when new code gets to main / BumpTag (push) Failing after 24s
Create tag and build when new code gets to main / Export (push) Failing after 51s
2026-01-28 15:19:04 +01:00
6b23fdbd26 testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 12s
Create tag and build when new code gets to main / BumpTag (push) Failing after 22s
Create tag and build when new code gets to main / Export (push) Failing after 1m29s
2026-01-28 15:13:11 +01:00
ea6258ff19 testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 12s
Create tag and build when new code gets to main / BumpTag (push) Failing after 21s
Create tag and build when new code gets to main / Export (push) Failing after 53s
2026-01-28 15:05:12 +01:00
5e54f0f83b testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 12s
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Export (push) Failing after 55s
2026-01-28 15:00:24 +01:00
f00439a430 testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 18s
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 54s
2026-01-28 13:08:24 +01:00
02ec230b3f testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 13s
Create tag and build when new code gets to main / BumpTag (push) Successful in 25s
Create tag and build when new code gets to main / Export (push) Failing after 52s
2026-01-28 12:48:28 +01:00
867554b835 testing ci
Some checks failed
Create tag and build when new code gets to main / Test (push) Failing after 18s
Create tag and build when new code gets to main / BumpTag (push) Successful in 30s
Create tag and build when new code gets to main / Export (push) Failing after 47s
2026-01-28 12:45:13 +01:00
a4835eeb3c testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 7s
Create tag and build when new code gets to main / Test (push) Failing after 6m21s
2026-01-28 12:34:26 +01:00
55b877226e testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 1m31s
2026-01-28 10:49:08 +01:00
d7d33d0dac testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 1m28s
2026-01-28 10:47:15 +01:00
f92c6d282f testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 1m33s
2026-01-28 10:43:32 +01:00
2d41523668 testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 1m24s
2026-01-28 10:38:56 +01:00
34b04a365a testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Has been cancelled
2026-01-28 10:38:21 +01:00
d0ac644e14 testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 9m17s
2026-01-28 10:28:07 +01:00
510a3200d1 testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 9m32s
2026-01-28 10:17:05 +01:00
bef601941c testing ci
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 1m24s
2026-01-28 10:14:25 +01:00
b6605d6293 testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 1m11s
Create tag and build when new code gets to main / Export (push) Failing after 1m29s
2026-01-28 10:10:51 +01:00
1b6742ea45 testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 1m27s
2026-01-28 10:06:32 +01:00
405e487881 testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 37s
Create tag and build when new code gets to main / Export (push) Failing after 2m18s
Create tag and build when new code gets to main / Test (push) Successful in 6m42s
2026-01-28 09:53:22 +01:00
af1f6da98d updating project files
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 31s
Create tag and build when new code gets to main / Test (push) Has been cancelled
Create tag and build when new code gets to main / Export (push) Has been cancelled
2026-01-28 09:50:10 +01:00
119850a7b4 testing ci
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Export (push) Failing after 3m29s
Create tag and build when new code gets to main / Test (push) Failing after 9m11s
2026-01-28 09:33:21 +01:00
b198aba09b updating state chart addon
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 21s
Create tag and build when new code gets to main / Export (push) Failing after 1m28s
2026-01-27 19:13:53 +01:00
37165d1562 fuck tests I guess
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Export (push) Failing after 1m57s
2026-01-27 19:04:52 +01:00
5684561b66 fuck tests I guess 2026-01-27 19:04:34 +01:00
2678cac0e6 FUUUUCK FUUUUUUUUUUCK
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 21s
Create tag and build when new code gets to main / Export (push) Failing after 2m1s
2026-01-27 19:01:05 +01:00
230b409abe FUUUUCK FUUUUUUUUUUCK
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Export (push) Failing after 15s
2026-01-27 18:56:12 +01:00
a6c80206c9 trying to fix syntax error in checkout action
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 40s
2026-01-27 18:53:03 +01:00
1a73f23670 trying to fix syntax error in checkout action
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 15s
2026-01-27 18:52:04 +01:00
38e62dcbb3 trying to fix syntax error in checkout action
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 35s
Create tag and build when new code gets to main / Export (push) Failing after 1m8s
2026-01-27 18:48:37 +01:00
19bdc143c1 trying to fix syntax error in checkout action
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 2m3s
Create tag and build when new code gets to main / Export (push) Failing after 1m37s
2026-01-27 18:44:03 +01:00
1709554e72 changing GDUnit dependency because 4.6 not yet supported
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 3m44s
2026-01-27 18:07:54 +01:00
5c2e9408c5 caching improvements
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 26s
Create tag and build when new code gets to main / Test (push) Failing after 2m6s
Create tag and build when new code gets to main / Export (push) Has been skipped
2026-01-27 17:59:10 +01:00
caeae26a09 fixed camera and sword animation issue and upgraded to Godot 4.6
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Test (push) Failing after 2m10s
Create tag and build when new code gets to main / Export (push) Has been skipped
2026-01-27 17:47:19 +01:00
056a68b0ad small death animation and toolbox
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 35s
Create tag and build when new code gets to main / Test (push) Successful in 6m20s
Create tag and build when new code gets to main / Export (push) Successful in 7m20s
2026-01-27 16:42:31 +01:00
f1f0febf29 death and restart menu working 2026-01-27 15:11:43 +01:00
916a6e7153 some editor icons and a base level scene
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 33s
Create tag and build when new code gets to main / Test (push) Successful in 6m19s
Create tag and build when new code gets to main / Export (push) Successful in 7m39s
2026-01-27 10:35:35 +01:00
ddf1bd019b better healthbars and one for the player as well
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 42s
Create tag and build when new code gets to main / Test (push) Successful in 6m13s
Create tag and build when new code gets to main / Export (push) Successful in 7m5s
2026-01-26 18:09:29 +01:00
2fdc9c7ca8 instanciating explosion on slam
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Has been cancelled
Create tag and build when new code gets to main / Test (push) Has been cancelled
Create tag and build when new code gets to main / Export (push) Has been cancelled
2026-01-26 16:34:18 +01:00
d79ca44972 everything should work fine now
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 26s
Create tag and build when new code gets to main / Test (push) Successful in 8m7s
Create tag and build when new code gets to main / Export (push) Successful in 7m13s
2026-01-26 09:26:54 +01:00
148aea9bb4 trying to remove ad unit addon from build job because it breaks
All checks were successful
Create tag and build when new code gets to main / Export (push) Successful in 6m59s
2026-01-26 09:18:28 +01:00
bdce8b969c reinstalling GDUnit from assetlib
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 6m41s
2026-01-26 09:05:55 +01:00
4095f818f6 gdunit addon post import by godot
All checks were successful
Create tag and build when new code gets to main / Export (push) Successful in 7m17s
2026-01-26 08:59:34 +01:00
72bf3d4cc5 making sure the issue comes from GDUnit addon folder
All checks were successful
Create tag and build when new code gets to main / Export (push) Successful in 7m6s
2026-01-26 08:51:14 +01:00
51907a1f01 trying to fix Export
All checks were successful
Create tag and build when new code gets to main / Export (push) Successful in 6m53s
2026-01-26 08:41:48 +01:00
c5c4ceb032 trying to fix Export
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 6m47s
2026-01-26 08:30:31 +01:00
64957334de trying to fix Export
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 35s
Create tag and build when new code gets to main / Export (push) Has been cancelled
2026-01-26 08:29:43 +01:00
7323b6e814 trying to fix Export
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Export (push) Failing after 8m32s
2026-01-26 08:12:23 +01:00
24f9801093 trying to fix Export
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 26s
Create tag and build when new code gets to main / Export (push) Failing after 7m16s
2026-01-26 07:58:33 +01:00
bcc8af76c2 trying to fix Export
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Export (push) Failing after 6m41s
2026-01-25 23:58:19 +01:00
2923407e90 fixing parallelization issue of the runner
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 26s
Create tag and build when new code gets to main / Test (push) Successful in 8m5s
Create tag and build when new code gets to main / Export (push) Failing after 6m17s
2026-01-25 20:35:33 +01:00
ee5844f603 putting back old checkout version for the bump tag job because auth is broken
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 43s
Create tag and build when new code gets to main / Test (push) Successful in 8m10s
Create tag and build when new code gets to main / Export (push) Failing after 10m9s
2026-01-25 20:15:51 +01:00
5ab1589609 Finalizing workflows
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Failing after 27s
Create tag and build when new code gets to main / Export (push) Has been skipped
Create tag and build when new code gets to main / Test (push) Failing after 19m23s
2026-01-25 19:59:37 +01:00
bed7437893 removing useles cache actions, fixing report path, letting GDUnitAction setup dotnet by itself and fixing godot path in this environment
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 6m7s
2026-01-25 19:36:22 +01:00
05219b536e manually uploading test report
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 7m4s
2026-01-25 19:21:22 +01:00
05c4ce1c43 setting up LFS cache and installing XVFB
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 6m48s
2026-01-25 19:06:08 +01:00
c66e929d8b fixing lfs checkout
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 8m33s
2026-01-25 18:38:36 +01:00
5e193ede88 back with old LFS system but trying checkout v6 and cache v5
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 5m4s
2026-01-25 18:26:57 +01:00
c28d97de2d setting up GDUnit
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 3m40s
2026-01-25 18:19:26 +01:00
39d6ab1c5f trying new runner
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 18m44s
2026-01-25 17:28:23 +01:00
bf06955c06 setup dotnet node20
Some checks are pending
Create tag and build when new code gets to main / Export (push) Waiting to run
2026-01-25 17:18:16 +01:00
370e015cc5 setup dotnet
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 8s
2026-01-25 10:53:41 +01:00
5ce2824f8b back to old checkout system
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 8m21s
2026-01-25 10:21:16 +01:00
b73c1a6dd5 trying out GDUnit action and checkout v6
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 14s
2026-01-25 10:18:21 +01:00
e50e2c9918 removing .net config file
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 24s
Create tag and build when new code gets to main / Export (push) Successful in 14m44s
2026-01-25 10:02:11 +01:00
5cb2d2beb5 removed blocking condition in ci 2026-01-25 10:02:05 +01:00
cb2d7e35ce dev branch workflow 2026-01-25 10:01:59 +01:00
58bb1d9ca5 trying to fix CI
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 23s
Create tag and build when new code gets to main / Export (push) Failing after 1m43s
2026-01-25 00:24:51 +01:00
cf7591b413 this is so easy to develop there must be a catch
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 26s
Create tag and build when new code gets to main / Export (push) Failing after 1m55s
2026-01-25 00:16:16 +01:00
92cc4f0264 sooooo coded a feature first try? weird
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 25s
Create tag and build when new code gets to main / Export (push) Failing after 1m48s
2026-01-24 23:29:22 +01:00
18c8b741dd can plant weapon in targetables, dash towards it, jump in the air.
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 28s
Create tag and build when new code gets to main / Export (push) Failing after 1m47s
2026-01-24 15:22:16 +01:00
b84b7e4dd5 aim dashing through targetable entities now possible 2026-01-24 13:49:16 +01:00
4d419b9010 basic healthbars for enemies 2026-01-23 13:31:11 +01:00
8b2bf3e32e fixed a going through wall issue 2026-01-21 17:31:24 +01:00
db49703326 added fixed dash targets and can dash towards enemies to hit them, get a knockback or dash through if killed
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Export (push) Failing after 1m51s
2026-01-21 16:46:20 +01:00
fb78add739 export target variables and made a targetable interface 2026-01-21 14:21:47 +01:00
04121f18a4 basic targeting system 2026-01-21 12:32:58 +01:00
fa029b9e53 fixed resources constructors
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 24s
Create tag and build when new code gets to main / Export (push) Successful in 15m37s
2026-01-21 10:13:00 +01:00
494f0cb9ca some SFX
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 21s
Create tag and build when new code gets to main / Export (push) Successful in 15m19s
2026-01-20 17:39:14 +01:00
c1ca0bf27b some shake 2026-01-20 15:27:59 +01:00
8d1e7ebb4f some jumping animations
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 18s
Create tag and build when new code gets to main / Export (push) Successful in 10m30s
2026-01-20 13:23:42 +01:00
a257306999 reorganizing stuff
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 19s
Create tag and build when new code gets to main / Export (push) Successful in 10m45s
2026-01-20 12:23:26 +01:00
2e5fcb6a75 implemented player health, knockback, invicibility frames and hitstop
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Export (push) Successful in 9m56s
2026-01-20 12:05:31 +01:00
87a9fad005 now that's an animation
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Export (push) Successful in 9m57s
2026-01-19 23:15:40 +01:00
837b3d7705 hyperfocused on the procedural FP animations
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 19s
Create tag and build when new code gets to main / Export (push) Successful in 9m46s
2026-01-19 18:51:27 +01:00
4224333963 improved weapon system and cleaner weapon setup
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Export (push) Successful in 10m20s
2026-01-19 17:25:12 +01:00
4dd48bed70 orienting flying sword 2026-01-19 16:55:22 +01:00
abe6f42a3b some juice work on the first person weapon 2026-01-19 16:46:00 +01:00
27c67dbdd9 Some swords
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 18s
Create tag and build when new code gets to main / Export (push) Successful in 10m0s
2026-01-18 18:34:11 +01:00
98ed361546 reorganizing stuff
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 17s
Create tag and build when new code gets to main / Export (push) Successful in 9m26s
2026-01-18 17:04:28 +01:00
eb1c7f78fa flying knockback as well
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 19s
Create tag and build when new code gets to main / Export (push) Has been cancelled
2026-01-18 16:58:23 +01:00
35b9ea383c knockback component 2026-01-18 12:39:01 +01:00
9690280cd7 better spawning
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Export (push) Successful in 10m36s
2026-01-18 10:27:21 +01:00
65538495c4 spawning
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 19s
Create tag and build when new code gets to main / Export (push) Successful in 9m59s
2026-01-17 23:18:06 +01:00
561e026834 ok so this should be the way to go
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Export (push) Successful in 10m24s
2026-01-17 21:51:57 +01:00
7c74b8b5e5 removing broken ABC and refactoring enemy movement
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 19s
Create tag and build when new code gets to main / Export (push) Successful in 10m2s
2026-01-17 19:55:51 +01:00
f7705a6d57 moving files around
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 18s
Create tag and build when new code gets to main / Export (push) Successful in 10m3s
2026-01-17 17:58:23 +01:00
0dcf4a3f99 fixed damage composition issue 2026-01-17 17:47:14 +01:00
4ccdbc0ee6 broken composition and signals 2026-01-17 17:02:31 +01:00
0436053c62 broken composition and signals 2026-01-17 17:02:16 +01:00
6b97c226f1 setup damage types and modifiers as resources
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 19s
Create tag and build when new code gets to main / Export (push) Successful in 10m48s
2026-01-17 14:32:48 +01:00
b1e78df6c7 some damage interfacing 2026-01-17 11:02:17 +01:00
5908494977 two enemy types, ready to refactor 2026-01-17 10:10:14 +01:00
63529a11ae removed tool script stuff
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Export (push) Successful in 9m57s
2026-01-16 18:36:36 +01:00
255b87f991 basic interface and no success trying to use them in a Tool script 2026-01-16 18:35:58 +01:00
fd3eb35782 added enemy inputs as a resource
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 18s
Create tag and build when new code gets to main / Export (push) Successful in 9m48s
2026-01-16 11:18:57 +01:00
9e75193731 enemies can move, also changed and named a few collision layers
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Export (push) Successful in 10m18s
2026-01-16 11:05:02 +01:00
609078c584 enemy work
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 19s
Create tag and build when new code gets to main / Export (push) Successful in 10m32s
2026-01-15 21:47:17 +01:00
893126ef78 starting on enemies 2026-01-14 18:10:37 +01:00
6737668391 groundslide camera setup
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Export (push) Successful in 10m16s
2026-01-14 17:21:57 +01:00
ca77579168 ground sliding under stuff 2026-01-14 15:26:41 +01:00
c6559d593a fixed slope ground slide
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 19s
Create tag and build when new code gets to main / Export (push) Successful in 9m32s
2026-01-14 10:21:50 +01:00
e32dac9e6e broken sloped slide
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Export (push) Successful in 9m32s
2026-01-13 23:52:44 +01:00
30b4d1a2eb jumping from slides and some improvement on air gliding 2026-01-13 14:22:26 +01:00
2fa4ce68e7 revamped the dash, fixed an infinite jump issue and fixed buffered inputs
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Export (push) Successful in 10m15s
2026-01-13 11:18:56 +01:00
80e533d98e simple slam and changed wall hugging to only work when input is towards the wall 2026-01-13 09:43:58 +01:00
0e3e258fd3 basic slide and air glide mechanic
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 17s
Create tag and build when new code gets to main / Export (push) Successful in 10m40s
2026-01-12 17:47:32 +01:00
c7991198ea code cleanup 2026-01-12 16:43:52 +01:00
1a4b2f4c19 Ended with remapping and removed old junk
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 20s
Create tag and build when new code gets to main / Export (push) Successful in 10m34s
2026-01-12 15:41:43 +01:00
52a9c3f120 Remapped some inputs and added slide, slam and parry mappings 2026-01-12 14:58:43 +01:00
2301884418 added a wall jump section in the GYM 2026-01-12 12:01:40 +01:00
04054cfeae Added ground-like movement (i.e. air control when close to ground) and wall jumping away from the wall is always possible even when facing it 2026-01-12 11:52:35 +01:00
66be7838bb buffered inputs revamped and added mantle jumps and dashed
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 21s
Create tag and build when new code gets to main / Export (push) Successful in 10m30s
2026-01-11 18:14:53 +01:00
1eb65d1520 some shader work and improved mantle feel 2026-01-11 17:09:58 +01:00
f2a39316ba shader changes that don't do anything
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 19s
Create tag and build when new code gets to main / Export (push) Successful in 9m48s
2026-01-07 17:11:49 +01:00
fffd8c947b implementing jump input buffering on grounded
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 18s
Create tag and build when new code gets to main / Export (push) Successful in 9m47s
2026-01-07 09:58:18 +01:00
4508 changed files with 111928 additions and 24758 deletions

View File

@@ -0,0 +1,42 @@
name: Create tag and build when new code gets to main
run-name: Create tag and build when new code gets to main
on:
push:
branches:
- '**' # matches every branch
- '!main' # except main
- '!release/*' # except release branches
tags-ignore:
- "**"
env:
GODOT_VERSION: 4.6
GAME_NAME: MovementTests
ITCHIO_USERNAME: Minimata
ITCHIO_GAMEID: MovementTests
jobs:
Export:
runs-on: godot
steps:
- name: Checkout with LFS
uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main
- name: Run tests
uses: godot-gdunit-labs/gdUnit4-action@v1
with:
godot-version: ${GODOT_VERSION}
godot-net: true
godot-force-mono: true
dotnet-version: 'net9.0'
paths: |
res://tests/
timeout: 1
publish-report: false
upload-report: false
- name: Upload test report
uses: actions/upload-artifact@v3-node20
with:
name: Test Report
path: ${{ github.workspace }}/reports/test-result.html

View File

@@ -8,6 +8,7 @@ on:
- "**" - "**"
env: env:
GODOT_VERSION: 4.6
GAME_NAME: MovementTests GAME_NAME: MovementTests
ITCHIO_USERNAME: Minimata ITCHIO_USERNAME: Minimata
ITCHIO_GAMEID: MovementTests ITCHIO_GAMEID: MovementTests
@@ -21,6 +22,7 @@ jobs:
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
token: ${{ secrets.TOKEN }}
lfs: false lfs: false
- name: Remove buggy pre-push hook - name: Remove buggy pre-push hook
run: | run: |
@@ -36,81 +38,79 @@ jobs:
INITIAL_VERSION: 0.1.0 INITIAL_VERSION: 0.1.0
DEFAULT_BUMP: patch DEFAULT_BUMP: patch
Export: Test:
runs-on: ubuntu-latest runs-on: godot
needs: BumpTag # env:
container: # RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache
image: barichello/godot-ci:mono-4.5
steps: steps:
- name: Install node, curl and zip
run: |
apt update && apt -y install curl zip nodejs
- name: Checkout with LFS - name: Checkout with LFS
uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main
with:
checkout-version: 3
- name: Import resources and build solution - name: Run tests
uses: godot-gdunit-labs/gdUnit4-action@v1
with:
godot-version: ${GODOT_VERSION}
godot-net: true
godot-force-mono: true
dotnet-version: 'net9.0'
paths: |
res://tests/
timeout: 1
publish-report: false
upload-report: false
- name: Upload test report
uses: actions/upload-artifact@v3-node20
with:
name: Test Report
path: ${{ github.workspace }}/reports/test-result.html
Export:
runs-on: godot
needs:
- BumpTag
steps:
- name: Checkout with LFS
uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main
- name: Setup Godot
id: setup-godot
uses: https://git.game-dev.space/minimata/setup-godot.git@main
with:
godot-version: '4.6'
dotnet-version: 'net9.0'
- name: Remove GDUnit addon
run: | run: |
godot --headless --editor --build-solutions --quit --import --path $PWD rm -rf ${{ gitea.workspace }}/addons/gdUnit4
- name: Build Windows - name: Build Windows
run: | run: |
mkdir -v -p build/windows mkdir -v -p build/windows
godot --headless --verbose --build-solutions --export-release "Windows Desktop" build/windows/${{ env.GAME_NAME }}.exe ${{ steps.setup-godot.outputs.godot_bin }} --headless --verbose --export-release "Windows Desktop" build/windows/${{ env.GAME_NAME }}.exe
zip -r Windows.zip build/windows zip -r Windows.zip build/windows
- name: Upload to Itch
uses: KikimoraGames/itch-publish@v0.0.3
with:
butlerApiKey: ${{ secrets.BUTLER_TOKEN }}
itchUsername: ${{ env.ITCHIO_USERNAME }}
itchGameId: ${{ env.ITCHIO_GAMEID }}
buildNumber: ${{ needs.BumpTag.outputs.tag_name }}
gameData: Windows.zip
buildChannel: windows
- name: Build Windows ARM - name: Setup Butler
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: | run: |
mkdir -v -p build/windowsArm mkdir ./tools 2>/dev/null || true
godot --headless --verbose --build-solutions --export-release "Windows ARM" build/windowsArm/${{ env.GAME_NAME }}.exe pushd tools
zip -r WindowsArm.zip build/windowsArm curl -sSLfo ./butler.zip "https://broth.itch.zone/butler/linux-amd64/LATEST/archive/default"
- name: Upload to Itch unzip butler.zip
uses: KikimoraGames/itch-publish@v0.0.3 chmod +x ./butler
with: popd
butlerApiKey: ${{ secrets.BUTLER_TOKEN }} ./tools/butler -V
itchUsername: ${{ env.ITCHIO_USERNAME }}
itchGameId: ${{ env.ITCHIO_GAMEID }}
buildNumber: ${{ needs.BumpTag.outputs.tag_name }}
gameData: WindowsArm.zip
buildChannel: windows-arm
- name: Linux Build - name: Upload to itch.io
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: | run: |
mkdir -v -p build/linux versionArgument="--userversion ${{ needs.BumpTag.outputs.tag_name }}"
godot --headless --verbose --export-release "Linux/X11" build/linux/${{ env.GAME_NAME }}.x86_64 ./tools/butler push \
zip -r Linux.zip build/linux "Windows.zip" \
- name: Upload to Itch ${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:windows ${versionArgument}
uses: KikimoraGames/itch-publish@v0.0.3
with:
butlerApiKey: ${{ secrets.BUTLER_TOKEN }}
itchUsername: ${{ env.ITCHIO_USERNAME }}
itchGameId: ${{ env.ITCHIO_GAMEID }}
buildNumber: ${{ needs.BumpTag.outputs.tag_name }}
gameData: Linux.zip
buildChannel: linux
- name: Mac Build
run: |
mkdir -v -p build/mac
godot --headless --verbose --export-release "macOS" build/mac/${{ env.GAME_NAME }}.zip
zip -r Mac.zip build/mac
- name: Upload to Itch
uses: KikimoraGames/itch-publish@v0.0.3
with:
butlerApiKey: ${{ secrets.BUTLER_TOKEN }}
itchUsername: ${{ env.ITCHIO_USERNAME }}
itchGameId: ${{ env.ITCHIO_GAMEID }}
buildNumber: ${{ needs.BumpTag.outputs.tag_name }}
gameData: Mac.zip
buildChannel: mac

View File

@@ -0,0 +1,116 @@
name: Create tag and build when new code gets to main
run-name: Create tag and build when new code gets to main
on:
push:
branches:
- 'release/*' # only release branches
tags-ignore:
- "**"
env:
GODOT_VERSION: 4.6
GAME_NAME: MovementTests
ITCHIO_USERNAME: Minimata
ITCHIO_GAMEID: MovementTests
jobs:
ReleaseName:
runs-on: ubuntu-latest
if: ${{ contains(gitea.ref_name, 'release/') }}
outputs:
release_name: ${{ steps.split.outputs._1 }}
steps:
- uses: winterjung/split@v2
id: split
with:
msg: ${{ gitea.ref_name }}
separator: '/'
Release:
runs-on: godot
if: ${{ contains(gitea.ref_name, 'release/') }}
needs: ReleaseName
steps:
- name: Checkout with LFS
uses: https://git.game-dev.space/minimata/checkout-with-lfs.git@main
- name: Setup Godot
id: setup-godot
uses: https://git.game-dev.space/minimata/setup-godot.git@main
with:
godot-version: '4.6'
dotnet-version: 'net9.0'
- name: Setup Butler
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: |
mkdir ./tools 2>/dev/null || true
pushd tools
curl -sSLfo ./butler.zip "https://broth.itch.zone/butler/linux-amd64/LATEST/archive/default"
unzip butler.zip
chmod +x ./butler
popd
./tools/butler -V
- name: Build Windows
run: |
mkdir -v -p build/windows
godot --headless --verbose --build-solutions --export-release "Windows Desktop" build/windows/${{ env.GAME_NAME }}.exe
zip -r Windows.zip build/windows
- name: Upload Windows to itch.io
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: |
versionArgument="--userversion ${{ needs.ReleaseName.outputs.release_name }}"
./tools/butler push \
"Windows.zip" \
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:windows ${versionArgument}
- name: Build Windows ARM
run: |
mkdir -v -p build/windowsArm
godot --headless --verbose --build-solutions --export-release "Windows ARM" build/windowsArm/${{ env.GAME_NAME }}.exe
zip -r WindowsArm.zip build/windowsArm
- name: Upload Windows to itch.io
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: |
versionArgument="--userversion ${{ needs.ReleaseName.outputs.release_name }}"
./tools/butler push \
"WindowsArm.zip" \
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:windows-arm ${versionArgument}
- name: Linux Build
run: |
mkdir -v -p build/linux
godot --headless --verbose --export-release "Linux/X11" build/linux/${{ env.GAME_NAME }}.x86_64
zip -r Linux.zip build/linux
- name: Upload Windows to itch.io
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: |
versionArgument="--userversion ${{ needs.ReleaseName.outputs.release_name }}"
./tools/butler push \
"Linux.zip" \
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:linux ${versionArgument}
- name: Mac Build
run: |
mkdir -v -p build/mac
godot --headless --verbose --export-release "macOS" build/mac/${{ env.GAME_NAME }}.zip
zip -r Mac.zip build/mac
- name: Upload Windows to itch.io
shell: bash
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_TOKEN }}
run: |
versionArgument="--userversion ${{ needs.ReleaseName.outputs.release_name }}"
./tools/butler push \
"Mac.zip" \
${{ env.ITCHIO_USERNAME }}/${{ env.ITCHIO_GAMEID }}:mac ${versionArgument}

1
.gitignore vendored
View File

@@ -10,6 +10,7 @@
.import/ .import/
/builds /builds
/communication
# Imported translations (automatically generated from CSV files) # Imported translations (automatically generated from CSV files)
*.translation *.translation

55
.runsettings Normal file
View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<RunConfiguration>
<MaxCpuCount>1</MaxCpuCount>
<ResultsDirectory>./TestResults</ResultsDirectory>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<TestSessionTimeout>180000</TestSessionTimeout>
<TreatNoTestsAsError>true</TreatNoTestsAsError>
<EnvironmentVariables>
<GODOT_BIN>d:\development\Godot_v4.5-stable_mono_win64\Godot_v4.5-stable_mono_win64.exe</GODOT_BIN>
</EnvironmentVariables>
</RunConfiguration>
<LoggerRunSettings>
<Loggers>
<Logger friendlyName="console" enabled="True">
<Configuration>
<Verbosity>detailed</Verbosity>
</Configuration>
</Logger>
<Logger friendlyName="html" enabled="True">
<Configuration>
<LogFileName>test-result.html</LogFileName>
</Configuration>
</Logger>
<Logger friendlyName="trx" enabled="True">
<Configuration>
<LogFileName>test-result.trx</LogFileName>
</Configuration>
</Logger>
</Loggers>
</LoggerRunSettings>
<GdUnit4>
<!-- Additional Godot runtime parameters. These are passed to the Godot executable when running tests.-->
<Parameters>"--verbose"</Parameters>
<!-- Controls the display name format of test cases in the test results.
Allowed values:
- SimpleName: Uses only the method name (e.g., "TestMethod")
- FullyQualifiedName: Uses the full path including class and method name (e.g., "MyNamespace.MyClass.TestMethod")
Default: SimpleName -->
<DisplayName>FullyQualifiedName</DisplayName>
<!-- When set to true, standard output (stdout) from test cases is captured
and included in the test result. This can be useful for debugging. -->
<CaptureStdOut>true</CaptureStdOut>
<!-- The maximum duration allowed for a Godot project compilation process in milliseconds.
After this timeout period expires, the compilation process is forcefully terminated.
For large or complex Godot projects, you may need to increase this value.
Default: 20000 (20 seconds) -->
<CompileProcessTimeout>20000</CompileProcessTimeout>
</GdUnit4>
</RunSettings>

View File

@@ -1,131 +1,143 @@
<Project Sdk="Godot.NET.Sdk/4.5.0"> <Project Sdk="Godot.NET.Sdk/4.6.0">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading> <EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>Movementtests</RootNamespace> <RootNamespace>Movementtests</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="export_presets.cfg" /> <Content Include=".runsettings"/>
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png" /> <Content Include="export_presets.cfg"/>
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png.import" /> <Content Include="menus\assets\git_logo\Git-Logo-2Color.png"/>
<Content Include="menus\assets\git_logo\LICENSE.txt" /> <Content Include="menus\assets\git_logo\Git-Logo-2Color.png.import"/>
<Content Include="menus\assets\godot_engine_logo\LICENSE.txt" /> <Content Include="menus\assets\git_logo\LICENSE.txt"/>
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png" /> <Content Include="menus\assets\godot_engine_logo\LICENSE.txt"/>
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png.import" /> <Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png"/>
<Content Include="menus\assets\icon.png" /> <Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png.import"/>
<Content Include="menus\assets\icon.png.import" /> <Content Include="menus\assets\icon.png"/>
<Content Include="menus\ATTRIBUTION.md" /> <Content Include="menus\assets\icon.png.import"/>
<Content Include="menus\resources\themes\expedition.tres" /> <Content Include="menus\ATTRIBUTION.md"/>
<Content Include="menus\resources\themes\gravity.tres" /> <Content Include="menus\resources\themes\expedition.tres"/>
<Content Include="menus\resources\themes\grow.tres" /> <Content Include="menus\resources\themes\gravity.tres"/>
<Content Include="menus\resources\themes\lab.tres" /> <Content Include="menus\resources\themes\grow.tres"/>
<Content Include="menus\resources\themes\lore.tres" /> <Content Include="menus\resources\themes\lab.tres"/>
<Content Include="menus\resources\themes\steal_this_theme.tres" /> <Content Include="menus\resources\themes\lore.tres"/>
<Content Include="menus\scenes\credits\scrollable_credits.gd" /> <Content Include="menus\resources\themes\steal_this_theme.tres"/>
<Content Include="menus\scenes\credits\scrollable_credits.gd.uid" /> <Content Include="menus\scenes\credits\scrollable_credits.gd"/>
<Content Include="menus\scenes\credits\scrollable_credits.tscn" /> <Content Include="menus\scenes\credits\scrollable_credits.gd.uid"/>
<Content Include="menus\scenes\credits\scrolling_credits.gd" /> <Content Include="menus\scenes\credits\scrollable_credits.tscn"/>
<Content Include="menus\scenes\credits\scrolling_credits.gd.uid" /> <Content Include="menus\scenes\credits\scrolling_credits.gd"/>
<Content Include="menus\scenes\credits\scrolling_credits.tscn" /> <Content Include="menus\scenes\credits\scrolling_credits.gd.uid"/>
<Content Include="menus\scenes\end_credits\end_credits.gd" /> <Content Include="menus\scenes\credits\scrolling_credits.tscn"/>
<Content Include="menus\scenes\end_credits\end_credits.gd.uid" /> <Content Include="menus\scenes\end_credits\end_credits.gd"/>
<Content Include="menus\scenes\end_credits\end_credits.tscn" /> <Content Include="menus\scenes\end_credits\end_credits.gd.uid"/>
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd" /> <Content Include="menus\scenes\end_credits\end_credits.tscn"/>
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd.uid" /> <Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd"/>
<Content Include="menus\scenes\game_scene\game_ui.tscn" /> <Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd.uid"/>
<Content Include="menus\scenes\game_scene\input_display_label.gd" /> <Content Include="menus\scenes\game_scene\game_ui.tscn"/>
<Content Include="menus\scenes\game_scene\input_display_label.gd.uid" /> <Content Include="menus\scenes\game_scene\input_display_label.gd"/>
<Content Include="menus\scenes\game_scene\levels\level.gd" /> <Content Include="menus\scenes\game_scene\input_display_label.gd.uid"/>
<Content Include="menus\scenes\game_scene\levels\level.gd.uid" /> <Content Include="menus\scenes\game_scene\levels\level.gd"/>
<Content Include="menus\scenes\game_scene\levels\level_1.tscn" /> <Content Include="menus\scenes\game_scene\levels\level.gd.uid"/>
<Content Include="menus\scenes\game_scene\levels\level_2.tscn" /> <Content Include="menus\scenes\game_scene\levels\level_1.tscn"/>
<Content Include="menus\scenes\game_scene\levels\level_3.tscn" /> <Content Include="menus\scenes\game_scene\levels\level_2.tscn"/>
<Content Include="menus\scenes\game_scene\tutorials\tutorial_1.tscn" /> <Content Include="menus\scenes\game_scene\levels\level_3.tscn"/>
<Content Include="menus\scenes\game_scene\tutorials\tutorial_2.tscn" /> <Content Include="menus\scenes\game_scene\tutorials\tutorial_1.tscn"/>
<Content Include="menus\scenes\game_scene\tutorials\tutorial_3.tscn" /> <Content Include="menus\scenes\game_scene\tutorials\tutorial_2.tscn"/>
<Content Include="menus\scenes\game_scene\tutorial_manager.gd" /> <Content Include="menus\scenes\game_scene\tutorials\tutorial_3.tscn"/>
<Content Include="menus\scenes\game_scene\tutorial_manager.gd.uid" /> <Content Include="menus\scenes\game_scene\tutorial_manager.gd"/>
<Content Include="menus\scenes\loading_screen\level_loading_screen.tscn" /> <Content Include="menus\scenes\game_scene\tutorial_manager.gd.uid"/>
<Content Include="menus\scenes\loading_screen\loading_screen.gd" /> <Content Include="menus\scenes\loading_screen\level_loading_screen.tscn"/>
<Content Include="menus\scenes\loading_screen\loading_screen.gd.uid" /> <Content Include="menus\scenes\loading_screen\loading_screen.gd"/>
<Content Include="menus\scenes\loading_screen\loading_screen.tscn" /> <Content Include="menus\scenes\loading_screen\loading_screen.gd.uid"/>
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd" /> <Content Include="menus\scenes\loading_screen\loading_screen.tscn"/>
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd.uid" /> <Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd"/>
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.tscn" /> <Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd.uid"/>
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd" /> <Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.tscn"/>
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd.uid" /> <Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd"/>
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.tscn" /> <Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd.uid"/>
<Content Include="menus\scenes\menus\main_menu\main_menu.gd" /> <Content Include="menus\scenes\menus\level_select_menu\level_select_menu.tscn"/>
<Content Include="menus\scenes\menus\main_menu\main_menu.gd.uid" /> <Content Include="menus\scenes\menus\main_menu\main_menu.gd"/>
<Content Include="menus\scenes\menus\main_menu\main_menu.tscn" /> <Content Include="menus\scenes\menus\main_menu\main_menu.gd.uid"/>
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd" /> <Content Include="menus\scenes\menus\main_menu\main_menu.tscn"/>
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd.uid" /> <Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd"/>
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.tscn" /> <Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd.uid"/>
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd" /> <Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.tscn"/>
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd.uid" /> <Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd"/>
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.tscn" /> <Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd.uid"/>
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd" /> <Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.tscn"/>
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd.uid" /> <Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd"/>
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.tscn" /> <Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd.uid"/>
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd" /> <Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.tscn"/>
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd.uid" /> <Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd"/>
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.tscn" /> <Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd.uid"/>
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd" /> <Content Include="menus\scenes\menus\options_menu\game\game_options_menu.tscn"/>
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd.uid" /> <Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd"/>
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.tscn" /> <Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd.uid"/>
<Content Include="menus\scenes\menus\options_menu\input\input_extras_menu.tscn" /> <Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.tscn"/>
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd" /> <Content Include="menus\scenes\menus\options_menu\input\input_extras_menu.tscn"/>
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd.uid" /> <Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd"/>
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.tscn" /> <Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd.uid"/>
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu_with_mouse_sensitivity.tscn" /> <Content Include="menus\scenes\menus\options_menu\input\input_options_menu.tscn"/>
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd" /> <Content Include="menus\scenes\menus\options_menu\input\input_options_menu_with_mouse_sensitivity.tscn"/>
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd.uid" /> <Content Include="menus\scenes\menus\options_menu\master_options_menu.gd"/>
<Content Include="menus\scenes\menus\options_menu\master_options_menu.tscn" /> <Content Include="menus\scenes\menus\options_menu\master_options_menu.gd.uid"/>
<Content Include="menus\scenes\menus\options_menu\master_options_menu_with_tabs.tscn" /> <Content Include="menus\scenes\menus\options_menu\master_options_menu.tscn"/>
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd" /> <Content Include="menus\scenes\menus\options_menu\master_options_menu_with_tabs.tscn"/>
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd.uid" /> <Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd"/>
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.tscn" /> <Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd.uid"/>
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd" /> <Content Include="menus\scenes\menus\options_menu\mini_options_menu.tscn"/>
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd.uid" /> <Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd"/>
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.tscn" /> <Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd.uid"/>
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd" /> <Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.tscn"/>
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd.uid" /> <Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd"/>
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.tscn" /> <Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd.uid"/>
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu_with_extras.tscn" /> <Content Include="menus\scenes\menus\options_menu\video\video_options_menu.tscn"/>
<Content Include="menus\scenes\opening\opening.gd" /> <Content Include="menus\scenes\menus\options_menu\video\video_options_menu_with_extras.tscn"/>
<Content Include="menus\scenes\opening\opening.gd.uid" /> <Content Include="menus\scenes\opening\opening.gd"/>
<Content Include="menus\scenes\opening\opening.tscn" /> <Content Include="menus\scenes\opening\opening.gd.uid"/>
<Content Include="menus\scenes\opening\opening_with_logo.tscn" /> <Content Include="menus\scenes\opening\opening.tscn"/>
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd" /> <Content Include="menus\scenes\opening\opening_with_logo.tscn"/>
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd.uid" /> <Content Include="menus\scenes\overlaid_menus\game_won_menu.gd"/>
<Content Include="menus\scenes\overlaid_menus\game_won_menu.tscn" /> <Content Include="menus\scenes\overlaid_menus\game_won_menu.gd.uid"/>
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd" /> <Content Include="menus\scenes\overlaid_menus\game_won_menu.tscn"/>
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd.uid" /> <Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd"/>
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.tscn" /> <Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd.uid"/>
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd" /> <Content Include="menus\scenes\overlaid_menus\level_lost_menu.tscn"/>
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd.uid" /> <Content Include="menus\scenes\overlaid_menus\level_won_menu.gd"/>
<Content Include="menus\scenes\overlaid_menus\level_won_menu.tscn" /> <Content Include="menus\scenes\overlaid_menus\level_won_menu.gd.uid"/>
<Content Include="menus\scenes\overlaid_menus\mini_options_overlaid_menu.tscn" /> <Content Include="menus\scenes\overlaid_menus\level_won_menu.tscn"/>
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd" /> <Content Include="menus\scenes\overlaid_menus\mini_options_overlaid_menu.tscn"/>
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd.uid" /> <Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd"/>
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.tscn" /> <Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd.uid"/>
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd" /> <Content Include="menus\scenes\overlaid_menus\overlaid_menu.tscn"/>
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd.uid" /> <Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd"/>
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.tscn" /> <Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd.uid"/>
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd" /> <Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.tscn"/>
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd.uid" /> <Content Include="menus\scenes\overlaid_menus\pause_menu.gd"/>
<Content Include="menus\scenes\overlaid_menus\pause_menu.tscn" /> <Content Include="menus\scenes\overlaid_menus\pause_menu.gd.uid"/>
<Content Include="menus\scripts\game_state.gd" /> <Content Include="menus\scenes\overlaid_menus\pause_menu.tscn"/>
<Content Include="menus\scripts\game_state.gd.uid" /> <Content Include="menus\scripts\game_state.gd"/>
<Content Include="menus\scripts\level_list_and_state_manager.gd" /> <Content Include="menus\scripts\game_state.gd.uid"/>
<Content Include="menus\scripts\level_list_and_state_manager.gd.uid" /> <Content Include="menus\scripts\level_list_and_state_manager.gd"/>
<Content Include="menus\scripts\level_state.gd" /> <Content Include="menus\scripts\level_list_and_state_manager.gd.uid"/>
<Content Include="menus\scripts\level_state.gd.uid" /> <Content Include="menus\scripts\level_state.gd"/>
<Content Include="menus\scripts\level_state.gd.uid"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="addons\" /> <Folder Include="addons\"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="RustyOptions" Version="0.10.1" /> <PackageReference Include="RustyOptions" Version="0.10.1"/>
</ItemGroup> </ItemGroup>
<!-- gdUnit4 package dependencies -->
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0"/>
<PackageReference Include="gdUnit4.api" Version="5.1.0-rc3"/>
<PackageReference Include="gdUnit4.test.adapter" Version="3.0.0"/>
<PackageReference Include="gdUnit4.analyzers" Version="1.0.0">
<PrivateAssets>none</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<Import Project="addons/forge/Forge.props"/>
</Project> </Project>

142
Movement tests.csproj.old.1 Normal file
View File

@@ -0,0 +1,142 @@
<Project Sdk="Godot.NET.Sdk/4.5.0">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>Movementtests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Content Include=".runsettings" />
<Content Include="export_presets.cfg" />
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png" />
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png.import" />
<Content Include="menus\assets\git_logo\LICENSE.txt" />
<Content Include="menus\assets\godot_engine_logo\LICENSE.txt" />
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png" />
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png.import" />
<Content Include="menus\assets\icon.png" />
<Content Include="menus\assets\icon.png.import" />
<Content Include="menus\ATTRIBUTION.md" />
<Content Include="menus\resources\themes\expedition.tres" />
<Content Include="menus\resources\themes\gravity.tres" />
<Content Include="menus\resources\themes\grow.tres" />
<Content Include="menus\resources\themes\lab.tres" />
<Content Include="menus\resources\themes\lore.tres" />
<Content Include="menus\resources\themes\steal_this_theme.tres" />
<Content Include="menus\scenes\credits\scrollable_credits.gd" />
<Content Include="menus\scenes\credits\scrollable_credits.gd.uid" />
<Content Include="menus\scenes\credits\scrollable_credits.tscn" />
<Content Include="menus\scenes\credits\scrolling_credits.gd" />
<Content Include="menus\scenes\credits\scrolling_credits.gd.uid" />
<Content Include="menus\scenes\credits\scrolling_credits.tscn" />
<Content Include="menus\scenes\end_credits\end_credits.gd" />
<Content Include="menus\scenes\end_credits\end_credits.gd.uid" />
<Content Include="menus\scenes\end_credits\end_credits.tscn" />
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd" />
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd.uid" />
<Content Include="menus\scenes\game_scene\game_ui.tscn" />
<Content Include="menus\scenes\game_scene\input_display_label.gd" />
<Content Include="menus\scenes\game_scene\input_display_label.gd.uid" />
<Content Include="menus\scenes\game_scene\levels\level.gd" />
<Content Include="menus\scenes\game_scene\levels\level.gd.uid" />
<Content Include="menus\scenes\game_scene\levels\level_1.tscn" />
<Content Include="menus\scenes\game_scene\levels\level_2.tscn" />
<Content Include="menus\scenes\game_scene\levels\level_3.tscn" />
<Content Include="menus\scenes\game_scene\tutorials\tutorial_1.tscn" />
<Content Include="menus\scenes\game_scene\tutorials\tutorial_2.tscn" />
<Content Include="menus\scenes\game_scene\tutorials\tutorial_3.tscn" />
<Content Include="menus\scenes\game_scene\tutorial_manager.gd" />
<Content Include="menus\scenes\game_scene\tutorial_manager.gd.uid" />
<Content Include="menus\scenes\loading_screen\level_loading_screen.tscn" />
<Content Include="menus\scenes\loading_screen\loading_screen.gd" />
<Content Include="menus\scenes\loading_screen\loading_screen.gd.uid" />
<Content Include="menus\scenes\loading_screen\loading_screen.tscn" />
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd" />
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd.uid" />
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.tscn" />
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd" />
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd.uid" />
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.tscn" />
<Content Include="menus\scenes\menus\main_menu\main_menu.gd" />
<Content Include="menus\scenes\menus\main_menu\main_menu.gd.uid" />
<Content Include="menus\scenes\menus\main_menu\main_menu.tscn" />
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd" />
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd.uid" />
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.tscn" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.tscn" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd" />
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.tscn" />
<Content Include="menus\scenes\menus\options_menu\input\input_extras_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu_with_mouse_sensitivity.tscn" />
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\master_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\master_options_menu_with_tabs.tscn" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.tscn" />
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu_with_extras.tscn" />
<Content Include="menus\scenes\opening\opening.gd" />
<Content Include="menus\scenes\opening\opening.gd.uid" />
<Content Include="menus\scenes\opening\opening.tscn" />
<Content Include="menus\scenes\opening\opening_with_logo.tscn" />
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\game_won_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\level_won_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\mini_options_overlaid_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.tscn" />
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\pause_menu.tscn" />
<Content Include="menus\scripts\game_state.gd" />
<Content Include="menus\scripts\game_state.gd.uid" />
<Content Include="menus\scripts\level_list_and_state_manager.gd" />
<Content Include="menus\scripts\level_list_and_state_manager.gd.uid" />
<Content Include="menus\scripts\level_state.gd" />
<Content Include="menus\scripts\level_state.gd.uid" />
</ItemGroup>
<ItemGroup>
<Folder Include="addons\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="RustyOptions" Version="0.10.1" />
</ItemGroup>
<!-- gdUnit4 package dependencies -->
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0"/>
<PackageReference Include="gdUnit4.api" Version="5.1.0-rc3"/>
<PackageReference Include="gdUnit4.test.adapter" Version="3.0.0"/>
<PackageReference Include="gdUnit4.analyzers" Version="1.0.0">
<PrivateAssets>none</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -1,4 +1,12 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAction_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F7c0f83388bfc4d2c9d09befcec9dd79bc90908_003Fb8_003F4d300c4d_003FAction_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAction_00602_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F7c0f83388bfc4d2c9d09befcec9dd79bc90908_003F87_003Fded27e2d_003FAction_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnemy_005FScriptMethods_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F8e71dc81611862c01a2cb998a1f327de14747655_003FEnemy_005FScriptMethods_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F716d154fef5cbe863cd637bd32beda6e3cec5f12e8fed2dc5b2d8149a0d558ab_003FNode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fdf73a4db74df89d59655c5fb6326406f47fbfa9af1fa81518fe0a07c49d34133_003FNode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fdf73a4db74df89d59655c5fb6326406f47fbfa9af1fa81518fe0a07c49d34133_003FNode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASceneTree_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F8d6960554e939a669841b1ece03d27df4ab42f92bb80be3767eaec8cdaccf84b_003FSceneTree_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASceneTree_002Ecs_002Fl_003AC_0021_003FUsers_003FMinimata_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F8d6960554e939a669841b1ece03d27df4ab42f92bb80be3767eaec8cdaccf84b_003FSceneTree_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=dd9a7ac6_002Dbb9b_002D4001_002Db145_002D15e6509b7e78/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;Solution /&gt;&#xD;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Housekeeping/UnitTestingMru/UnitTestRunner/RunConfigurationFilename/@EntryValue">D:\Godot\Projects\movement-tests\.runsettings</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=floorplane/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/UserDictionary/Words/=floorplane/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -0,0 +1,42 @@
# CSG Toolkit Settings
The CSG Toolkit now uses Godot's built-in **ProjectSettings** system instead of a custom configuration file.
## Accessing Settings
Settings can be accessed in two ways:
1. **Through the CSG Toolkit config window** (recommended for users)
- Click the config button in the CSG Toolkit sidebar
- Modify settings using the UI
- Click "Save" to persist changes
2. **Directly in Project Settings** (for advanced users)
- Go to Project → Project Settings
- Navigate to the "Addons" section
- Look for `addons/csg_toolkit/*` settings
## Available Settings
| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `addons/csg_toolkit/default_behavior` | Enum | Sibling | Default insertion behavior (Sibling or Child) |
| `addons/csg_toolkit/action_key` | Key | Shift | Primary action key for shortcuts |
| `addons/csg_toolkit/secondary_action_key` | Key | Alt | Secondary action key for behavior inversion |
| `addons/csg_toolkit/auto_hide` | Boolean | true | Auto-hide sidebar when no CSG nodes selected |
## Migration from Old Config
If you were using an older version with `csg_toolkit_config.cfg`:
- The old config file is no longer used
- Settings are now stored in `project.godot`
- Settings will be initialized with defaults on first run
- You'll need to reconfigure your preferences if migrating
## Advantages of ProjectSettings
- Settings are version-controlled with your project
- Visible and editable in Project Settings editor
- Better integration with Godot's editor
- No separate config file to manage
- Settings persist per-project automatically

View File

@@ -0,0 +1,57 @@
@tool
class_name CsgToolkit extends EditorPlugin
@onready var config: CsgTkConfig:
get:
return get_tree().root.get_node_or_null(AUTOLOAD_NAME) as CsgTkConfig
var sidebar: CSGSideToolkitBar
var topbar: CSGTopToolkitBar
var shortcut_manager: CsgShortcutManager
const AUTOLOAD_NAME = "CsgToolkitAutoload"
static var csg_plugin_path
static var undo_manager: EditorUndoRedoManager
func _enter_tree():
# Config
add_autoload_singleton(AUTOLOAD_NAME, "res://addons/csg_toolkit/scripts/csg_toolkit_config.gd")
csg_plugin_path = get_path()
undo_manager = get_undo_redo()
# Nodes
add_custom_type("CSGRepeater3D", "CSGCombiner3D", preload("res://addons/csg_toolkit/scripts/csg_repeater_3d.gd"), null)
add_custom_type("CSGSpreader3D", "CSGCombiner3D", preload("res://addons/csg_toolkit/scripts/csg_spreader_3d.gd"), null)
# Sidebar
var sidebarScene = preload("res://addons/csg_toolkit/scenes/csg_side_toolkit_bar.tscn")
sidebar = sidebarScene.instantiate()
add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, sidebar)
# Ensure sidebar can be found by shortcut manager if needed
sidebar.add_to_group("CSGSideToolkit")
# Topbar
var topbarScene = preload("res://addons/csg_toolkit/scenes/csg_top_toolkit_bar.tscn")
topbar = topbarScene.instantiate()
add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, topbar)
# Shortcut Manager
shortcut_manager = CsgShortcutManager.new()
get_tree().root.add_child(shortcut_manager)
shortcut_manager.sidebar = sidebar
func _exit_tree():
remove_custom_type("CSGRepeater3D")
remove_custom_type("CSGSpreader3D")
undo_manager = null
remove_autoload_singleton(AUTOLOAD_NAME)
# Note: ProjectSettings for CSG Toolkit are preserved in project.godot
# They can be manually removed from Project Settings if desired
remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, sidebar)
sidebar.free()
remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, topbar)
topbar.free()
if shortcut_manager and is_instance_valid(shortcut_manager):
shortcut_manager.queue_free()

View File

@@ -0,0 +1 @@
uid://cmwnf4lkck6mm

View File

@@ -0,0 +1,71 @@
[gd_scene format=3 uid="uid://bltlelosbn4ky"]
[ext_resource type="Script" uid="uid://c68dxahp0v5xg" path="res://addons/csg_toolkit/scripts/csg_repeater_3d.gd" id="1_mnwru"]
[ext_resource type="Script" uid="uid://3il6xs7cr7gj" path="res://addons/csg_toolkit/scripts/patterns/noise_pattern.gd" id="2_mnwru"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_i7qq0"]
sky_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
ground_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
[sub_resource type="Sky" id="Sky_lhy2n"]
sky_material = SubResource("ProceduralSkyMaterial_i7qq0")
[sub_resource type="Environment" id="Environment_mnwru"]
background_mode = 2
sky = SubResource("Sky_lhy2n")
tonemap_mode = 2
glow_enabled = true
[sub_resource type="Resource" id="Resource_fg620"]
script = ExtResource("2_mnwru")
bounds = Vector3(10, 1, 10)
noise_threshold = 0.9
noise_frequency = 16.0
use_template_size = true
metadata/_custom_type_script = "uid://3il6xs7cr7gj"
[node name="Node" type="Node3D" unique_id=1351203367]
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=190667961]
environment = SubResource("Environment_mnwru")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=656509290]
transform = Transform3D(-0.8660254, -0.43301278, 0.25, 0, 0.49999997, 0.86602545, -0.50000006, 0.75, -0.43301266, 0, 0, 0)
shadow_enabled = true
[node name="Camera3D" type="Camera3D" parent="." unique_id=1763227161]
transform = Transform3D(-0.93186235, 0, -0.36281216, 0, 1, 0, 0.36281216, 0, -0.9318623, -5.236788, 3.9650297, -6.404826)
current = true
[node name="CSGRepeater3D" type="CSGCombiner3D" parent="." unique_id=873109275]
script = ExtResource("1_mnwru")
template_node_path = NodePath("CSGCombiner3D2")
estimated_instances = 27
pattern = SubResource("Resource_fg620")
randomize_rotation = false
randomize_rot_x = false
randomize_rot_y = false
randomize_rot_z = false
randomize_scale = false
scale_variance = 0.15
randomize_scale_x = false
randomize_scale_y = false
randomize_scale_z = false
metadata/_custom_type_script = "uid://c68dxahp0v5xg"
[node name="CSGCombiner3D2" type="CSGCombiner3D" parent="CSGRepeater3D" unique_id=109113911]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.31901503, -0.3383956, -7.8776984)
visible = false
[node name="CSGBox3D" type="CSGBox3D" parent="CSGRepeater3D/CSGCombiner3D2" unique_id=110356410]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.48632813)
size = Vector3(1, 1, 1.9726563)
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGRepeater3D/CSGCombiner3D2/CSGBox3D" unique_id=194604460]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.51814646, 0.37408447, -0.24641752)
size = Vector3(1, 1.748169, 1)
[node name="CSGBox3D" type="CSGBox3D" parent="CSGRepeater3D/CSGCombiner3D2/CSGBox3D" unique_id=763032662]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4555664, 0.08105469)
operation = 2
size = Vector3(1, 1.9111328, 0.8378906)

View File

@@ -0,0 +1,3 @@
[gd_resource type="ShaderMaterial" format=3 uid="uid://bvlrolerfilhd"]
[resource]

Binary file not shown.

View File

@@ -0,0 +1,7 @@
[plugin]
name="CSG Toolkit - Enhance Your Blockout Speed"
description="Enhance your blockout workflow with the CSG Toolkit. This tool adds quick access buttons to the left toolbar for easy addition of CSG nodes. By pressing SHIFT, you can efficiently add the selected CSG as a child node. The toolkit also automatically preselects operations, streamlining your process. Additionally, shortcuts are available for CSG nodes or children of CSG combiners, further boosting your productivity."
author="LuckyTepot"
version="1.7.0"
script="csg_toolkit.gd"

BIN
addons/csg_toolkit/res/demo-image.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d3biubbsy5rmr"
path="res://.godot/imported/demo-image.png-239231e5e25f0563a6646f0443acfa4e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/demo-image.png"
dest_files=["res://.godot/imported/demo-image.png-239231e5e25f0563a6646f0443acfa4e.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
addons/csg_toolkit/res/icon.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c10bvkyvb2xdd"
path="res://.godot/imported/icon.png-4ba3e0a5d510aafd8e055683d8699174.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/icon.png"
dest_files=["res://.godot/imported/icon.png-4ba3e0a5d510aafd8e055683d8699174.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@@ -0,0 +1 @@
<svg height="16" width="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="m8 2 6 3v6l-6 3-6-3V5zm0 12V8l6-3M8 8 2 5" fill="none" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg>

After

Width:  |  Height:  |  Size: 467 B

View File

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cjxx30pcamj36"
path="res://.godot/imported/box.svg-24afd61c89464af4af9f4d845ca57b9a.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/icons/box.svg"
dest_files=["res://.godot/imported/box.svg-24afd61c89464af4af9f4d845ca57b9a.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=true

View File

@@ -0,0 +1,4 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="24" cy="24" r="10" stroke="#fc7f7f" stroke-width="4"/>
<circle cx="24" cy="24" r="14" stroke="#fc7f7f" stroke-width="4" stroke-dasharray="8 2"/>
</svg>

After

Width:  |  Height:  |  Size: 260 B

View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://clgooji83dl4u"
path="res://.godot/imported/config.svg-dd635575de604576026d414163f67266.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/icons/config.svg"
dest_files=["res://.godot/imported/config.svg-dd635575de604576026d414163f67266.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M2 4v8a6 2 0 0 0 12 0V4A6 2 0 0 0 2 4a6 2 0 0 0 12 0" stroke-width="2" fill="none" stroke="#fc7f7f" mask="url(#a)"/></svg>

After

Width:  |  Height:  |  Size: 478 B

View File

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dioxt3oaqvsi3"
path="res://.godot/imported/cyliner.svg-50489df51cf1e8dde56b511c63cd2437.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/icons/cyliner.svg"
dest_files=["res://.godot/imported/cyliner.svg-50489df51cf1e8dde56b511c63cd2437.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=true

View File

@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><linearGradient x2="0" y2="16" gradientUnits="userSpaceOnUse" id="a"><stop offset=".1875" stop-color="#ff4545"/><stop stop-color="#ffe345"/><stop offset=".3125" stop-color="#ffe345"/><stop stop-color="#80ff45"/><stop offset=".4375" stop-color="#80ff45"/><stop stop-color="#45ffa2"/><stop offset=".5625" stop-color="#45ffa2"/><stop stop-color="#45d7ff"/><stop offset=".6875" stop-color="#45d7ff"/><stop stop-color="#8045ff"/><stop offset=".8125" stop-color="#8045ff"/><stop stop-color="#ff4596"/></linearGradient><path d="m8 2 6 3v6l-6 3-6-3V5zm0 12V8l6-3M8 8 2 5" fill="none" stroke-width="2" stroke-linejoin="round" stroke="url(#a)"/></svg>

After

Width:  |  Height:  |  Size: 724 B

View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cg5jbylrlg00x"
path="res://.godot/imported/empty-material.svg-3f6f61e9606d6bae9d37867d9ac3ad0b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/icons/empty-material.svg"
dest_files=["res://.godot/imported/empty-material.svg-3f6f61e9606d6bae9d37867d9ac3ad0b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1,4 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.5 16.204C19.8099 17.7602 18 20.6687 18 24C18 27.3313 19.8099 30.2398 22.5 31.796C25.1901 30.2398 27 27.3313 27 24C27 20.6687 25.1901 17.7602 22.5 16.204Z" fill="#fc7f7f"/>
<path d="M22.5 16.204C19.8099 17.7602 18 20.6687 18 24C18 27.3313 19.8099 30.2398 22.5 31.796M22.5 16.204C23.8238 15.4383 25.3607 15 27 15C31.9706 15 36 19.0294 36 24C36 28.9706 31.9706 33 27 33C25.3607 33 23.8238 32.5617 22.5 31.796M22.5 16.204C21.1762 15.4383 19.6393 15 18 15C13.0294 15 9 19.0294 9 24C9 28.9706 13.0294 33 18 33C19.6393 33 21.1762 32.5617 22.5 31.796M22.5 16.204C25.1901 17.7602 27 20.6687 27 24C27 27.3313 25.1901 30.2398 22.5 31.796" stroke="#fc7f7f"/>
</svg>

After

Width:  |  Height:  |  Size: 763 B

View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://2kohi3tb3c70"
path="res://.godot/imported/intersection.svg-a7d56bc571616c4c8343b7fe260d4607.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/icons/intersection.svg"
dest_files=["res://.godot/imported/intersection.svg-a7d56bc571616c4c8343b7fe260d4607.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M4.73 2A2 2 0 1 0 2 4.73v6.541A2 2 0 1 0 4.729 14H8v-2H4.729A2 2 0 0 0 4 11.271V5.415l4.914 4.916A2 2 0 0 1 9.998 10a2 2 0 0 1 .33-1.084L5.414 4h5.856a2 2 0 0 0 .73.729V8h2V4.729A2 2 0 1 0 11.27 2z" fill="#fc7f7f"/><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/></svg>

After

Width:  |  Height:  |  Size: 459 B

View File

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bnpu878eanspj"
path="res://.godot/imported/mesh.svg-156308ec8458ed846eae996bc130ccbc.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/icons/mesh.svg"
dest_files=["res://.godot/imported/mesh.svg-156308ec8458ed846eae996bc130ccbc.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=true

View File

@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8 2 6 3.5v5L8 14l-6-3.5v-5h6zm6 3.5L8 9 2 5.5M8 9v5" fill="none" stroke-linejoin="round" stroke-width="2" stroke="#fc7f7f"/></svg>

After

Width:  |  Height:  |  Size: 223 B

View File

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cjps2pofcsfc0"
path="res://.godot/imported/polygon.svg-9526fe3ac0cb0791e6339d67ae6afbcf.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/icons/polygon.svg"
dest_files=["res://.godot/imported/polygon.svg-9526fe3ac0cb0791e6339d67ae6afbcf.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=true

View File

@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M8 2a6 6 0 0 0 0 12A6 6 0 0 0 8 2v12M2.05 7.4a6 2 0 0 0 11.9 0" fill="none" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg>

After

Width:  |  Height:  |  Size: 488 B

View File

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://kpld1ou63wlf"
path="res://.godot/imported/sphere.svg-c7c8c1311207415ed880ee6c5e1e06f9.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/icons/sphere.svg"
dest_files=["res://.godot/imported/sphere.svg-c7c8c1311207415ed880ee6c5e1e06f9.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=true

View File

@@ -0,0 +1,4 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 24C9 28.9706 13.0294 33 18 33C19.6393 33 21.1762 32.5617 22.5 31.796C19.8099 30.2398 18 27.3313 18 24C18 20.6687 19.8099 17.7602 22.5 16.204C21.1762 15.4383 19.6393 15 18 15C13.0294 15 9 19.0294 9 24Z" fill="#fc7f7f"/>
<path d="M22.5 16.204C19.8099 17.7602 18 20.6687 18 24C18 27.3313 19.8099 30.2398 22.5 31.796M22.5 16.204C23.8238 15.4383 25.3607 15 27 15C31.9706 15 36 19.0294 36 24C36 28.9706 31.9706 33 27 33C25.3607 33 23.8238 32.5617 22.5 31.796M22.5 16.204C21.1762 15.4383 19.6393 15 18 15C13.0294 15 9 19.0294 9 24C9 28.9706 13.0294 33 18 33C19.6393 33 21.1762 32.5617 22.5 31.796M22.5 16.204C25.1901 17.7602 27 20.6687 27 24C27 27.3313 25.1901 30.2398 22.5 31.796" stroke="#fc7f7f" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 826 B

View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://snbhkpq6sh4j"
path="res://.godot/imported/subtraction.svg-9afd3882eada513c9c6754b2b38f150f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/icons/subtraction.svg"
dest_files=["res://.godot/imported/subtraction.svg-9afd3882eada513c9c6754b2b38f150f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><ellipse cx="8" cy="7.5" fill="none" rx="6" ry="3.5" stroke="#fc7f7f" stroke-width="2" mask="url(#a)"/></svg>

After

Width:  |  Height:  |  Size: 456 B

View File

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://gbgmyv4bknqo"
path="res://.godot/imported/torus.svg-73f1780e51211292acb9e9d40bb4276f.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/icons/torus.svg"
dest_files=["res://.godot/imported/torus.svg-73f1780e51211292acb9e9d40bb4276f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=true

View File

@@ -0,0 +1,6 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 24C9 28.9706 13.0294 33 18 33C19.6393 33 21.1762 32.5617 22.5 31.796C19.8099 30.2398 18 27.3313 18 24C18 20.6687 19.8099 17.7602 22.5 16.204C21.1762 15.4383 19.6393 15 18 15C13.0294 15 9 19.0294 9 24Z" fill="#fc7f7f"/>
<path d="M27 33C31.9706 33 36 28.9706 36 24C36 19.0294 31.9706 15 27 15C25.3607 15 23.8238 15.4383 22.5 16.204C25.1901 17.7602 27 20.6687 27 24C27 27.3313 25.1901 30.2398 22.5 31.796C23.8238 32.5617 25.3607 33 27 33Z" fill="#fc7f7f"/>
<path d="M22.5 16.204C19.8099 17.7602 18 20.6687 18 24C18 27.3313 19.8099 30.2398 22.5 31.796C25.1901 30.2398 27 27.3313 27 24C27 20.6687 25.1901 17.7602 22.5 16.204Z" fill="#fc7f7f"/>
<path d="M22.5 16.204C19.8099 17.7602 18 20.6687 18 24C18 27.3313 19.8099 30.2398 22.5 31.796M22.5 16.204C23.8238 15.4383 25.3607 15 27 15C31.9706 15 36 19.0294 36 24C36 28.9706 31.9706 33 27 33C25.3607 33 23.8238 32.5617 22.5 31.796M22.5 16.204C21.1762 15.4383 19.6393 15 18 15C13.0294 15 9 19.0294 9 24C9 28.9706 13.0294 33 18 33C19.6393 33 21.1762 32.5617 22.5 31.796M22.5 16.204C25.1901 17.7602 27 20.6687 27 24C27 27.3313 25.1901 30.2398 22.5 31.796" fill="#fc7f7f" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://sc7itwu80oi3"
path="res://.godot/imported/union.svg-330af8601d6493b573ab68d43d4b23f3.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/icons/union.svg"
dest_files=["res://.godot/imported/union.svg-330af8601d6493b573ab68d43d4b23f3.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d0dy7wteea7l5"
path="res://.godot/imported/image.png-e18db3df6f24d50b719c17a226c337bd.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/csg_toolkit/res/image.png"
dest_files=["res://.godot/imported/image.png-e18db3df6f24d50b719c17a226c337bd.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@@ -0,0 +1,3 @@
[gd_resource type="ButtonGroup" format=3 uid="uid://wxtkg1wlxka8"]
[resource]

View File

@@ -0,0 +1,125 @@
[gd_scene load_steps=4 format=3 uid="uid://dts41g6camqwq"]
[ext_resource type="Script" uid="uid://b7isqiq2asnu6" path="res://addons/csg_toolkit/scripts/config_window.gd" id="1_pigko"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ng5lh"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_vj6tm"]
[node name="ConfigWindow" type="Window"]
oversampling_override = 1.0
title = "CSG Toolit Configuration"
initial_position = 1
size = Vector2i(480, 360)
popup_window = true
script = ExtResource("1_pigko")
[node name="MarginContainer" type="MarginContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 8
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Default Behavior"
[node name="VSeparator" type="VSeparator" parent="MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/separator = SubResource("StyleBoxEmpty_ng5lh")
[node name="OptionButton" type="OptionButton" parent="MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
selected = 0
item_count = 2
popup/item_0/text = "Sibling"
popup/item_0/id = 0
popup/item_1/text = "Child"
popup/item_1/id = 1
[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer2"]
layout_mode = 2
text = "Action Key"
[node name="VSeparator" type="VSeparator" parent="MarginContainer/VBoxContainer/HBoxContainer2"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/separator = SubResource("StyleBoxEmpty_ng5lh")
[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2"]
layout_mode = 2
text = "Shift"
[node name="HBoxContainer4" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer4"]
layout_mode = 2
text = "Behvaior Toggle"
[node name="VSeparator" type="VSeparator" parent="MarginContainer/VBoxContainer/HBoxContainer4"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/separator = SubResource("StyleBoxEmpty_ng5lh")
[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer4"]
layout_mode = 2
text = "Shift"
[node name="HBoxContainer3" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer3"]
layout_mode = 2
text = "Auto Hide"
[node name="VSeparator" type="VSeparator" parent="MarginContainer/VBoxContainer/HBoxContainer3"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/separator = SubResource("StyleBoxEmpty_ng5lh")
[node name="CheckButton" type="CheckBox" parent="MarginContainer/VBoxContainer/HBoxContainer3"]
layout_mode = 2
button_pressed = true
[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/separator = SubResource("StyleBoxEmpty_vj6tm")
[node name="Save" type="Button" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
text = "Save"
[node name="HSeparator2" type="HSeparator" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Ko-Fi" type="LinkButton" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
text = "Support me on Ko-Fi"
underline = 1
uri = "https://ko-fi.com/luckyteapot"
[connection signal="item_selected" from="MarginContainer/VBoxContainer/HBoxContainer/OptionButton" to="." method="_on_option_button_item_selected"]
[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/Button" to="." method="_on_button_pressed"]
[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer4/Button" to="." method="_on_second_button_pressed"]
[connection signal="toggled" from="MarginContainer/VBoxContainer/HBoxContainer3/CheckButton" to="." method="_on_check_box_toggled"]
[connection signal="pressed" from="MarginContainer/VBoxContainer/Save" to="." method="_on_save_pressed"]

View File

@@ -0,0 +1,20 @@
[gd_scene load_steps=2 format=3 uid="uid://dhpjubljm0tf0"]
[ext_resource type="Texture2D" uid="uid://cjxx30pcamj36" path="res://addons/csg_toolkit/res/icons/box.svg" id="1_n1dqv"]
[node name="CsgQuickActions" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="CenterContainer" type="CenterContainer" parent="."]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="Box" type="TextureButton" parent="CenterContainer"]
layout_mode = 2
texture_normal = ExtResource("1_n1dqv")

View File

@@ -0,0 +1,179 @@
[gd_scene load_steps=15 format=3 uid="uid://cdjxmp0p1bbup"]
[ext_resource type="Script" uid="uid://dr5f1egll7hdq" path="res://addons/csg_toolkit/scripts/csg_side_toolkit_bar.gd" id="1_awo4p"]
[ext_resource type="Texture2D" uid="uid://cjxx30pcamj36" path="res://addons/csg_toolkit/res/icons/box.svg" id="2_p4f7r"]
[ext_resource type="Texture2D" uid="uid://dioxt3oaqvsi3" path="res://addons/csg_toolkit/res/icons/cyliner.svg" id="3_j7s68"]
[ext_resource type="Texture2D" uid="uid://bnpu878eanspj" path="res://addons/csg_toolkit/res/icons/mesh.svg" id="4_328ee"]
[ext_resource type="Texture2D" uid="uid://cjps2pofcsfc0" path="res://addons/csg_toolkit/res/icons/polygon.svg" id="5_0os5t"]
[ext_resource type="Texture2D" uid="uid://kpld1ou63wlf" path="res://addons/csg_toolkit/res/icons/sphere.svg" id="6_ju1pi"]
[ext_resource type="Texture2D" uid="uid://gbgmyv4bknqo" path="res://addons/csg_toolkit/res/icons/torus.svg" id="7_6hoxg"]
[ext_resource type="Texture2D" uid="uid://sc7itwu80oi3" path="res://addons/csg_toolkit/res/icons/union.svg" id="8_gmt87"]
[ext_resource type="Texture2D" uid="uid://2kohi3tb3c70" path="res://addons/csg_toolkit/res/icons/intersection.svg" id="9_55ipi"]
[ext_resource type="Texture2D" uid="uid://snbhkpq6sh4j" path="res://addons/csg_toolkit/res/icons/subtraction.svg" id="10_8u5xb"]
[ext_resource type="Texture2D" uid="uid://cg5jbylrlg00x" path="res://addons/csg_toolkit/res/icons/empty-material.svg" id="11_ten1m"]
[ext_resource type="Texture2D" uid="uid://clgooji83dl4u" path="res://addons/csg_toolkit/res/icons/config.svg" id="12_3gawg"]
[sub_resource type="ButtonGroup" id="ButtonGroup_2ipgb"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ixko7"]
[node name="MarginContainer" type="MarginContainer"]
visible = false
custom_minimum_size = Vector2(52, 0)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = -2.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_top = 8
theme_override_constants/margin_bottom = 8
script = ExtResource("1_awo4p")
[node name="ScrollContainer" type="ScrollContainer" parent="."]
layout_mode = 2
horizontal_scroll_mode = 0
[node name="HBoxContainer" type="VBoxContainer" parent="ScrollContainer"]
custom_minimum_size = Vector2(0, 120)
layout_mode = 2
size_flags_horizontal = 6
size_flags_vertical = 3
theme_override_constants/separation = 16
alignment = 1
[node name="CSG" type="VBoxContainer" parent="ScrollContainer/HBoxContainer"]
layout_mode = 2
alignment = 1
[node name="Box" type="Button" parent="ScrollContainer/HBoxContainer/CSG"]
custom_minimum_size = Vector2(42, 42)
layout_mode = 2
tooltip_text = "Box"
icon = ExtResource("2_p4f7r")
icon_alignment = 1
expand_icon = true
[node name="Cylinder" type="Button" parent="ScrollContainer/HBoxContainer/CSG"]
custom_minimum_size = Vector2(42, 42)
layout_mode = 2
tooltip_text = "Cylinder"
theme_override_constants/icon_max_width = 24
icon = ExtResource("3_j7s68")
icon_alignment = 1
expand_icon = true
[node name="Mesh" type="Button" parent="ScrollContainer/HBoxContainer/CSG"]
custom_minimum_size = Vector2(42, 42)
layout_mode = 2
tooltip_text = "Mesh"
theme_override_constants/icon_max_width = 24
icon = ExtResource("4_328ee")
icon_alignment = 1
expand_icon = true
[node name="Polygon" type="Button" parent="ScrollContainer/HBoxContainer/CSG"]
custom_minimum_size = Vector2(42, 42)
layout_mode = 2
tooltip_text = "Polygon"
theme_override_constants/icon_max_width = 24
icon = ExtResource("5_0os5t")
icon_alignment = 1
expand_icon = true
[node name="Sphere" type="Button" parent="ScrollContainer/HBoxContainer/CSG"]
custom_minimum_size = Vector2(42, 42)
layout_mode = 2
tooltip_text = "Sphere"
theme_override_constants/icon_max_width = 24
icon = ExtResource("6_ju1pi")
icon_alignment = 1
expand_icon = true
[node name="Torus" type="Button" parent="ScrollContainer/HBoxContainer/CSG"]
custom_minimum_size = Vector2(42, 42)
layout_mode = 2
tooltip_text = "Torus"
theme_override_constants/icon_max_width = 24
icon = ExtResource("7_6hoxg")
icon_alignment = 1
expand_icon = true
[node name="Operation" type="VBoxContainer" parent="ScrollContainer/HBoxContainer"]
layout_mode = 2
alignment = 1
[node name="Union" type="Button" parent="ScrollContainer/HBoxContainer/Operation"]
custom_minimum_size = Vector2(42, 42)
layout_mode = 2
tooltip_text = "Union"
theme_override_constants/icon_max_width = 24
toggle_mode = true
button_group = SubResource("ButtonGroup_2ipgb")
icon = ExtResource("8_gmt87")
icon_alignment = 1
expand_icon = true
[node name="Intersection" type="Button" parent="ScrollContainer/HBoxContainer/Operation"]
custom_minimum_size = Vector2(42, 42)
layout_mode = 2
tooltip_text = "Intersection"
theme_override_constants/icon_max_width = 24
toggle_mode = true
button_group = SubResource("ButtonGroup_2ipgb")
icon = ExtResource("9_55ipi")
icon_alignment = 1
expand_icon = true
[node name="Subtraction" type="Button" parent="ScrollContainer/HBoxContainer/Operation"]
custom_minimum_size = Vector2(42, 42)
layout_mode = 2
tooltip_text = "Subtraction"
theme_override_constants/icon_max_width = 24
toggle_mode = true
button_group = SubResource("ButtonGroup_2ipgb")
icon = ExtResource("10_8u5xb")
icon_alignment = 1
expand_icon = true
[node name="Material" type="VBoxContainer" parent="ScrollContainer/HBoxContainer"]
layout_mode = 2
alignment = 1
[node name="MaterialPicker" type="Button" parent="ScrollContainer/HBoxContainer/Material"]
custom_minimum_size = Vector2(42, 42)
layout_mode = 2
tooltip_text = "Material"
icon = ExtResource("11_ten1m")
icon_alignment = 1
expand_icon = true
[node name="HSeparator" type="HSeparator" parent="ScrollContainer/HBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/separator = SubResource("StyleBoxEmpty_ixko7")
[node name="Options" type="VBoxContainer" parent="ScrollContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
alignment = 1
[node name="Config" type="Button" parent="ScrollContainer/HBoxContainer/Options"]
custom_minimum_size = Vector2(42, 42)
layout_mode = 2
tooltip_text = "Config"
theme_override_constants/icon_max_width = 24
icon = ExtResource("12_3gawg")
icon_alignment = 1
[connection signal="pressed" from="ScrollContainer/HBoxContainer/CSG/Box" to="." method="_on_box_pressed"]
[connection signal="pressed" from="ScrollContainer/HBoxContainer/CSG/Cylinder" to="." method="_on_cylinder_pressed"]
[connection signal="pressed" from="ScrollContainer/HBoxContainer/CSG/Mesh" to="." method="_on_mesh_pressed"]
[connection signal="pressed" from="ScrollContainer/HBoxContainer/CSG/Polygon" to="." method="_on_polygon_pressed"]
[connection signal="pressed" from="ScrollContainer/HBoxContainer/CSG/Sphere" to="." method="_on_sphere_pressed"]
[connection signal="pressed" from="ScrollContainer/HBoxContainer/CSG/Torus" to="." method="_on_torus_pressed"]
[connection signal="pressed" from="ScrollContainer/HBoxContainer/Operation/Union" to="." method="_on_operation_pressed" binds= [0]]
[connection signal="pressed" from="ScrollContainer/HBoxContainer/Operation/Intersection" to="." method="_on_operation_pressed" binds= [1]]
[connection signal="pressed" from="ScrollContainer/HBoxContainer/Operation/Subtraction" to="." method="_on_operation_pressed" binds= [2]]
[connection signal="pressed" from="ScrollContainer/HBoxContainer/Material/MaterialPicker" to="." method="_on_material_picker_pressed"]
[connection signal="pressed" from="ScrollContainer/HBoxContainer/Options/Config" to="." method="_on_config_pressed"]

View File

@@ -0,0 +1,16 @@
[gd_scene load_steps=2 format=3 uid="uid://dgldacs362p7g"]
[ext_resource type="Script" uid="uid://dk6dt8fk1s43s" path="res://addons/csg_toolkit/scripts/csg_top_toolkit_bar.gd" id="1_154hl"]
[node name="HBoxContainer" type="HBoxContainer"]
visible = false
offset_right = 40.0
offset_bottom = 40.0
script = ExtResource("1_154hl")
[node name="Refresh" type="Button" parent="."]
layout_mode = 2
text = "Refresh"
flat = true
[connection signal="pressed" from="Refresh" to="." method="_on_refresh_pressed"]

View File

@@ -0,0 +1,5 @@
[gd_scene format=3 uid="uid://b6adrnqobv5fi"]
[node name="Demo" type="Node3D"]
[node name="CSGBox3D" type="CSGBox3D" parent="."]

View File

@@ -0,0 +1,46 @@
@tool
extends Window
@onready var config: CsgTkConfig:
get: return get_tree().root.get_node(CsgToolkit.AUTOLOAD_NAME) as CsgTkConfig
@onready var default_behavior_option: OptionButton = $MarginContainer/VBoxContainer/HBoxContainer/OptionButton
@onready var action_key_button: Button = $MarginContainer/VBoxContainer/HBoxContainer2/Button
@onready var behvaior_toogle_button: Button = $MarginContainer/VBoxContainer/HBoxContainer4/Button
@onready var auto_hide_switch: CheckBox = $MarginContainer/VBoxContainer/HBoxContainer3/CheckButton
signal key_press(key: InputEventKey)
func _ready():
default_behavior_option.select(config.default_behavior)
action_key_button.text = OS.get_keycode_string(config.action_key)
behvaior_toogle_button.text = OS.get_keycode_string(config.secondary_action_key)
auto_hide_switch.button_pressed = config.auto_hide
func _on_option_button_item_selected(index):
match index:
0: config.default_behavior = CsgTkConfig.CSGBehavior.SIBLING
1: config.default_behavior = CsgTkConfig.CSGBehavior.CHILD
func _unhandled_input(event):
if event is InputEventKey:
if event.pressed:
key_press.emit(event)
func _on_save_pressed():
config.save_config()
hide()
func _on_button_pressed():
var key_event: InputEventKey = await key_press
config.action_key = key_event.keycode
action_key_button.text = key_event.as_text_key_label()
func _on_second_button_pressed():
var key_event: InputEventKey = await key_press
config.secondary_action_key = key_event.keycode
behvaior_toogle_button.text = key_event.as_text_key_label()
func _on_check_box_toggled(toggled_on):
config.auto_hide = toggled_on

View File

@@ -0,0 +1 @@
uid://b7isqiq2asnu6

View File

@@ -0,0 +1,522 @@
@tool
class_name CSGRepeater3D extends CSGCombiner3D
# NOTE: Registered as custom type in plugin (csg_toolkit.gd) inheriting CSGCombiner3D.
# Ensure pattern resource scripts are loaded (Godot should handle via class_name, but we force references for safety):
const _REF_GRID = preload("res://addons/csg_toolkit/scripts/patterns/grid_pattern.gd") # ensure subclass scripts loaded
const _REF_CIRC = preload("res://addons/csg_toolkit/scripts/patterns/circular_pattern.gd")
const _REF_SPIRAL = preload("res://addons/csg_toolkit/scripts/patterns/spiral_pattern.gd")
const _REF_NOISE = preload("res://addons/csg_toolkit/scripts/patterns/noise_pattern.gd")
const REPEATER_NODE_META = "REPEATED_NODE_META"
const MAX_INSTANCES = 20000
var _dirty: bool = false
var _template_node_path: NodePath
@export var template_node_path: NodePath:
get: return _template_node_path
set(value):
_template_node_path = value
_mark_dirty()
var _template_node_scene: PackedScene
@export var template_node_scene: PackedScene:
get: return _template_node_scene
set(value):
_template_node_scene = value
_mark_dirty()
var _hide_template: bool = true
@export var hide_template: bool = true:
get: return _hide_template
set(value):
_hide_template = value
_update_template_visibility()
## repeat & spacing removed (migrated into pattern resources)
@export_group("Pattern Options")
# A single exported pattern resource (`pattern`) defines generation behavior.
@export_group("Variation Options")
# Rotation variation properties now managed via custom property list for collapsible enable group.
var _randomize_rotation: bool = false
var randomize_rotation: bool:
get: return _randomize_rotation
set(value):
_randomize_rotation = value
_mark_dirty()
notify_property_list_changed()
var _randomize_rot_x: bool = false
var randomize_rot_x: bool:
get: return _randomize_rot_x
set(value):
_randomize_rot_x = value
if _randomize_rotation: _mark_dirty()
notify_property_list_changed()
var _randomize_rot_y: bool = false
var randomize_rot_y: bool:
get: return _randomize_rot_y
set(value):
_randomize_rot_y = value
if _randomize_rotation: _mark_dirty()
notify_property_list_changed()
var _randomize_rot_z: bool = false
var randomize_rot_z: bool:
get: return _randomize_rot_z
set(value):
_randomize_rot_z = value
if _randomize_rotation: _mark_dirty()
notify_property_list_changed()
# Per-axis rotation variance in degrees (0 = full 0..360 random for that axis; >0 jitters around original)
var _rotation_variance_x_deg: float = 0.0
var rotation_variance_x_deg: float:
get: return _rotation_variance_x_deg
set(value):
_rotation_variance_x_deg = clamp(value, 0.0, 360.0)
if _randomize_rotation and _randomize_rot_x: _mark_dirty()
var _rotation_variance_y_deg: float = 0.0
var rotation_variance_y_deg: float:
get: return _rotation_variance_y_deg
set(value):
_rotation_variance_y_deg = clamp(value, 0.0, 360.0)
if _randomize_rotation and _randomize_rot_y: _mark_dirty()
var _rotation_variance_z_deg: float = 0.0
var rotation_variance_z_deg: float:
get: return _rotation_variance_z_deg
set(value):
_rotation_variance_z_deg = clamp(value, 0.0, 360.0)
if _randomize_rotation and _randomize_rot_z: _mark_dirty()
var _randomize_scale: bool = false
var randomize_scale: bool:
get: return _randomize_scale
set(value):
_randomize_scale = value
_mark_dirty()
notify_property_list_changed()
var _scale_variance: float = 0.0
var scale_variance: float:
get: return _scale_variance
set(value):
_scale_variance = clamp(value, 0.0, 1.0)
if _randomize_scale:
_mark_dirty()
# Per-axis scale variance (if zero => use global variance when axis toggle active)
var _scale_variance_x: float = 0.0
var scale_variance_x: float:
get: return _scale_variance_x
set(value):
_scale_variance_x = clamp(value, 0.0, 1.0)
if _randomize_scale and _randomize_scale_x: _mark_dirty()
var _scale_variance_y: float = 0.0
var scale_variance_y: float:
get: return _scale_variance_y
set(value):
_scale_variance_y = clamp(value, 0.0, 1.0)
if _randomize_scale and _randomize_scale_y: _mark_dirty()
var _scale_variance_z: float = 0.0
var scale_variance_z: float:
get: return _scale_variance_z
set(value):
_scale_variance_z = clamp(value, 0.0, 1.0)
if _randomize_scale and _randomize_scale_z: _mark_dirty()
# Per-axis scale randomization toggles (optional if none enabled acts as uniform variance on all axes)
var _randomize_scale_x: bool = false
var randomize_scale_x: bool:
get: return _randomize_scale_x
set(value):
_randomize_scale_x = value
if _randomize_scale: _mark_dirty()
notify_property_list_changed()
var _randomize_scale_y: bool = false
var randomize_scale_y: bool:
get: return _randomize_scale_y
set(value):
_randomize_scale_y = value
if _randomize_scale: _mark_dirty()
notify_property_list_changed()
var _randomize_scale_z: bool = false
var randomize_scale_z: bool:
get: return _randomize_scale_z
set(value):
_randomize_scale_z = value
if _randomize_scale: _mark_dirty()
notify_property_list_changed()
var _position_jitter: float = 0.0
@export var position_jitter: float = 0.0:
get: return _position_jitter
set(value):
_position_jitter = max(0.0, value)
_mark_dirty()
var _random_seed: int = 0
@export var random_seed: int = 0:
get: return _random_seed
set(value):
_random_seed = value
_mark_dirty()
# Estimated instance count (read-only in inspector; updated internally)
@export var estimated_instances: int = 0
var rng: RandomNumberGenerator
var _generation_in_progress := false
var _pattern: CSGPattern
@export var pattern: CSGPattern:
get: return _pattern
set(value):
if value == _pattern:
return
# Reject non-CSGPattern resources
if value != null and not (value is CSGPattern):
push_warning("Assigned pattern is not a CSGPattern-derived resource; ignoring.")
return
# Prevent assigning the abstract base directly (must use subclass)
if value != null and value.get_class() == "CSGPattern":
push_warning("Cannot assign base CSGPattern directly. Please use a concrete pattern (Grid, Circular, Spiral...).")
return
# Disconnect old
if _pattern and _pattern.is_connected("changed", Callable(self, "_on_pattern_changed")):
_pattern.disconnect("changed", Callable(self, "_on_pattern_changed"))
_pattern = value
if _pattern and not _pattern.is_connected("changed", Callable(self, "_on_pattern_changed")):
_pattern.connect("changed", Callable(self, "_on_pattern_changed"))
_mark_dirty()
func _ready():
rng = RandomNumberGenerator.new()
# Provide a default pattern if none assigned (through setter for signal wiring).
if pattern == null:
pattern = CSGGridPattern.new()
_mark_dirty()
# Generate instances in-game on ready
if not Engine.is_editor_hint():
call_deferred("repeat_template")
func _on_pattern_changed():
# Called when the assigned pattern resource's exported properties are edited in inspector.
_mark_dirty()
func _process(_delta):
if not Engine.is_editor_hint(): return
if _dirty and not _generation_in_progress:
_dirty = false
call_deferred("repeat_template")
func _exit_tree():
# Clean up any remaining repeated nodes
clear_children()
func _mark_dirty():
_dirty = true
func _update_template_visibility():
if not is_inside_tree():
return
var template_node = get_node_or_null(template_node_path)
if template_node and template_node is Node3D:
template_node.visible = not _hide_template
func clear_children():
# Clear existing children except the template node
var children_to_remove = []
for child in get_children(true):
if child.has_meta(REPEATER_NODE_META):
children_to_remove.append(child)
# Remove children immediately for better performance
for child in children_to_remove:
remove_child(child)
child.queue_free()
func repeat_template():
if _generation_in_progress:
return
_generation_in_progress = true
clear_children()
var template_node = get_node_or_null(template_node_path)
var using_scene = false
# Determine template source
if not template_node:
if not template_node_scene or not template_node_scene.can_instantiate():
_generation_in_progress = false
return
template_node = template_node_scene.instantiate()
using_scene = true
add_child(template_node)
# Use pattern estimation for cap check
var template_size := _get_template_size(template_node)
var ctx_cap := {"template_size": template_size, "rng": rng, "position_jitter": _position_jitter}
var estimate := 0
if pattern:
estimate = pattern.get_estimated_count(ctx_cap)
if estimate <= 1:
if using_scene:
remove_child(template_node)
template_node.queue_free()
_generation_in_progress = false
return
if estimate > MAX_INSTANCES:
push_warning("CSGRepeater3D: Estimated count %s exceeds cap %s. Aborting generation." % [estimate, MAX_INSTANCES])
_generation_in_progress = false
return
rng.seed = _random_seed
# template_size already computed earlier (template_size variable)
var positions = _generate_positions(template_size)
estimated_instances = positions.size() - 1
for i in range(positions.size()):
var position = positions[i]
if i == 0 and position.is_zero_approx():
continue
var instance = template_node.duplicate()
if instance == null:
continue
instance.set_meta(REPEATER_NODE_META, true)
instance.transform.origin = position
# Ensure instance is visible regardless of template visibility
if instance is Node3D:
instance.visible = true
_apply_variations(instance)
add_child(instance)
if using_scene:
remove_child(template_node)
template_node.queue_free()
else:
_update_template_visibility()
_generation_in_progress = false
func _generate_positions(template_size: Vector3) -> Array:
var ctx: Dictionary = {"template_size": template_size, "rng": rng, "position_jitter": _position_jitter}
if pattern == null:
return []
return pattern.generate(ctx)
# -- Geometry-based spacing helpers -------------------------------------------------
func _get_template_size(template_node: Node) -> Vector3:
if template_node == null or not (template_node is Node3D):
return Vector3.ONE
var aabb := _get_combined_aabb(template_node)
var size: Vector3 = aabb.size
if size.x <= 0.0001: size.x = 1.0
if size.y <= 0.0001: size.y = 1.0
if size.z <= 0.0001: size.z = 1.0
return size
func _get_combined_aabb(node: Node) -> AABB:
var found := false
var combined := AABB()
if node is Node3D and node.has_method("get_aabb"):
var aabb = node.get_aabb()
combined = aabb
found = true
for child in node.get_children():
if child is Node3D:
var child_aabb = _get_combined_aabb(child)
if child_aabb.size != Vector3.ZERO:
if not found:
combined = child_aabb
found = true
else:
combined = combined.merge(child_aabb)
return combined if found else AABB(Vector3.ZERO, Vector3.ZERO)
func _apply_material_recursive(node: Node, material: Material):
if node is CSGShape3D:
node.material_override = material
for child in node.get_children():
_apply_material_recursive(child, material)
func _apply_variations(instance: Node3D):
if _randomize_rotation:
var final_rot := instance.rotation
if _randomize_rot_x:
if _rotation_variance_x_deg > 0.0:
final_rot.x += rng.randf_range(-deg_to_rad(_rotation_variance_x_deg), deg_to_rad(_rotation_variance_x_deg))
else:
final_rot.x = rng.randf() * TAU
if _randomize_rot_y:
if _rotation_variance_y_deg > 0.0:
final_rot.y += rng.randf_range(-deg_to_rad(_rotation_variance_y_deg), deg_to_rad(_rotation_variance_y_deg))
else:
final_rot.y = rng.randf() * TAU
if _randomize_rot_z:
if _rotation_variance_z_deg > 0.0:
final_rot.z += rng.randf_range(-deg_to_rad(_rotation_variance_z_deg), deg_to_rad(_rotation_variance_z_deg))
else:
final_rot.z = rng.randf() * TAU
instance.rotation = final_rot
if _randomize_scale:
# If any axis toggles are on, apply independent variance per axis; else uniform.
var use_axes = _randomize_scale_x or _randomize_scale_y or _randomize_scale_z
if use_axes:
var sx = instance.scale.x
var sy = instance.scale.y
var sz = instance.scale.z
if _randomize_scale_x:
var vx = (_scale_variance_x if _scale_variance_x > 0.0 else _scale_variance)
sx *= max(0.1, 1.0 + rng.randf_range(-vx, vx))
if _randomize_scale_y:
var vy = (_scale_variance_y if _scale_variance_y > 0.0 else _scale_variance)
sy *= max(0.1, 1.0 + rng.randf_range(-vy, vy))
if _randomize_scale_z:
var vz = (_scale_variance_z if _scale_variance_z > 0.0 else _scale_variance)
sz *= max(0.1, 1.0 + rng.randf_range(-vz, vz))
instance.scale = Vector3(sx, sy, sz)
else:
var scale_factor = max(0.1, 1.0 + rng.randf_range(-_scale_variance, _scale_variance))
instance.scale *= scale_factor
func regenerate():
_mark_dirty()
# -- Custom property list (Godot 4.5 group enable support) -------------------------
func _get_property_list() -> Array:
var props: Array = []
# Keep default exported properties (engine already exposes them). Only inject
# the rotation variation cluster with group enable + subgroup organization.
# Group header for random rotation feature.
# Variation Options parent group
props.append({
"name": "Variation Options",
"type": TYPE_NIL,
"usage": PROPERTY_USAGE_GROUP
})
# Rotation subgroup under Variation Options
props.append({
"name": "Rotation Randomization",
"type": TYPE_NIL,
"usage": PROPERTY_USAGE_SUBGROUP
})
# Enabling checkbox on group header via PROPERTY_HINT_GROUP_ENABLE.
props.append({
"name": "randomize_rotation",
"type": TYPE_BOOL,
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR,
"hint": PROPERTY_HINT_GROUP_ENABLE
})
# Per-axis random toggles
props.append(_prop_bool("randomize_rot_x"))
if _randomize_rotation and _randomize_rot_x:
props.append({
"name": "rotation_variance_x_deg",
"type": TYPE_FLOAT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0,360,0.1,degrees",
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR
})
props.append(_prop_bool("randomize_rot_y"))
if _randomize_rotation and _randomize_rot_y:
props.append({
"name": "rotation_variance_y_deg",
"type": TYPE_FLOAT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0,360,0.1,degrees",
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR
})
props.append(_prop_bool("randomize_rot_z"))
if _randomize_rotation and _randomize_rot_z:
props.append({
"name": "rotation_variance_z_deg",
"type": TYPE_FLOAT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0,360,0.1,degrees",
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR
})
# Subgroup for locked rotations (should reside inside Rotation Randomization group)
# (Locked rotations removed as per user request)
# Scale variation subgroup under Variation Options
props.append({
"name": "Scale Variation",
"type": TYPE_NIL,
"usage": PROPERTY_USAGE_SUBGROUP
})
props.append({
"name": "randomize_scale",
"type": TYPE_BOOL,
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR,
"hint": PROPERTY_HINT_GROUP_ENABLE
})
props.append(_prop_float_range("scale_variance", "0,1,0.01"))
props.append(_prop_bool("randomize_scale_x"))
if _randomize_scale and _randomize_scale_x:
props.append(_prop_float_range("scale_variance_x", "0,1,0.01"))
props.append(_prop_bool("randomize_scale_y"))
if _randomize_scale and _randomize_scale_y:
props.append(_prop_float_range("scale_variance_y", "0,1,0.01"))
props.append(_prop_bool("randomize_scale_z"))
if _randomize_scale and _randomize_scale_z:
props.append(_prop_float_range("scale_variance_z", "0,1,0.01"))
return props
func _prop_bool(name: String) -> Dictionary:
return {
"name": name,
"type": TYPE_BOOL,
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR
}
func _prop_float_deg(name: String, value: float) -> Dictionary:
return {
"name": name,
"type": TYPE_FLOAT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "-360,360,0.1,degrees",
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR
}
func _prop_float_range(name: String, hint_str: String) -> Dictionary:
return {
"name": name,
"type": TYPE_FLOAT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": hint_str,
"usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR
}
func get_instance_count() -> int:
if pattern == null:
return 0
var ctx := {"template_size": Vector3.ONE, "rng": rng, "position_jitter": _position_jitter}
return max(0, pattern.get_estimated_count(ctx) - 1)
func apply_template():
if get_child_count() == 0:
return
var stack = []
stack.append_array(get_children())
while stack.size() > 0:
var node = stack.pop_back()
node.set_owner(owner)
stack.append_array(node.get_children())
# Alias for clarity in UI
func bake_instances():
apply_template()

View File

@@ -0,0 +1 @@
uid://c68dxahp0v5xg

View File

@@ -0,0 +1,86 @@
@tool
extends Node
class_name CsgShortcutManager
# Provides global key handling for quick CSG creation & operation switching (Layers 1 & 2)
# Delegates actual creation to the sidebar instance to reuse UndoRedo + material logic.
var sidebar: CSGSideToolkitBar
var config: CsgTkConfig
# Mapping shape keycode -> factory id (string used for log / optional future use)
var _shape_key_map: Dictionary = {
KEY_B: CSGBox3D,
KEY_S: CSGSphere3D,
KEY_C: CSGCylinder3D,
KEY_T: CSGTorus3D,
KEY_M: CSGMesh3D,
KEY_P: CSGPolygon3D,
}
# Layer 2 operation selection numbers
var _op_number_map: Dictionary = {
KEY_1: 0, # Union
KEY_2: 1, # Intersection
KEY_3: 2, # Subtraction
}
# Optional cycle order
var _op_cycle: Array = [0,1,2]
var _cycle_index := 0
func _enter_tree():
set_process_unhandled_key_input(true)
func _unhandled_key_input(event: InputEvent):
if not event is InputEventKey: return
var ev := event as InputEventKey
if not ev.pressed or ev.echo: return
if config == null:
config = get_tree().root.get_node_or_null(CsgToolkit.AUTOLOAD_NAME) as CsgTkConfig
if sidebar == null:
# Try to find existing sidebar if not explicitly set
var candidates = get_tree().get_nodes_in_group("CSGSideToolkit")
if candidates.size() > 0:
sidebar = candidates[0]
# Prevent interfering with text input fields
var focus_owner = get_viewport().gui_get_focus_owner()
if focus_owner and (focus_owner is LineEdit or focus_owner is TextEdit):
return
# Operation & shape shortcuts only trigger when primary action key is held (secondary key reserved for behavior inversion in creation)
if Input.is_key_pressed(config.action_key):
if ev.physical_keycode in _op_number_map:
var op_val = _op_number_map[ev.physical_keycode]
sidebar.set_operation(op_val)
_print_feedback("Op -> %s" % _op_label(op_val))
return
# Cycle operation with backtick (`) or TAB
if ev.physical_keycode in [KEY_APOSTROPHE, KEY_QUOTELEFT, KEY_TAB]:
_cycle_index = (_cycle_index + 1) % _op_cycle.size()
var cyc_op = _op_cycle[_cycle_index]
sidebar.set_operation(cyc_op)
_print_feedback("Op Cycle -> %s" % _op_label(cyc_op))
return
# Direct shape create (Layer 1)
if ev.physical_keycode in _shape_key_map:
_create_shape(_shape_key_map[ev.physical_keycode])
return
func _create_shape(type_ref: Variant):
if sidebar == null:
_print_feedback("No sidebar found for creation")
return
# Delegates to sidebar logic (handles operation, insertion mode, UndoRedo, materials)
sidebar.create_csg(type_ref)
_print_feedback("Create %s (%s)" % [type_ref, _op_label(sidebar.operation)])
func _op_label(op: int) -> String:
match op:
0: return "Union"
1: return "Intersect"
2: return "Subtract"
_: return str(op)
func _print_feedback(msg: String):
print("CSG Toolkit: %s" % msg)

View File

@@ -0,0 +1 @@
uid://1bqvx7teqnrl

View File

@@ -0,0 +1,236 @@
@tool
class_name CSGSideToolkitBar extends Control
@onready var config: CsgTkConfig:
get:
return get_tree().root.get_node_or_null(CsgToolkit.AUTOLOAD_NAME) as CsgTkConfig
var operation: CSGShape3D.Operation = CSGShape3D.OPERATION_UNION
var selected_material: BaseMaterial3D
var selected_shader: ShaderMaterial
@onready var picker_button: Button = $ScrollContainer/HBoxContainer/Material/MaterialPicker
func _enter_tree():
EditorInterface.get_selection().selection_changed.connect(_on_selection_changed)
func _exit_tree():
EditorInterface.get_selection().selection_changed.disconnect(_on_selection_changed)
func _on_selection_changed():
if not config.auto_hide:
return
var selection = EditorInterface.get_selection().get_selected_nodes()
if selection.any(func (node): return node is CSGShape3D):
show()
else:
hide()
func _ready():
picker_button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
# Connect material picker button if not connected via scene
if not picker_button.pressed.is_connected(_on_material_picker_pressed):
picker_button.pressed.connect(_on_material_picker_pressed)
set_process_unhandled_key_input(true)
func _unhandled_key_input(event: InputEvent):
# Shortcut: action_key + 1/2/3 to set operation (Union / Intersection / Subtraction)
if not (event is InputEventKey):
return
var ev := event as InputEventKey
if ev.pressed and not ev.echo:
# Ensure action key is held (config.action_key)
if Input.is_key_pressed(config.action_key):
match ev.physical_keycode:
KEY_1, KEY_KP_1:
set_operation(0)
_accept_shortcut_feedback("Union")
KEY_2, KEY_KP_2:
set_operation(1)
_accept_shortcut_feedback("Intersection")
KEY_3, KEY_KP_3:
set_operation(2)
_accept_shortcut_feedback("Subtraction")
func _accept_shortcut_feedback(label: String):
# Provide lightweight visual/editor feedback. Avoid static call to non-existent get_status_bar in Godot 4.
# Fallback: print to output.
var ei = EditorInterface
if ei:
# Some editor builds expose status bar via base control's children - skip deep search for now.
print("CSG Operation: %s" % label)
else:
print("CSG Operation: %s" % label)
func _on_box_pressed():
create_csg(CSGBox3D)
func _on_cylinder_pressed():
create_csg(CSGCylinder3D)
func _on_mesh_pressed():
create_csg(CSGMesh3D)
func _on_polygon_pressed():
create_csg(CSGPolygon3D)
func _on_sphere_pressed():
create_csg(CSGSphere3D)
func _on_torus_pressed():
create_csg(CSGTorus3D)
# Operation Toggle (accept optional arg for signal variations)
func _on_operation_pressed(val := 0):
set_operation(val)
func _on_config_pressed():
var config_view_scene = preload("res://addons/csg_toolkit/scenes/config_window.tscn")
var config_view = config_view_scene.instantiate()
config_view.close_requested.connect(func ():
get_tree().root.remove_child(config_view)
config_view.queue_free()
)
get_tree().root.add_child(config_view)
func _request_material():
var dialog = EditorFileDialog.new()
dialog.title = "Select Material"
dialog.display_mode = EditorFileDialog.DISPLAY_LIST
dialog.filters = ["*.tres, *.material, *.res"]
dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE
dialog.position = ((EditorInterface.get_base_control().size / 2) as Vector2i) - dialog.size
dialog.close_requested.connect(func ():
get_tree().root.remove_child(dialog)
dialog.queue_free()
)
get_tree().root.add_child(dialog)
dialog.show()
var res_path = await dialog.file_selected
var res = ResourceLoader.load(res_path)
if res == null:
return
if res is BaseMaterial3D:
update_material(res)
elif res is ShaderMaterial:
update_shader(res)
else:
return
var previewer = EditorInterface.get_resource_previewer()
previewer.queue_edited_resource_preview(res, self, "_update_picker_icon", null)
func _update_picker_icon(path, preview, thumbnail, userdata):
if preview:
picker_button.icon = preview
func set_operation(val: int):
match val:
0: operation = CSGShape3D.OPERATION_UNION
1: operation = CSGShape3D.OPERATION_INTERSECTION
2: operation = CSGShape3D.OPERATION_SUBTRACTION
_: operation = CSGShape3D.OPERATION_UNION
func update_material(material: BaseMaterial3D):
selected_material = material
selected_shader = null
func update_shader(shader: ShaderMaterial):
selected_material = null
selected_shader = shader
func create_csg(type: Variant):
var selection = EditorInterface.get_selection()
var selected_nodes = selection.get_selected_nodes()
if selected_nodes.is_empty() or !(selected_nodes[0] is CSGShape3D):
push_warning("Select a CSGShape3D to add a new CSG node")
return
var selected_node: CSGShape3D = selected_nodes[0]
var csg: CSGShape3D
match type:
CSGBox3D: csg = CSGBox3D.new()
CSGCylinder3D: csg = CSGCylinder3D.new()
CSGSphere3D: csg = CSGSphere3D.new()
CSGMesh3D: csg = CSGMesh3D.new()
CSGPolygon3D: csg = CSGPolygon3D.new()
CSGTorus3D: csg = CSGTorus3D.new()
csg.operation = operation
if selected_material:
csg.material = selected_material
elif selected_shader:
csg.material = selected_shader
if (selected_node.get_owner() == null):
return
var parent: Node
var add_as_child := false
# Behavior inversion now uses secondary_action_key (e.g. Alt) instead of primary action key
var invert := Input.is_key_pressed(config.secondary_action_key)
if config.default_behavior == CsgTkConfig.CSGBehavior.SIBLING:
add_as_child = invert
else:
add_as_child = !invert
parent = selected_node if add_as_child else selected_node.get_parent()
if parent == null:
return
# Try undo manager path if plugin provided one
if CsgToolkit.undo_manager:
var insert_index := parent.get_child_count()
CsgToolkit.undo_manager.create_action("Add %s" % csg.get_class())
# DO methods
CsgToolkit.undo_manager.add_do_method(self, "_undoable_add_csg", parent, csg, selected_node.get_owner(), selected_node.global_position, insert_index)
CsgToolkit.undo_manager.add_do_method(self, "_select_created_csg", csg)
# UNDO methods
CsgToolkit.undo_manager.add_undo_method(self, "_undoable_remove_csg", parent, csg)
CsgToolkit.undo_manager.add_undo_method(self, "_clear_selection_if", csg)
CsgToolkit.undo_manager.commit_action()
else:
parent.add_child(csg, true)
csg.owner = selected_node.get_owner()
csg.global_position = selected_node.global_position
call_deferred("_select_created_csg", csg)
func _deferred_select(csg: Node):
call_deferred("_select_created_csg", csg)
func _undoable_add_csg(parent: Node, csg: CSGShape3D, owner_ref: Node, global_pos: Vector3, insert_index: int):
if csg.get_parent() != parent:
parent.add_child(csg, true)
if insert_index >= 0 and insert_index < parent.get_child_count():
parent.move_child(csg, insert_index)
csg.owner = owner_ref
csg.global_position = global_pos
func _undoable_remove_csg(parent: Node, csg: CSGShape3D):
if csg.get_parent() == parent:
parent.remove_child(csg)
# Intentionally do NOT free node so redo can re-add it. If you need memory, implement a recreate pattern instead.
func _clear_selection_if(csg: Node):
var selection = EditorInterface.get_selection()
if selection:
var nodes: Array = selection.get_selected_nodes()
if csg in nodes:
selection.remove_node(csg)
func _select_created_csg(csg: Node):
var selection = EditorInterface.get_selection()
selection.clear()
selection.add_node(csg)
func _add_as_child(selected_node: CSGShape3D, csg: CSGShape3D):
selected_node.add_child(csg, true)
csg.owner = selected_node.get_owner()
csg.global_position = selected_node.global_position
func _add_as_sibling(selected_node: CSGShape3D, csg: CSGShape3D):
selected_node.get_parent().add_child(csg, true)
csg.owner = selected_node.get_owner()
csg.global_position = selected_node.global_position
func _on_material_picker_pressed() -> void:
_request_material()

View File

@@ -0,0 +1 @@
uid://dr5f1egll7hdq

View File

@@ -0,0 +1,280 @@
@tool
class_name CSGSpreader3D extends CSGCombiner3D
const SPREADER_NODE_META = "SPREADER_NODE_META"
const MAX_INSTANCES = 20000
var _dirty: bool = false
var _generation_in_progress := false
var _template_node_path: NodePath
@export var template_node_path: NodePath:
get: return _template_node_path
set(value):
_template_node_path = value
_mark_dirty()
var _hide_template: bool = true
@export var hide_template: bool = true:
get: return _hide_template
set(value):
_hide_template = value
_update_template_visibility()
var _spread_area_3d: Shape3D = null
@export var spread_area_3d: Shape3D = null:
get: return _spread_area_3d
set(value):
_spread_area_3d = value
_mark_dirty()
var _max_count: int = 10
@export var max_count: int = 10:
get: return _max_count
set(value):
_max_count = clamp(value, 1, 100000)
_mark_dirty()
@export_group("Spread Options")
var _noise_threshold: float = 0.5
@export var noise_threshold: float = 0.5:
get: return _noise_threshold
set(value):
_noise_threshold = clamp(value, 0.0, 1.0)
_mark_dirty()
var _seed: int = 0
@export var seed: int = 0:
get: return _seed
set(value):
_seed = value
_mark_dirty()
var _allow_rotation: bool = false
@export var allow_rotation: bool = false:
get: return _allow_rotation
set(value):
_allow_rotation = value
_mark_dirty()
var _allow_scale: bool = false
@export var allow_scale: bool = false:
get: return _allow_scale
set(value):
_allow_scale = value
_mark_dirty()
var _snap_distance = 0
@export var snap_distance = 0:
get: return _snap_distance
set(value):
_snap_distance = value
_mark_dirty()
@export_group("Collision Options")
var _avoid_overlaps: bool = false
@export var avoid_overlaps: bool = false:
get: return _avoid_overlaps
set(value):
_avoid_overlaps = value
_mark_dirty()
var _min_distance: float = 1.0
@export var min_distance: float = 1.0:
get: return _min_distance
set(value):
_min_distance = max(0.0, value)
_mark_dirty()
var _max_placement_attempts: int = 100
@export var max_placement_attempts: int = 100:
get: return _max_placement_attempts
set(value):
_max_placement_attempts = clamp(value, 10, 1000)
_mark_dirty()
@export var estimated_instances: int = 0
var rng: RandomNumberGenerator
func _ready():
rng = RandomNumberGenerator.new()
_mark_dirty()
# Generate instances in-game on ready
if not Engine.is_editor_hint():
call_deferred("spread_template")
func _process(_delta):
if not Engine.is_editor_hint(): return
if _dirty and not _generation_in_progress:
_dirty = false
call_deferred("spread_template")
func _exit_tree():
if not Engine.is_editor_hint():
return
clear_children()
func _mark_dirty():
_dirty = true
func _update_template_visibility():
if not is_inside_tree():
return
var template_node = get_node_or_null(template_node_path)
if template_node and template_node is Node3D:
template_node.visible = not _hide_template
func clear_children():
var children_to_remove = []
for child in get_children(true):
if child.has_meta(SPREADER_NODE_META):
children_to_remove.append(child)
for child in children_to_remove:
remove_child(child)
child.queue_free()
func get_random_position_in_area() -> Vector3:
if spread_area_3d is SphereShape3D:
var radius = spread_area_3d.get_radius()
var u = rng.randf()
var v = rng.randf()
var theta = u * TAU
var phi = acos(2.0 * v - 1.0)
var r = radius * pow(rng.randf(), 1.0/3.0)
return Vector3(r * sin(phi) * cos(theta), r * sin(phi) * sin(theta), r * cos(phi))
if spread_area_3d is BoxShape3D:
var size = spread_area_3d.size
return Vector3(
rng.randf_range(-size.x * 0.5, size.x * 0.5),
rng.randf_range(-size.y * 0.5, size.y * 0.5),
rng.randf_range(-size.z * 0.5, size.z * 0.5)
)
if spread_area_3d is CapsuleShape3D:
var radius = spread_area_3d.get_radius()
var height = spread_area_3d.get_height() * 0.5
if rng.randf() < noise_threshold:
var angle = rng.randf() * TAU
var r = radius * sqrt(rng.randf())
return Vector3(r * cos(angle), rng.randf_range(-height, height), r * sin(angle))
else:
var hemisphere_y = height if rng.randf() < noise_threshold else -height
var u = rng.randf()
var v = rng.randf()
var theta = u * TAU
var phi = acos(1.0 - v)
var r = radius * pow(rng.randf(), 1.0/3.0)
return Vector3(
r * sin(phi) * cos(theta),
hemisphere_y + r * cos(phi) * (1 if hemisphere_y > 0 else -1),
r * sin(phi) * sin(theta)
)
if spread_area_3d is CylinderShape3D:
var radius = spread_area_3d.get_radius()
var height = spread_area_3d.get_height() * 0.5
var angle = rng.randf() * TAU
var r = radius * sqrt(rng.randf())
return Vector3(r * cos(angle), rng.randf_range(-height, height), r * sin(angle))
if spread_area_3d is HeightMapShape3D:
var width = spread_area_3d.map_width
var depth = spread_area_3d.map_depth
if width <= 0 or depth <= 0 or spread_area_3d.map_data.size() == 0:
return Vector3.ZERO
var x = rng.randi_range(0, width - 1)
var z = rng.randi_range(0, depth - 1)
var index = x + z * width
if index < spread_area_3d.map_data.size():
return Vector3(x, spread_area_3d.map_data[index], z)
return Vector3.ZERO
if spread_area_3d is WorldBoundaryShape3D:
var bound = 100.0
return Vector3(rng.randf_range(-bound, bound), 0, rng.randf_range(-bound, bound))
if spread_area_3d is ConvexPolygonShape3D or spread_area_3d is ConcavePolygonShape3D:
var pts = spread_area_3d.points if spread_area_3d.has_method("get_points") else []
if pts.size() == 0:
return Vector3.ZERO
var min_point = pts[0]
var max_point = pts[0]
for p in pts:
min_point = min_point.min(p)
max_point = max_point.max(p)
return Vector3(
rng.randf_range(min_point.x, max_point.x),
rng.randf_range(min_point.y, max_point.y),
rng.randf_range(min_point.z, max_point.z)
)
push_warning("CSGSpreader3D: Shape type not supported")
return Vector3.ZERO
func spread_template():
if _generation_in_progress:
return
_generation_in_progress = true
if not spread_area_3d:
_generation_in_progress = false
return
clear_children()
var template_node = get_node_or_null(template_node_path)
if not template_node:
_generation_in_progress = false
return
rng.seed = _seed
var instances_created = 0
var placed_positions = []
var budget = min(_max_count, MAX_INSTANCES)
if _max_count > MAX_INSTANCES:
push_warning("CSGSpreader3D: max_count %s exceeds cap %s. Limiting." % [_max_count, MAX_INSTANCES])
for i in range(budget):
var noise_value = rng.randf()
if noise_value <= _noise_threshold:
continue
var position_found = false
var final_position = Vector3.ZERO
var attempts = _max_placement_attempts if _avoid_overlaps else 1
for attempt in range(attempts):
var test_position = get_random_position_in_area()
if not _avoid_overlaps:
final_position = test_position
position_found = true
break
var overlap = false
for existing_pos in placed_positions:
if test_position.distance_to(existing_pos) < _min_distance:
overlap = true
break
if not overlap:
final_position = test_position
position_found = true
break
if not position_found:
continue
var instance = template_node.duplicate()
if instance == null:
continue
instance.set_meta(SPREADER_NODE_META, true)
instance.transform.origin = final_position
# Ensure instance is visible regardless of template visibility
if instance is Node3D:
instance.visible = true
placed_positions.append(final_position)
if _allow_rotation:
var rotation_y = rng.randf_range(0, TAU)
instance.rotate_y(rotation_y)
if _allow_scale:
var scale_factor = rng.randf_range(0.5, 2.0)
instance.scale *= scale_factor
add_child(instance)
instances_created += 1
estimated_instances = instances_created
_update_template_visibility()
_generation_in_progress = false
func bake_instances():
if get_child_count() == 0:
return
var stack = []
stack.append_array(get_children())
while stack.size() > 0:
var node = stack.pop_back()
node.set_owner(owner)
stack.append_array(node.get_children())

View File

@@ -0,0 +1 @@
uid://rgfomqnhbhpk

View File

@@ -0,0 +1,99 @@
@tool
extends Node
class_name CsgTkConfig
# ProjectSettings paths
const SETTING_DEFAULT_BEHAVIOR = "addons/csg_toolkit/default_behavior"
const SETTING_ACTION_KEY = "addons/csg_toolkit/action_key"
const SETTING_SECONDARY_ACTION_KEY = "addons/csg_toolkit/secondary_action_key"
const SETTING_AUTO_HIDE = "addons/csg_toolkit/auto_hide"
# Default values
const DEFAULT_DEFAULT_BEHAVIOR = CSGBehavior.SIBLING
const DEFAULT_ACTION_KEY = KEY_SHIFT
const DEFAULT_SECONDARY_ACTION_KEY = KEY_ALT
const DEFAULT_AUTO_HIDE = true
# Configurable properties
## Default behavior when adding new CSG nodes
var default_behavior: CSGBehavior = CSGBehavior.SIBLING:
get: return _get_setting(SETTING_DEFAULT_BEHAVIOR, DEFAULT_DEFAULT_BEHAVIOR)
set(value): _set_setting(SETTING_DEFAULT_BEHAVIOR, value)
## Key to hold for primary action (e.g., adding CSG nodes)
var action_key: Key = KEY_SHIFT:
get: return _get_setting(SETTING_ACTION_KEY, DEFAULT_ACTION_KEY)
set(value): _set_setting(SETTING_ACTION_KEY, value)
## Key to hold for secondary action (e.g., alternative CSG operations)
var secondary_action_key: Key = KEY_ALT:
get: return _get_setting(SETTING_SECONDARY_ACTION_KEY, DEFAULT_SECONDARY_ACTION_KEY)
set(value): _set_setting(SETTING_SECONDARY_ACTION_KEY, value)
## Whether to auto-hide the CSG toolkit UI when not in use
var auto_hide: bool = true:
get: return _get_setting(SETTING_AUTO_HIDE, DEFAULT_AUTO_HIDE)
set(value): _set_setting(SETTING_AUTO_HIDE, value)
signal config_saved()
enum CSGBehavior { SIBLING, CHILD }
func _enter_tree():
_ensure_settings_exist()
func _ensure_settings_exist():
"""Register settings in ProjectSettings if they don't exist."""
if not ProjectSettings.has_setting(SETTING_DEFAULT_BEHAVIOR):
ProjectSettings.set_setting(SETTING_DEFAULT_BEHAVIOR, DEFAULT_DEFAULT_BEHAVIOR)
ProjectSettings.set_initial_value(SETTING_DEFAULT_BEHAVIOR, DEFAULT_DEFAULT_BEHAVIOR)
ProjectSettings.add_property_info({
"name": SETTING_DEFAULT_BEHAVIOR,
"type": TYPE_INT,
"hint": PROPERTY_HINT_ENUM,
"hint_string": "Sibling,Child"
})
if not ProjectSettings.has_setting(SETTING_ACTION_KEY):
ProjectSettings.set_setting(SETTING_ACTION_KEY, DEFAULT_ACTION_KEY)
ProjectSettings.set_initial_value(SETTING_ACTION_KEY, DEFAULT_ACTION_KEY)
ProjectSettings.add_property_info({
"name": SETTING_ACTION_KEY,
"type": TYPE_INT,
"hint": PROPERTY_HINT_NONE
})
if not ProjectSettings.has_setting(SETTING_SECONDARY_ACTION_KEY):
ProjectSettings.set_setting(SETTING_SECONDARY_ACTION_KEY, DEFAULT_SECONDARY_ACTION_KEY)
ProjectSettings.set_initial_value(SETTING_SECONDARY_ACTION_KEY, DEFAULT_SECONDARY_ACTION_KEY)
ProjectSettings.add_property_info({
"name": SETTING_SECONDARY_ACTION_KEY,
"type": TYPE_INT,
"hint": PROPERTY_HINT_NONE
})
if not ProjectSettings.has_setting(SETTING_AUTO_HIDE):
ProjectSettings.set_setting(SETTING_AUTO_HIDE, DEFAULT_AUTO_HIDE)
ProjectSettings.set_initial_value(SETTING_AUTO_HIDE, DEFAULT_AUTO_HIDE)
ProjectSettings.add_property_info({
"name": SETTING_AUTO_HIDE,
"type": TYPE_BOOL
})
func _get_setting(path: String, default_value: Variant) -> Variant:
"""Get a setting from ProjectSettings."""
return ProjectSettings.get_setting(path, default_value)
func _set_setting(path: String, value: Variant):
"""Set a setting in ProjectSettings."""
ProjectSettings.set_setting(path, value)
func save_config():
"""Save settings to project.godot file."""
var err = ProjectSettings.save()
if err == OK:
print("CsgToolkit: Saved Config to ProjectSettings")
config_saved.emit()
else:
push_error("CsgToolkit: Failed to save config - error code %d" % err)

View File

@@ -0,0 +1 @@
uid://w8ad8q4lneis

View File

@@ -0,0 +1,41 @@
@tool
class_name CSGTopToolkitBar extends Control
func _enter_tree():
EditorInterface.get_selection().selection_changed.connect(_on_selection_changed)
_on_selection_changed()
# Attempt to find buttons and add tooltips if present
var refresh_btn = find_child("Refresh", true, false)
if refresh_btn and refresh_btn is Button:
refresh_btn.tooltip_text = "Regenerate preview instances"
var bake_btn = find_child("Bake", true, false)
if bake_btn and bake_btn is Button:
bake_btn.tooltip_text = "Bake generated instances into the scene (makes them persistent)"
func _exit_tree():
EditorInterface.get_selection().selection_changed.disconnect(_on_selection_changed)
func _on_selection_changed():
var selection = EditorInterface.get_selection().get_selected_nodes()
if selection.is_empty():
hide()
elif selection[0] is CSGRepeater3D or selection[0] is CSGSpreader3D:
show()
else:
hide()
func _on_refresh_pressed():
var selection = EditorInterface.get_selection().get_selected_nodes()
if (selection.is_empty()):
return
if selection[0] is CSGRepeater3D:
selection[0].call("repeat_template")
elif selection[0] is CSGSpreader3D:
selection[0].call("spread_template")
func _on_bake_pressed():
var selection = EditorInterface.get_selection().get_selected_nodes()
if selection.is_empty():
return
if selection[0] is CSGRepeater3D or selection[0] is CSGSpreader3D:
selection[0].call("bake_instances")

View File

@@ -0,0 +1 @@
uid://dk6dt8fk1s43s

View File

@@ -0,0 +1,31 @@
@tool
class_name CSGCircularPattern
extends CSGPattern
@export var radius: float = 5.0
@export var points: int = 8
@export var layers: int = 1
## If 0 use template_size.y
@export var layer_height: float = 0.0
## Additional gap added per layer beyond base height
@export var layer_spacing: float = 0.0
func _generate(ctx: Dictionary) -> Array:
var positions: Array = []
var template_size: Vector3 = ctx.get("template_size", Vector3.ONE)
var rad: float = max(0.0, radius)
var count: int = max(1, points)
if count <= 1:
return [Vector3.ZERO]
var lyr_count = max(1, layers)
var base_y = layer_height if layer_height > 0.0 else template_size.y
var step_y = base_y + max(0.0, layer_spacing)
for i in range(count):
var angle = (i * TAU) / count
var base_pos = Vector3(cos(angle) * rad, 0, sin(angle) * rad)
for layer in range(lyr_count):
positions.append(base_pos + Vector3(0, layer * step_y, 0))
return positions
func get_estimated_count(ctx: Dictionary) -> int:
return max(1, points) * max(1, layers)

View File

@@ -0,0 +1 @@
uid://b3ws8vwtsqmjt

View File

@@ -0,0 +1,19 @@
@tool
@abstract
class_name CSGPattern
extends Resource
## Base pattern interface. Subclasses implement _generate(RepeaterContext) returning Array[Vector3].
# Common interface call
func generate(ctx: Dictionary) -> Array:
# ctx expected keys: repeat: Vector3i, spacing: Vector3, rng: RandomNumberGenerator, step_spacing: Vector3, user: Node (repeater)
return _generate(ctx)
func _generate(_ctx: Dictionary) -> Array:
return []
func get_estimated_count(ctx: Dictionary) -> int:
# Default: fallback to generating (may be overridden for performance/accuracy)
var arr = _generate(ctx)
return arr.size()

View File

@@ -0,0 +1 @@
uid://dbavf0pl65chb

View File

@@ -0,0 +1,33 @@
@tool
class_name CSGGridPattern
extends CSGPattern
@export var count_x: int = 2
@export var count_y: int = 1
@export var count_z: int = 1
@export var spacing: Vector3 = Vector3.ZERO
## If true, automatically adds template AABB size to spacing for proper object separation
@export var use_template_size: bool = true
func _generate(ctx: Dictionary) -> Array:
var positions: Array = []
var template_size: Vector3 = ctx.get("template_size", Vector3.ONE)
var jitter: float = ctx.get("position_jitter", 0.0)
var rng: RandomNumberGenerator = ctx.rng
var cx = max(1, count_x)
var cy = max(1, count_y)
var cz = max(1, count_z)
var base_step: Vector3 = (template_size if use_template_size else Vector3.ZERO) + spacing
for x in range(cx):
for y in range(cy):
for z in range(cz):
var position = Vector3(x * base_step.x, y * base_step.y, z * base_step.z)
if jitter > 0.0:
position += Vector3(
rng.randf_range(-jitter, jitter),
rng.randf_range(-jitter, jitter),
rng.randf_range(-jitter, jitter)
)
positions.append(position)
return positions

View File

@@ -0,0 +1 @@
uid://bc1sxg4vy464o

View File

@@ -0,0 +1,124 @@
@tool
class_name CSGNoisePattern
extends CSGPattern
## Generates instance positions based on noise sampling in a 3D volume
## Instances are placed where noise value exceeds the threshold
##
@export var bounds: Vector3 = Vector3(10, 10, 10)
##
@export var sample_density: Vector3i = Vector3i(20, 1, 20)
##
@export_range(0.0, 1.0) var noise_threshold: float = 0.5
##
@export var noise_seed: int = 0
##
@export_range(0.01, 100) var noise_frequency: float = 0.1
##
@export_enum("Simplex", "Simplex Smooth", "Cellular", "Perlin", "Value Cubic", "Value") var noise_type: int = 0
##
@export_enum("None", "OpenSimplex2", "OpenSimplex2S", "Cellular", "Perlin", "Value Cubic", "Value") var fractal_type: int = 0
##
@export_range(1, 8) var fractal_octaves: int = 3
##
@export var use_template_size: bool = false
var noise: FastNoiseLite
func _init():
noise = FastNoiseLite.new()
_update_noise()
func _update_noise():
if not noise:
noise = FastNoiseLite.new()
noise.seed = noise_seed
noise.frequency = noise_frequency
noise.fractal_octaves = fractal_octaves
# Map noise_type enum to FastNoiseLite types
match noise_type:
0: noise.noise_type = FastNoiseLite.TYPE_SIMPLEX
1: noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
2: noise.noise_type = FastNoiseLite.TYPE_CELLULAR
3: noise.noise_type = FastNoiseLite.TYPE_PERLIN
4: noise.noise_type = FastNoiseLite.TYPE_VALUE_CUBIC
5: noise.noise_type = FastNoiseLite.TYPE_VALUE
# Map fractal_type enum to FastNoiseLite fractal types
match fractal_type:
0: noise.fractal_type = FastNoiseLite.FRACTAL_NONE
1: noise.fractal_type = FastNoiseLite.FRACTAL_FBM
2: noise.fractal_type = FastNoiseLite.FRACTAL_RIDGED
3: noise.fractal_type = FastNoiseLite.FRACTAL_PING_PONG
func _generate(ctx: Dictionary) -> Array:
_update_noise()
var positions: Array = []
var template_size: Vector3 = ctx.get("template_size", Vector3.ONE) if use_template_size else Vector3.ZERO
var jitter: float = ctx.get("position_jitter", 0.0)
var rng: RandomNumberGenerator = ctx.get("rng", RandomNumberGenerator.new())
var effective_bounds = bounds
var sample_count = sample_density
# Calculate step size for sampling
var step = Vector3(
effective_bounds.x / max(1, sample_count.x),
effective_bounds.y / max(1, sample_count.y),
effective_bounds.z / max(1, sample_count.z)
)
# Start from negative half to center the pattern around origin
var start_pos = -effective_bounds * 0.5
# Sample noise at regular intervals
for x in range(sample_count.x):
for y in range(sample_count.y):
for z in range(sample_count.z):
var sample_pos = start_pos + Vector3(
x * step.x + step.x * 0.5,
y * step.y + step.y * 0.5,
z * step.z + step.z * 0.5
)
# Get noise value at this position (normalized to 0-1)
var noise_value = (noise.get_noise_3d(sample_pos.x, sample_pos.y, sample_pos.z) + 1.0) * 0.5
# Only place instance if noise exceeds threshold
if noise_value >= noise_threshold:
var final_pos = sample_pos
# Apply template size offset if enabled
if use_template_size:
final_pos += template_size * Vector3(x, y, z)
# Apply jitter
if jitter > 0.0:
final_pos += Vector3(
rng.randf_range(-jitter, jitter),
rng.randf_range(-jitter, jitter),
rng.randf_range(-jitter, jitter)
)
positions.append(final_pos)
return positions
func get_estimated_count(ctx: Dictionary) -> int:
# Rough estimate: total samples * (1 - threshold)
# Higher threshold = fewer instances
var total_samples = max(1, sample_density.x) * max(1, sample_density.y) * max(1, sample_density.z)
var estimated = int(total_samples * (1.0 - noise_threshold))
return max(1, estimated)

View File

@@ -0,0 +1 @@
uid://3il6xs7cr7gj

View File

@@ -0,0 +1,39 @@
@tool
class_name CSGSpiralPattern
extends CSGPattern
@export var turns: float = 2.0
@export var start_radius: float = 0.5
@export var end_radius: float = 5.0
## If > 0 overrides vertical spread based on repeat & step
@export var total_height: float = 0.0
@export var use_radius_curve: bool = false
@export var radius_curve: Curve
@export var points: int = 32
func _generate(ctx: Dictionary) -> Array:
var positions: Array = []
var template_size: Vector3 = ctx.get("template_size", Vector3.ONE)
var t_turns: float = max(0.1, turns)
var r_start: float = max(0.0, start_radius)
var r_end: float = max(r_start, end_radius)
var total: int = max(2, points)
if total <= 1:
return [Vector3.ZERO]
for i in range(total):
var t: float = float(i) / float(total - 1)
var angle = t * t_turns * TAU
var curve_t = t
if use_radius_curve and radius_curve and radius_curve.get_point_count() > 0:
curve_t = clamp(radius_curve.sample(t), 0.0, 1.0)
var radius = lerp(r_start, r_end, curve_t)
var y_pos: float = t * (total_height if total_height > 0.0 else template_size.y * 1.0)
positions.append(Vector3(
cos(angle) * radius,
y_pos,
sin(angle) * radius
))
return positions
func get_estimated_count(ctx: Dictionary) -> int:
return max(2, points)

View File

@@ -0,0 +1 @@
uid://belgcjd0ys212

8
addons/forge/Forge.props Normal file
View File

@@ -0,0 +1,8 @@
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Gamesmiths.Forge" Version="0.2.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,109 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System.Diagnostics;
using Gamesmiths.Forge.Godot.Editor;
using Gamesmiths.Forge.Godot.Editor.Attributes;
using Gamesmiths.Forge.Godot.Editor.Cues;
using Gamesmiths.Forge.Godot.Editor.Tags;
using Godot;
namespace Gamesmiths.Forge.Godot;
[Tool]
public partial class ForgePluginLoader : EditorPlugin
{
private const string AutoloadPath = "uid://ba8fquhtwu5mu";
private const string PluginScenePath = "uid://pjscvogl6jak";
private EditorDock? _editorDock;
private PanelContainer? _dockedScene;
private TagContainerInspectorPlugin? _tagContainerInspectorPlugin;
private TagInspectorPlugin? _tagInspectorPlugin;
private AttributeSetInspectorPlugin? _attributeSetInspectorPlugin;
private CueHandlerInspectorPlugin? _cueHandlerInspectorPlugin;
private AttributeEditorPlugin? _attributeEditorPlugin;
public override void _EnterTree()
{
PackedScene pluginScene = ResourceLoader.Load<PackedScene>(PluginScenePath);
_editorDock = new EditorDock
{
Title = "Forge",
DockIcon = GD.Load<Texture2D>("uid://cu6ncpuumjo20"),
DefaultSlot = EditorDock.DockSlot.RightUl,
};
_dockedScene = (PanelContainer)pluginScene.Instantiate();
_dockedScene.GetNode<TagsEditor>("%Tags").IsPluginInstance = true;
_editorDock.AddChild(_dockedScene);
AddDock(_editorDock);
_tagContainerInspectorPlugin = new TagContainerInspectorPlugin();
AddInspectorPlugin(_tagContainerInspectorPlugin);
_tagInspectorPlugin = new TagInspectorPlugin();
AddInspectorPlugin(_tagInspectorPlugin);
_attributeSetInspectorPlugin = new AttributeSetInspectorPlugin();
AddInspectorPlugin(_attributeSetInspectorPlugin);
_cueHandlerInspectorPlugin = new CueHandlerInspectorPlugin();
AddInspectorPlugin(_cueHandlerInspectorPlugin);
_attributeEditorPlugin = new AttributeEditorPlugin();
AddInspectorPlugin(_attributeEditorPlugin);
AddToolMenuItem("Repair assets tags", new Callable(this, MethodName.CallAssetRepairTool));
}
public override void _ExitTree()
{
Debug.Assert(_editorDock is not null, $"{nameof(_editorDock)} should have been initialized on _Ready().");
Debug.Assert(_dockedScene is not null, $"{nameof(_dockedScene)} should have been initialized on _Ready().");
RemoveDock(_editorDock);
_editorDock.QueueFree();
_dockedScene.Free();
RemoveInspectorPlugin(_tagContainerInspectorPlugin);
RemoveInspectorPlugin(_tagInspectorPlugin);
RemoveInspectorPlugin(_attributeSetInspectorPlugin);
RemoveInspectorPlugin(_cueHandlerInspectorPlugin);
RemoveInspectorPlugin(_attributeEditorPlugin);
RemoveToolMenuItem("Repair assets tags");
}
public override void _EnablePlugin()
{
base._EnablePlugin();
var config = ProjectSettings.LoadResourcePack(AutoloadPath);
if (config)
{
GD.PrintErr("Failed to load script at res://addons/forge/core/ForgeBootstrap.cs");
return;
}
if (!ProjectSettings.HasSetting("autoload/Forge Bootstrap"))
{
ProjectSettings.SetSetting("autoload/Forge Bootstrap", AutoloadPath);
ProjectSettings.Save();
}
}
public override void _DisablePlugin()
{
if (ProjectSettings.HasSetting("autoload/Forge Bootstrap"))
{
ProjectSettings.Clear("autoload/Forge Bootstrap");
ProjectSettings.Save();
}
}
private static void CallAssetRepairTool()
{
AssetRepairTool.RepairAllAssetsTags();
}
}
#endif

View File

@@ -0,0 +1 @@
uid://686m2ah4as6w

21
addons/forge/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Gamesmiths Guild
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

58
addons/forge/README.md Normal file
View File

@@ -0,0 +1,58 @@
# Forge for Godot
Forge for Godot is an Unreal GAS-like gameplay framework for the Godot Engine.
It integrates the [Forge Gameplay System](https://github.com/gamesmiths-guild/forge) into Godot, providing a robust, data-driven foundation for gameplay features such as attributes, effects, gameplay tags, abilities, events, and cues, fully aligned with Godots node, resource, and editor workflows.
This plugin enables you to:
- Use **ForgeEntity** nodes or implement `IForgeEntity` to integrate core Forge systems like attributes, effects, abilities, events and tags.
- Define attributes, effects, abilities, cues, and tags directly in the Godot editor.
- Apply and manage gameplay effects with area or raycasting nodes.
- Create hierarchical gameplay tags using the built-in Tags Editor.
- Trigger visual and audio feedback with the Cues system.
- Create player skills, attacks, or behaviors, with support for custom logic, costs, cooldowns, and triggers.
## Features
- **Effects System**: Comprehensive effect application and management, including stacking, periodic, instant, and infinite effects.
- **Attributes System**: Attribute management, supporting sets, modifiers, and configuration.
- **Tags System**: Full hierarchical tag system with Godot editor integration.
- **Abilities System**: Feature-complete ability system, supporting grant/removal, custom behaviors, triggers, cooldowns, and costs.
- **Events System**: Gameplay event bus supporting event-driven logic, subscriptions, and triggers.
- **Cues System**: Visual/audio feedback layer; decouples presentation from game logic.
- **Editor Extensions**: Custom inspector elements and tag editor with Godot integration.
- **Custom Nodes**: Includes nodes like `ForgeEntity`, `ForgeAttributeSet`, `EffectArea2D`, and more.
## Installation
### Requirements
- Godot 4.6 or later with .NET support.
- .NET SDK 8.0 or later.
### Steps
1. Install the plugin via the Godot Asset Library or manually by copying the `addons` folder.
- [Godot Asset Library](https://godotengine.org/asset-library/asset/4239)
- [Manual installation guide](https://docs.godotengine.org/en/stable/tutorials/plugins/editor/installing_plugins.html)
2. Add the following line in your `.csproj` file (before the closing `</Project>` tag). The `.csproj` file can be created through Godot by navigating to `Project > Tools > C# > Create C# solution`:
```xml
<Import Project="addons/forge/Forge.props" />
```
3. Back in the Godot editor, build your project by clicking `Build` in the top-right corner of the script editor.
4. Enable **Forge Gameplay System** in `Project > Project Settings > Plugins`.
## Getting Started
- See the [Quick Start Guide](https://github.com/gamesmiths-guild/forge-godot/blob/main/docs/quick-start.md) for a basic setup.
- Explore [sample scenes](https://github.com/gamesmiths-guild/forge-godot/tree/main/examples) by cloning the full repo.
## Documentation
Full documentation, examples, and advanced usage are available in the [Forge for Godot GitHub repository](https://github.com/gamesmiths-guild/forge-godot).
For technical details about core systems, see the [Forge Gameplay System documentation](https://github.com/gamesmiths-guild/forge/blob/main/docs/README.md).
## License
This plugin is licensed under the same terms as the core [Forge Gameplay System](https://github.com/gamesmiths-guild/forge).

View File

@@ -0,0 +1,312 @@
// Copyright © Gamesmiths Guild.
using System.Collections.Generic;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Effects;
using Gamesmiths.Forge.Godot.Nodes;
using Godot;
namespace Gamesmiths.Forge.Godot.Core;
internal sealed class EffectApplier
{
private record struct EffectKey(EffectData EffectData, EffectOwnership EffectOwnership, int Level);
private readonly List<EffectData> _effects = [];
private readonly Dictionary<IForgeEntity, List<ActiveEffectHandle>> _effectInstances = [];
private readonly Dictionary<EffectKey, Effect> _effectsCache = [];
public EffectApplier(Node node)
{
foreach (Node child in node.GetChildren())
{
if (child is ForgeEffect effectNode && effectNode.EffectData is not null)
{
_effects.Add(effectNode.EffectData.GetEffectData());
}
}
}
public void ApplyEffects(
Node node,
IForgeEntity? effectOwner,
IForgeEntity? effectSource,
int level = 1)
{
if (node is IForgeEntity forgeEntity)
{
ApplyEffects(forgeEntity, effectOwner, effectSource, level);
return;
}
foreach (Node? child in node.GetChildren())
{
if (child is IForgeEntity forgeEntityChild)
{
ApplyEffects(forgeEntityChild, effectOwner, effectSource, level);
return;
}
}
}
public void ApplyEffects<TData>(
Node node,
TData contextData,
IForgeEntity? effectOwner,
IForgeEntity? effectSource,
int level = 1)
{
if (node is IForgeEntity forgeEntity)
{
ApplyEffects(forgeEntity, contextData, effectOwner, effectSource, level);
return;
}
foreach (Node? child in node.GetChildren())
{
if (child is IForgeEntity forgeEntityChild)
{
ApplyEffects(forgeEntityChild, contextData, effectOwner, effectSource, level);
return;
}
}
}
public void AddEffects(
Node node,
IForgeEntity? effectOwner,
IForgeEntity? effectSource,
int level)
{
if (node is IForgeEntity forgeEntity)
{
AddEffects(forgeEntity, effectOwner, effectSource, level);
return;
}
foreach (Node? child in node.GetChildren())
{
if (child is IForgeEntity forgeEntityChild)
{
AddEffects(forgeEntityChild, effectOwner, effectSource, level);
return;
}
}
}
public void AddEffects<TData>(
Node node,
TData contextData,
IForgeEntity? effectOwner,
IForgeEntity? effectSource,
int level)
{
if (node is IForgeEntity forgeEntity)
{
AddEffects(forgeEntity, contextData, effectOwner, effectSource, level);
return;
}
foreach (Node? child in node.GetChildren())
{
if (child is IForgeEntity forgeEntityChild)
{
AddEffects(forgeEntityChild, contextData, effectOwner, effectSource, level);
return;
}
}
}
public void RemoveEffects(Node node)
{
if (node is IForgeEntity forgeEntity)
{
RemoveEffects(forgeEntity);
return;
}
foreach (Node? child in node.GetChildren())
{
if (child is IForgeEntity forgeEntityChild)
{
RemoveEffects(forgeEntityChild);
return;
}
}
}
private void ApplyEffects(
IForgeEntity forgeEntity,
IForgeEntity? effectOwner,
IForgeEntity? effectSource,
int level)
{
var effectOwnership = new EffectOwnership(effectOwner, effectSource);
foreach (EffectData effectData in _effects)
{
var key = new EffectKey(effectData, effectOwnership, level);
if (_effectsCache.TryGetValue(key, out Effect? cachedEffect))
{
forgeEntity.EffectsManager.ApplyEffect(cachedEffect);
continue;
}
var effect = new Effect(
effectData,
new EffectOwnership(effectOwner, effectSource),
level);
_effectsCache[key] = effect;
forgeEntity.EffectsManager.ApplyEffect(effect);
}
}
private void ApplyEffects<TData>(
IForgeEntity forgeEntity,
TData contextData,
IForgeEntity? effectOwner,
IForgeEntity? effectSource,
int level)
{
var effectOwnership = new EffectOwnership(effectOwner, effectSource);
foreach (EffectData effectData in _effects)
{
var key = new EffectKey(effectData, effectOwnership, level);
if (_effectsCache.TryGetValue(key, out Effect? cachedEffect))
{
forgeEntity.EffectsManager.ApplyEffect(cachedEffect);
continue;
}
var effect = new Effect(
effectData,
new EffectOwnership(effectOwner, effectSource),
level);
_effectsCache[key] = effect;
forgeEntity.EffectsManager.ApplyEffect(effect, contextData);
}
}
private void AddEffects(
IForgeEntity forgeEntity,
IForgeEntity?
effectOwner,
IForgeEntity? effectSource,
int level)
{
var instanceEffects = new List<ActiveEffectHandle>();
if (!_effectInstances.TryAdd(forgeEntity, instanceEffects))
{
instanceEffects = _effectInstances[forgeEntity];
}
var effectOwnership = new EffectOwnership(effectOwner, effectSource);
foreach (EffectData effectData in _effects)
{
var key = new EffectKey(effectData, effectOwnership, level);
ActiveEffectHandle? handle;
if (_effectsCache.TryGetValue(key, out Effect? cachedEffect))
{
handle = forgeEntity.EffectsManager.ApplyEffect(cachedEffect);
if (handle is null)
{
continue;
}
instanceEffects.Add(handle);
continue;
}
var effect = new Effect(
effectData,
new EffectOwnership(effectOwner, effectSource),
level);
handle = forgeEntity.EffectsManager.ApplyEffect(effect);
if (handle is null)
{
continue;
}
instanceEffects.Add(handle);
}
}
private void AddEffects<TData>(
IForgeEntity forgeEntity,
TData contextData,
IForgeEntity? effectOwner,
IForgeEntity? effectSource,
int level)
{
var instanceEffects = new List<ActiveEffectHandle>();
if (!_effectInstances.TryAdd(forgeEntity, instanceEffects))
{
instanceEffects = _effectInstances[forgeEntity];
}
var effectOwnership = new EffectOwnership(effectOwner, effectSource);
foreach (EffectData effectData in _effects)
{
var key = new EffectKey(effectData, effectOwnership, level);
ActiveEffectHandle? handle;
if (_effectsCache.TryGetValue(key, out Effect? cachedEffect))
{
handle = forgeEntity.EffectsManager.ApplyEffect(cachedEffect);
if (handle is null)
{
continue;
}
instanceEffects.Add(handle);
continue;
}
var effect = new Effect(
effectData,
new EffectOwnership(effectOwner, effectSource),
level);
handle = forgeEntity.EffectsManager.ApplyEffect(effect, contextData);
if (handle is null)
{
continue;
}
instanceEffects.Add(handle);
}
}
private void RemoveEffects(IForgeEntity forgeEntity)
{
if (!_effectInstances.TryGetValue(forgeEntity, out List<ActiveEffectHandle>? value))
{
return;
}
foreach (ActiveEffectHandle handle in value)
{
forgeEntity.EffectsManager.RemoveEffect(handle);
}
_effectInstances[forgeEntity] = [];
}
}

View File

@@ -0,0 +1 @@
uid://bs52uo5esaiu2

View File

@@ -0,0 +1,14 @@
// Copyright © Gamesmiths Guild.
using Godot;
namespace Gamesmiths.Forge.Godot.Core;
public partial class ForgeBootstrap : Node
{
public override void _Ready()
{
ForgeData pluginData = ResourceLoader.Load<ForgeData>("uid://8j4xg16o3qnl");
_ = new ForgeManagers(pluginData);
}
}

View File

@@ -0,0 +1 @@
uid://ba8fquhtwu5mu

View File

@@ -0,0 +1,21 @@
// Copyright © Gamesmiths Guild.
using Gamesmiths.Forge.Core;
using Godot;
namespace Gamesmiths.Forge.Godot.Core;
public readonly struct ForgeCurve(Curve? curve) : ICurve
{
private readonly Curve? _curve = curve;
public float Evaluate(float value)
{
if (_curve is null)
{
return 1;
}
return _curve.Sample(value);
}
}

View File

@@ -0,0 +1 @@
uid://cei2cgvy84iy6

View File

@@ -0,0 +1,13 @@
// Copyright © Gamesmiths Guild.
using Godot;
using Godot.Collections;
namespace Gamesmiths.Forge.Godot.Core;
[Tool]
public partial class ForgeData : Resource
{
[Export]
public Array<string> RegisteredTags { get; set; } = [];
}

View File

@@ -0,0 +1 @@
uid://bq4vlbfx00hea

View File

@@ -0,0 +1,30 @@
// Copyright © Gamesmiths Guild.
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Cues;
using Gamesmiths.Forge.Tags;
namespace Gamesmiths.Forge.Godot.Core;
public class ForgeManagers
{
public static ForgeManagers Instance { get; private set; } = null!;
public TagsManager TagsManager { get; private set; }
public CuesManager CuesManager { get; private set; }
public ForgeManagers(ForgeData pluginData)
{
Instance = this;
#if DEBUG
Validation.Enabled = true;
#else
Validation.Enabled = false;
#endif
TagsManager = new TagsManager([.. pluginData.RegisteredTags]);
CuesManager = new CuesManager();
}
}

View File

@@ -0,0 +1 @@
uid://djeiinm1gclh4

View File

@@ -0,0 +1,98 @@
// Copyright © Gamesmiths Guild.
using System;
using Gamesmiths.Forge.Core;
using Godot;
namespace Gamesmiths.Forge.Godot.Core;
public class ForgeRandom : IRandom, IDisposable
{
private readonly RandomNumberGenerator _randomNumberGenerator;
public ForgeRandom()
{
_randomNumberGenerator = new RandomNumberGenerator();
_randomNumberGenerator.Randomize();
}
public void NextBytes(byte[] buffer)
{
for (var i = 0; i < buffer.Length; i++)
{
buffer[i] = (byte)_randomNumberGenerator.RandiRange(0, 255);
}
}
public void NextBytes(Span<byte> buffer)
{
for (var i = 0; i < buffer.Length; i++)
{
buffer[i] = (byte)_randomNumberGenerator.RandiRange(0, 255);
}
}
public double NextDouble()
{
return _randomNumberGenerator.Randf();
}
public int NextInt()
{
return (int)_randomNumberGenerator.Randi();
}
public int NextInt(int maxValue)
{
return _randomNumberGenerator.RandiRange(0, maxValue - 1);
}
public int NextInt(int minValue, int maxValue)
{
return _randomNumberGenerator.RandiRange(minValue, maxValue - 1);
}
public long NextInt64()
{
unchecked
{
var high = _randomNumberGenerator.Randi();
var low = _randomNumberGenerator.Randi();
return ((long)high << 32) | low;
}
}
public long NextInt64(long maxValue)
{
return NextInt64(0, maxValue);
}
public long NextInt64(long minValue, long maxValue)
{
if (minValue >= maxValue)
{
throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be less than maxValue.");
}
var range = (ulong)(maxValue - minValue);
var rand = (ulong)NextInt64();
return (long)(rand % range) + minValue;
}
public float NextSingle()
{
return _randomNumberGenerator.Randf();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
_randomNumberGenerator.Dispose();
}
}

View File

@@ -0,0 +1 @@
uid://dap6x2ddf6baj

View File

@@ -0,0 +1,7 @@
[gd_resource type="Resource" load_steps=2 format=3 uid="uid://8j4xg16o3qnl"]
[ext_resource type="Script" uid="uid://bq4vlbfx00hea" path="res://addons/forge/core/ForgeData.cs" id="1_x0pne"]
[resource]
script = ExtResource("1_x0pne")
RegisteredTags = Array[String](["effect.fire", "effect.wet", "cue.floating.text", "cue.vfx.fire", "cue.vfx.wet", "cue.vfx.regen", "cooldown.enemy.attack", "set_by_caller.damage", "event.damage", "cooldown", "cooldown.skill.projectile", "cooldown.skill.shield", "cooldown.skill.dash", "movement.block", "immunity.damage", "effect.mana_shield", "cue.vfx.shield", "event.damage.taken", "event.damage.dealt", "event", "set_by_caller", "trait.flammable", "trait.healable", "trait.damageable", "trait.wettable", "cue.vfx.reflect", "cue.vfx", "cooldown.skill", "cooldown.skill.reflect"])

View File

@@ -0,0 +1,210 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using System.Collections.Generic;
using System.Linq;
using Gamesmiths.Forge.Godot.Core;
using Gamesmiths.Forge.Godot.Resources;
using Gamesmiths.Forge.Tags;
using Godot;
using Godot.Collections;
namespace Gamesmiths.Forge.Godot.Editor;
[Tool]
public partial class AssetRepairTool : EditorPlugin
{
public static void RepairAllAssetsTags()
{
ForgeData pluginData = ResourceLoader.Load<ForgeData>("uid://8j4xg16o3qnl");
var tagsManager = new TagsManager([.. pluginData.RegisteredTags]);
List<string> scenes = GetScenePaths("res://");
GD.Print($"Found {scenes.Count} scene(s) to process.");
var openedScenes = EditorInterface.Singleton.GetOpenScenes();
foreach (var originalScenePath in scenes)
{
// For some weird reason scenes from the GetScenePath are coming with 3 slashes instead of just two.
var scenePath = originalScenePath.Replace("res:///", "res://");
GD.Print($"Processing scene: {scenePath}.");
PackedScene? packedScene = ResourceLoader.Load<PackedScene>(scenePath);
if (packedScene is null)
{
GD.PrintErr($"Failed to load scene: {scenePath}.");
continue;
}
Node sceneInstance = packedScene.Instantiate();
var modified = ProcessNode(sceneInstance, tagsManager);
if (!modified)
{
GD.Print($"No changes needed for {scenePath}.");
continue;
}
// 'sceneInstance' is the modified scene instance in memory, need to save to disk and reload if needed.
var newScene = new PackedScene();
Error error = newScene.Pack(sceneInstance);
if (error != Error.Ok)
{
GD.PrintErr($"Failed to pack scene: {error}.");
continue;
}
error = ResourceSaver.Save(newScene, scenePath);
if (error != Error.Ok)
{
GD.PrintErr($"Failed to save scene: {error}.");
continue;
}
if (openedScenes.Contains(scenePath))
{
GD.Print($"Scene was opened, reloading background scene: {scenePath}.");
EditorInterface.Singleton.ReloadSceneFromPath(scenePath);
}
}
}
/// <summary>
/// Recursively get scene files from a folder.
/// </summary>
/// <param name="basePath">Current path iteration.</param>
/// <returns>List of scenes found.</returns>
private static List<string> GetScenePaths(string basePath)
{
var scenePaths = new List<string>();
var dir = DirAccess.Open(basePath);
if (dir is null)
{
GD.PrintErr($"Failed to open directory: {basePath}");
return scenePaths;
}
// Start listing directory entries; skip navigational and hidden files.
dir.ListDirBegin();
while (true)
{
var fileName = dir.GetNext();
if (string.IsNullOrEmpty(fileName))
{
break;
}
var filePath = $"{basePath}/{fileName}";
if (dir.CurrentIsDir())
{
// Recursively scan subdirectories.
scenePaths.AddRange(GetScenePaths(filePath));
}
else if (fileName.EndsWith(".tscn", StringComparison.InvariantCultureIgnoreCase)
|| fileName.EndsWith(".scn", StringComparison.InvariantCultureIgnoreCase))
{
scenePaths.Add(filePath);
}
}
dir.ListDirEnd();
return scenePaths;
}
/// <summary>
/// Recursively process nodes; returns true if any ForgeEntity was modified.
/// </summary>
/// <param name="node">Current node iteration.</param>
/// <param name="tagsManager">The tags manager used to validate tags.</param>
/// <returns><see langword="true"/> if any ForgeEntity was modified.</returns>
private static bool ProcessNode(Node node, TagsManager tagsManager)
{
var modified = ValidateNode(node, tagsManager);
foreach (Node child in node.GetChildren())
{
modified |= ProcessNode(child, tagsManager);
}
return modified;
}
private static bool ValidateNode(Node node, TagsManager tagsManager)
{
var modified = false;
foreach (Dictionary propertyInfo in node.GetPropertyList())
{
if (!propertyInfo.TryGetValue("class_name", out Variant className))
{
continue;
}
if (className.AsString() != "TagContainer")
{
continue;
}
if (!propertyInfo.TryGetValue("name", out Variant nameObj))
{
continue;
}
var propertyName = nameObj.AsString();
Variant value = node.Get(propertyName);
if (value.VariantType != Variant.Type.Object)
{
continue;
}
if (value.As<Resource>() is ForgeTagContainer tagContainer)
{
modified |= ValidateTagContainerProperty(tagContainer, node.Name, tagsManager);
}
}
return modified;
}
private static bool ValidateTagContainerProperty(
ForgeTagContainer container,
string nodeName,
TagsManager tagsManager)
{
if (container.ContainerTags is null)
{
return false;
}
Array<string> originalTags = container.ContainerTags;
var newTags = new Array<string>();
var modified = false;
foreach (var tag in originalTags)
{
try
{
Tag.RequestTag(tagsManager, tag);
newTags.Add(tag);
}
catch (TagNotRegisteredException)
{
GD.PrintRich(
$"[color=LIGHT_STEEL_BLUE][RepairTool] Removing invalid tag [{tag}] from node {nodeName}.");
modified = true;
}
}
if (modified)
{
container.ContainerTags = newTags;
}
return modified;
}
}
#endif

View File

@@ -0,0 +1 @@
uid://1runivyr5don

View File

@@ -0,0 +1,62 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Gamesmiths.Forge.Attributes;
namespace Gamesmiths.Forge.Godot.Editor;
internal static class EditorUtils
{
/// <summary>
/// Uses reflection to gather all classes inheriting from AttributeSet and their fields of type Attribute.
/// </summary>
/// <returns>An array with the available attributes.</returns>
public static string[] GetAttributeSetOptions()
{
var options = new List<string>();
// Get all types in the current assembly
Type[] allTypes = Assembly.GetExecutingAssembly().GetTypes();
// Find all types that subclass AttributeSet
foreach (Type attributeSetType in allTypes.Where(x => x.IsSubclassOf(typeof(AttributeSet))))
{
options.Add(attributeSetType.Name);
}
return [.. options];
}
/// <summary>
/// Uses reflection to gather all classes inheriting from AttributeSet and their fields of type Attribute.
/// </summary>
/// <param name="attributeSet">The attribute set used to search for the attributes.</param>
/// <returns>An array with the available attributes.</returns>
public static string[] GetAttributeOptions(string? attributeSet)
{
if (string.IsNullOrEmpty(attributeSet))
{
return [];
}
var asm = Assembly.GetExecutingAssembly();
Type? type = Array.Find(
asm.GetTypes(),
x => x.IsSubclassOf(typeof(AttributeSet)) && x.Name == attributeSet);
if (type is null)
{
return [];
}
IEnumerable<PropertyInfo> properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof(EntityAttribute));
return [.. properties.Select(x => $"{x.Name}")];
}
}
#endif

View File

@@ -0,0 +1 @@
uid://dcvmf0r1f43m6

View File

@@ -0,0 +1,14 @@
[gd_scene format=3 uid="uid://pjscvogl6jak"]
[ext_resource type="PackedScene" uid="uid://c17f812by5x23" path="res://addons/forge/editor/tags/TagsEditor.tscn" id="1_bxwfw"]
[node name="Forge" type="PanelContainer" unique_id=249446352]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Tags" parent="." unique_id=654228508 instance=ExtResource("1_bxwfw")]
unique_name_in_owner = true
layout_mode = 2

View File

@@ -0,0 +1,34 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Attributes;
[Tool]
public partial class AttributeEditorPlugin : EditorInspectorPlugin
{
public override bool _CanHandle(GodotObject @object)
{
return @object is Resources.ForgeModifier || @object is Resources.ForgeCue;
}
public override bool _ParseProperty(
GodotObject @object,
Variant.Type type,
string name,
PropertyHint hintType,
string hintString,
PropertyUsageFlags usageFlags,
bool wide)
{
if (name == "Attribute" || name == "CapturedAttribute" || name == "MagnitudeAttribute")
{
AddPropertyEditor(name, new AttributeEditorProperty());
return true;
}
return false;
}
}
#endif

View File

@@ -0,0 +1 @@
uid://bl2w0vp6b5p8k

View File

@@ -0,0 +1,101 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Attributes;
[Tool]
public partial class AttributeEditorProperty : EditorProperty
{
private const int ButtonSize = 26;
private const int PopupSize = 300;
private Label _label = null!;
public override void _Ready()
{
Texture2D dropdownIcon = EditorInterface.Singleton
.GetEditorTheme()
.GetIcon("GuiDropdown", "EditorIcons");
var hbox = new HBoxContainer();
_label = new Label { Text = "None", SizeFlagsHorizontal = SizeFlags.ExpandFill };
var button = new Button { Icon = dropdownIcon, CustomMinimumSize = new Vector2(ButtonSize, 0) };
hbox.AddChild(_label);
hbox.AddChild(button);
AddChild(hbox);
var popup = new Popup { Size = new Vector2I(PopupSize, PopupSize) };
var tree = new Tree
{
HideRoot = true,
AnchorRight = 1,
AnchorBottom = 1,
};
popup.AddChild(tree);
var bg = new StyleBoxFlat
{
BgColor = EditorInterface.Singleton
.GetEditorTheme()
.GetColor("dark_color_2", "Editor"),
};
tree.AddThemeStyleboxOverride("panel", bg);
AddChild(popup);
BuildAttributeTree(tree);
button.Pressed += () =>
{
Window win = GetWindow();
popup.Position = (Vector2I)button.GlobalPosition
+ win.Position
- new Vector2I(PopupSize - ButtonSize, -30);
popup.Popup();
};
tree.ItemActivated += () =>
{
TreeItem item = tree.GetSelected();
if (item?.HasMeta("attribute_path") != true)
{
return;
}
var fullPath = item.GetMeta("attribute_path").AsString();
_label.Text = fullPath;
EmitChanged(GetEditedProperty(), fullPath);
popup.Hide();
};
}
public override void _UpdateProperty()
{
var value = GetEditedObject().Get(GetEditedProperty()).AsString();
_label.Text = string.IsNullOrEmpty(value) ? "None" : value;
}
private static void BuildAttributeTree(Tree tree)
{
TreeItem root = tree.CreateItem();
foreach (var attributeSet in EditorUtils.GetAttributeSetOptions())
{
TreeItem setItem = tree.CreateItem(root);
setItem.SetText(0, attributeSet);
setItem.Collapsed = true;
foreach (var attribute in EditorUtils.GetAttributeOptions(attributeSet))
{
TreeItem attributeItem = tree.CreateItem(setItem);
var attributePath = $"{attributeSet}.{attribute}";
attributeItem.SetText(0, attribute);
attributeItem.SetMeta("attribute_path", attributePath);
}
}
}
}
#endif

View File

@@ -0,0 +1 @@
uid://dvjqj637kfav

View File

@@ -0,0 +1,74 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using System.Linq;
using System.Reflection;
using Gamesmiths.Forge.Attributes;
using Gamesmiths.Forge.Godot.Nodes;
using Godot;
using Godot.Collections;
namespace Gamesmiths.Forge.Godot.Editor.Attributes;
[Tool]
public partial class AttributeSetClassEditorProperty : EditorProperty
{
private OptionButton _optionButton = null!;
public override void _Ready()
{
_optionButton = new OptionButton();
AddChild(_optionButton);
_optionButton.AddItem("Select AttributeSet Class");
foreach (var option in EditorUtils.GetAttributeSetOptions())
{
_optionButton.AddItem(option);
}
_optionButton.ItemSelected += x =>
{
var className = _optionButton.GetItemText((int)x);
EmitChanged(GetEditedProperty(), className);
GodotObject obj = GetEditedObject();
if (obj is not null)
{
var dict = new Dictionary<string, AttributeValues>();
var assembly = Assembly.GetAssembly(typeof(ForgeAttributeSet));
Type? targetType = System.Array.Find(assembly?.GetTypes() ?? [], x => x.Name == className);
if (targetType is not null)
{
System.Collections.Generic.IEnumerable<PropertyInfo> attrProps = targetType
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof(EntityAttribute));
foreach (PropertyInfo? pi in attrProps)
{
dict[pi.Name] = new AttributeValues(0, 0, int.MaxValue);
}
}
EmitChanged("InitialAttributeValues", dict);
}
};
}
public override void _UpdateProperty()
{
GodotObject obj = GetEditedObject();
StringName property = GetEditedProperty();
var val = obj.Get(property).AsString();
for (var i = 0; i < _optionButton.GetItemCount(); i++)
{
if (_optionButton.GetItemText(i) == val)
{
_optionButton.Selected = i;
break;
}
}
}
}
#endif

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