Compare commits

...

393 Commits

Author SHA1 Message Date
jonathan 50b2c04cb4 🐛 Fixed fighters keep fighting at 0 hp 2025-11-04 13:55:37 +01:00
Jonathan e864c62a3a Merge pull request 'Fight rework premature merge' (#22) from feature/fight_rework into develop
Reviewed-on: #22
2025-11-04 10:25:12 +01:00
jonathan f27f69c15f 🐛 Rebase fixing 2025-11-04 10:07:29 +01:00
jonathan 21b361f9d7 🎨 Live review changes 2025-11-04 10:07:19 +01:00
jonathan 83dc6bfd56 ♻️ Implemented FightersFormation to keep track of the fight entered state of the fighters 2025-11-04 05:59:19 +01:00
jonathan 0de3bcae22 ♻️Code cleanup 2025-11-04 05:59:19 +01:00
jonathan 3326bde40c 🐛Exiting fight happening when one side wins 2025-11-04 05:59:19 +01:00
jonathan 83c9dfc945 Added theme-based region colors 2025-11-04 05:59:19 +01:00
jonathan cd235248c6 Added minigame label for each region 2025-11-04 05:59:19 +01:00
jonathan 8bb1c22549 Added minigame to attack action 2025-11-04 05:59:18 +01:00
jonathan 8624e2aea8 Added minigame 2025-11-04 05:59:18 +01:00
jonathan fef1bcc3b6 cap health at minimum 0 2025-11-04 05:59:18 +01:00
jonathan f4d8ed87e2 Added blob attack action 2025-11-04 05:59:18 +01:00
jonathan 1f227f70a7 Added basic health bar 2025-11-04 05:59:18 +01:00
jonathan 9bf25640f6 Added basic action animation 2025-11-04 05:59:18 +01:00
jonathan 0e315396c9 Made fight fightable 2025-11-04 05:59:18 +01:00
jonathan f27dd199b8 Fight happening base setup 2025-11-04 05:27:34 +01:00
jonathan fd0e631b1f Bootstrap fight system
- Fight World data structure
- Generating basic fight world
- Opening correct fight room
- Block paths in fight rooms
- Transition between rooms
2025-11-04 05:25:22 +01:00
kziolkowski 759933c1cd Merge pull request 'feature/detection_cross' (#21) from feature/detection_cross into develop
Reviewed-on: #21
2025-10-31 10:47:20 +01:00
kziolkowski af8807610d :bug increased interaction area size for fence gates 2025-10-29 16:25:31 +01:00
kziolkowski 02b6584e44 🐛 fixed ObjectDisposedException from the Inventory when switching scenes. 2025-10-29 16:03:36 +01:00
kziolkowski e202534c6b 🐛 Vesnas Namensschild sieht nicht richtig aus FIXED 2025-10-29 15:00:30 +01:00
kziolkowski 1f13f3ea51 Merge branch 'develop' into feature/detection_cross
# Conflicts:
#	addons/dialogic/vesna_style.tres
#	dialog/NPC_narrative.tres
#	scenes/Babushka_scene_indoor_vesnas_room.tscn
2025-10-29 14:01:55 +01:00
kziolkowski 8b7afbcab6 Merge remote-tracking branch 'origin/feature/detection_cross' into feature/detection_cross 2025-10-29 13:32:57 +01:00
Jonathan afce4ad0b9 Merge pull request 'feature/events' (#19) from feature/events into develop
Reviewed-on: #19
2025-10-28 21:12:45 +01:00
kziolkowski 32ac007256 🎨 Exchanged Quest post it with ui sprite 2025-10-28 17:32:18 +01:00
kziolkowski d45deeb7ed 🔧 adjusted seed pickup positions and indoor collider sizes 2025-10-28 17:32:18 +01:00
kziolkowski 9e21b07271 🍱 Adjusted colliders in farm_outside scene and reactivated ground collider on Vesna 2025-10-28 17:32:18 +01:00
kziolkowski f94c2d0a50 :memo::fire:🔨
- removed a lot of unnecessary code
- Made a minimal working version
- Added documentation
2025-10-28 17:32:18 +01:00
kziolkowski 84b8938d1f 🚧 WIP first setup completed (still buggy!) 2025-10-28 17:32:18 +01:00
kziolkowski 8d421b58ec Implemented dialogue box design as described in mockups 2025-10-28 17:32:18 +01:00
kziolkowski ebe99c5f22 💄 Added new fonts and increased textbox size. Also added border line. 2025-10-28 17:32:18 +01:00
kziolkowski b32b483d09 upgraded project to 4.5 2025-10-28 17:32:18 +01:00
kziolkowski c6e9bc9336 scene updates after undoing changes to interactionArea 2025-10-28 17:32:17 +01:00
kziolkowski 5b0fed7816 sound autoimports 2025-10-28 17:31:10 +01:00
Nao 3bdd5aa873 SFX for Wolf added + redone reverb of footsteps (but not sure it worked?! - possible new upload/replacement possible) 2025-10-28 17:30:48 +01:00
kziolkowski 3fe79f2fd8 Added VariableGetter functionality and some Variable setting components. Also added them to test scene. 2025-10-28 13:09:36 +01:00
kziolkowski 8c1157c26e 🔥 removed indexed RaiseEvent-Call from EventRaiser 2025-10-28 12:04:41 +01:00
kziolkowski c96e6da78e Separated events from variables and adjusted test scene 2025-10-28 11:57:36 +01:00
kziolkowski 0fc1760b70 Merge pull request 'feature/animations_update' (#17) from feature/animations_update into develop
Reviewed-on: #17
2025-10-28 10:59:32 +01:00
kziolkowski 90b8dda16a Merge branch 'feature/dialogic_style_rework' into feature/detection_cross 2025-10-28 10:56:22 +01:00
kziolkowski ecff43d5c3 Merge branch 'develop' into feature/detection_cross 2025-10-28 10:46:02 +01:00
Jonathan 40b8c022fe Merge pull request 'feature/showcase_bugfixing_kathi_partII' (#16) from feature/showcase_bugfixing_kathi_partII into develop
Reviewed-on: #16
2025-10-24 18:31:55 +02:00
kziolkowski e1b1a2f447 🔧 adjusted seed pickup positions and indoor collider sizes 2025-10-24 15:57:14 +02:00
kziolkowski 2d4e853ec8 🍱 Adjusted colliders in farm_outside scene and reactivated ground collider on Vesna 2025-10-24 15:45:10 +02:00
kziolkowski 8f097de476 :memo::fire:🔨
- removed a lot of unnecessary code
- Made a minimal working version
- Added documentation
2025-10-24 15:13:35 +02:00
kziolkowski 109573fb1b 🎨 Exchanged Quest post it with ui sprite 2025-10-24 13:43:13 +02:00
kziolkowski 0e55394699 🚧 WIP first setup completed (still buggy!) 2025-10-22 19:30:33 +02:00
kziolkowski b1335d4b00 Merge branch 'feature/dialogic_style_rework' into feature/detection_cross 2025-10-22 14:46:55 +02:00
kziolkowski 078e26e868 Implemented dialogue box design as described in mockups 2025-10-22 14:23:48 +02:00
kziolkowski ffebe10593 💄 Added new fonts and increased textbox size. Also added border line. 2025-10-21 13:41:43 +02:00
kziolkowski 4c8406ba97 upgraded project to 4.5 2025-10-21 12:59:29 +02:00
kziolkowski 080ebaae47 removed redundant import statement 2025-10-21 12:50:48 +02:00
kziolkowski 5cf250b295 Added randomizer class for certain types of EventResources 2025-10-21 12:39:17 +02:00
kziolkowski ef56f79d5c Added event payload and Valuechange-handling 2025-10-21 12:39:01 +02:00
kziolkowski 5bcbee8865 created test scene and removed event test elements from start scene 2025-10-21 12:38:15 +02:00
kziolkowski a74abe684b Godot upgrade to 4.5 2025-10-21 12:33:38 +02:00
kziolkowski a593be8273 📝 added documentation and improved event scope 2025-10-18 17:15:35 +02:00
kziolkowski 1e004b62b8 Implemented first version of EventResources 2025-10-13 09:57:49 +02:00
kziolkowski 5fc7b3d563 scene updates after undoing changes to interactionArea 2025-10-03 17:02:07 +02:00
kziolkowski d4892e1aa4 sound autoimports 2025-10-03 16:52:17 +02:00
kziolkowski e0ea5ef5be Merge branch 'feature/sounds_update' into feature/bugfixing_kathi_partIII 2025-10-03 16:45:30 +02:00
kziolkowski 02ff401273 Merge branch 'feature/animations_update' into feature/bugfixing_kathi_partIII 2025-10-03 16:45:14 +02:00
kziolkowski c3e78b5db2 Merge remote-tracking branch 'origin/Nao's-Sounds' into feature/sounds_update 2025-10-03 16:39:30 +02:00
kziolkowski 09b2baab0a Merge remote-tracking branch 'origin/Animations' into feature/animations_update
# Conflicts:
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B01-Idle/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B02-Walk/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B03-Interact/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B03-Interact/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B03-Interact/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B03-Interact/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B03-Interact/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B03-Interact/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B03-Interact/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B03-Interact/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B03-Interact/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/B03-Interact/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D01-Idle/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D03-Farming/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D03-Farming/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D03-Farming/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D03-Farming/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D03-Farming/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D03-Farming/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D03-Farming/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D03-Farming/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D03-Farming/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D03-Farming/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D05-Talk/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D06-Harvesting/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D06-Harvesting/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D06-Harvesting/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D06-Harvesting/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D06-Harvesting/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D06-Harvesting/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D06-Harvesting/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D06-Harvesting/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D06-Harvesting/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D06-Harvesting/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0021.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0022.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0023.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0024.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0025.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D08-Watering/0026.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D10-Watercan/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/D11-Item/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F01-Idle/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/F02-Walk/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S01-Idle/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S02-Walk/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Sequences/S03-PickUp/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0020.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0001.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0002.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0003.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0004.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0005.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0006.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0007.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0008.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0009.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0010.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0011.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0012.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0013.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0014.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0015.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0016.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0017.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0018.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0019.png.import
#	art/animation/Yeli2D/F01-Yeli_Idle/0020.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0001.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0002.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0003.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0004.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0005.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0006.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0007.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0008.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0009.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0010.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0011.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0012.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0013.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0014.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0015.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0016.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0017.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0018.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0019.png.import
#	art/animation/Yeli2D/F02-Yeli_Talk/0020.png.import
#	project.godot
2025-10-03 16:20:23 +02:00
kziolkowski d6a2b586e4 🐛 fixed ducks counting bug 2025-09-27 17:38:23 +02:00
kziolkowski 936983e14f 💄 updated dialogic style to make it clearer who is talking and have vesna always on the left side 2025-09-27 17:24:01 +02:00
kziolkowski e73f13d7d4 🎨 💄 Created art template for farm plants and reworked existing plant prefabs and inventory resources 2025-09-27 16:54:17 +02:00
kziolkowski 0713e334b3 Fixed InventoryInstance behaviour 2025-09-26 00:31:11 +02:00
kziolkowski c56f654751 Set up second garden with beetroot, built transition from yard and back 2025-09-17 17:58:28 +02:00
kziolkowski 1f88f9b4b5 Added beetroot as growable plant 2025-09-17 16:53:57 +02:00
kziolkowski 06fd80f762 grass layering fix 2025-09-17 15:10:29 +02:00
kziolkowski 2cb605261e Fixed Tomato farming (again) 2025-09-17 15:07:17 +02:00
kziolkowski 975dd45c94 fixed item repository bug 2025-09-10 15:06:49 +02:00
kziolkowski d1a8ff0cbf WIP reworking the item repository 2025-09-09 23:57:55 +02:00
Nao cba6c09ca0 SFX for Wolf added + redone reverb of footsteps (but not sure it worked?! - possible new upload/replacement possible) 2025-09-08 16:51:21 +02:00
Felix Paul da7cd2f6f2 Small update 2025-09-01 19:52:11 +02:00
kziolkowski 9f50e1d293 WIP trying to implement pickup sounds 2025-08-27 18:21:28 +02:00
kziolkowski 652cac4232 Added typing sounds to dialogic characters 2025-08-27 18:00:26 +02:00
kziolkowski e44e06bace Added Footsteps SFX 2025-08-27 17:12:12 +02:00
kziolkowski 89313fd484 Merge remote-tracking branch 'origin/Nao's-Sounds' into feature/showcase_bugfixing_kathi_partII 2025-08-27 15:19:47 +02:00
Nao 6f340b54a4 SFX Footstep Gravel Added 2025-08-25 19:15:38 +02:00
Nao 8f3be823a4 Added Single Footstep SFX, added folders and moved files for better organization 2025-08-21 17:46:53 +02:00
kziolkowski eeb56fd7ad Added possibility to deactivate an interactionarea if there is no more timelines to play 2025-08-20 00:48:12 +02:00
kziolkowski 871e1856f1 Tools get deactivated from animation when switching to an empty slot in the inventory. Also fixed layering (AGAIN) 2025-08-20 00:26:58 +02:00
kziolkowski 378bf45c49 Implemented PR feedback + made sure initial farming quests run smoothly 2025-08-19 23:02:14 +02:00
kziolkowski f6e0c4e615 minor scene fixes 2025-08-18 00:10:20 +02:00
kziolkowski 6ae877f2ab Implemented first version of an interactable area that reacts to inventory selection 2025-08-17 23:49:04 +02:00
kziolkowski 3913143892 Added Hotkey-Info to inventory prefab 2025-08-17 23:21:45 +02:00
kziolkowski 4734ae953c Added option to play pickup animation for non-inventory items 2025-08-17 23:13:39 +02:00
kziolkowski 5992c390ee Reinstated plantgrowing animation 2025-08-17 23:07:22 +02:00
kziolkowski 283caf282b Added interaction on mouse click 2025-08-17 22:50:28 +02:00
kziolkowski 7c03964d33 changed watering can display to slider 2025-08-17 22:42:12 +02:00
kziolkowski 36c948f116 re-added watering animation and vfx 2025-08-17 22:18:39 +02:00
kziolkowski d0ba4076b3 planting, growing and watering a little less dependent than before 2025-08-17 22:11:18 +02:00
kziolkowski 7fd26ffed3 Fixed tomato farming again 2025-08-17 21:57:57 +02:00
kziolkowski ce2d7eb773 WIP moving seed to plant matching into separate system, making fields independent of specific plants. 2025-08-15 18:42:26 +02:00
kziolkowski 41365fb5d4 Fixed tool animation 2025-08-15 17:19:22 +02:00
kziolkowski 96c7d35aa7 Added word wrapping to quest post it. 2025-08-15 17:19:13 +02:00
kziolkowski c2f5359d0c changes after develop merge 2025-08-15 17:03:18 +02:00
Jonathan 9bebe1a44d Merge pull request 'Implemented first demo quest line' (#15) from feature/implement_quests into develop
Reviewed-on: #15
2025-08-14 23:28:09 +02:00
jonathan 06a270e916 Renamed function to not sound like a unity signal 2025-08-14 23:27:15 +02:00
jonathan b621df5435 removed invisible character 2025-08-14 23:16:52 +02:00
jonathan 03dbc08293 quest status name refactoring 2025-08-14 21:54:18 +02:00
jonathan 8676bbb2f6 Fix spelling 2025-08-13 17:13:33 +02:00
jonathan 6e998810b6 Completed first demo quest line 2025-08-13 03:23:45 +02:00
jonathan c96be7467e Added Quest field 2025-08-11 19:15:12 +02:00
Jonathan 6aa7530502 Added speed hack for vesna 2025-08-06 21:25:28 +02:00
Jonathan d7ac1c6c22 Fixed retrigger yeli dialog 2025-08-06 20:04:24 +02:00
Jonathan 0170a53b5a Added more quest stuff including dialogic quest condition 2025-08-06 18:20:38 +02:00
cblech abc33fd06c Added first quests 2025-08-06 18:19:52 +02:00
cblech 2fbeb93018 Dialogic quest addition and plugin fix 2025-08-06 18:19:08 +02:00
Jonathan 9253a78a06 Merge pull request 'feature/showcase_bugfixing_kathi' (#14) from feature/showcase_bugfixing_kathi into develop
Reviewed-on: #14
2025-08-06 18:16:53 +02:00
kziolkowski 0008b16d48 Adjusted moving values and animations to make vesna not slide any more 2025-08-02 15:32:13 +02:00
kziolkowski 8e0dced918 Vesna can walk diagonally now 2025-08-02 15:08:38 +02:00
kziolkowski 2e21fb7e98 Made sure splash screen features logo 2025-08-02 13:55:11 +02:00
kziolkowski b69191e7f7 fixed pickup layering 2025-08-02 13:46:01 +02:00
kziolkowski 8f75b5b644 Added secondary outline mat and assigned it to inventory items 2025-08-02 13:38:07 +02:00
kziolkowski 6275bea52c MAde Vesna and Yeli disappear on dialogic start 2025-07-31 23:30:12 +02:00
kziolkowski 279cac22ee Fixed dialogic settings 2025-07-31 23:25:43 +02:00
kziolkowski e88ba2e1ef Updated interaction area setups to avoid nullrefs 2025-07-31 23:08:10 +02:00
kziolkowski f01143e887 cleaned up dialogue structure and removed vesna2 from dialogues 2025-07-31 22:09:37 +02:00
kziolkowski 18711776bb Fixed output error ERROR: Script class can only be set together with base class name 2025-07-31 21:45:22 +02:00
kziolkowski 0f246825fc Added Wellbehaviour and fixed door interaction to offer outlines 2025-07-31 20:36:17 +02:00
kziolkowski 19e30dd0b8 Removed disclaimer reference from bootstrap 2025-07-31 20:35:49 +02:00
kziolkowski 730c4999d7 Extracted FightAttack Enum in the hopes of fixing errors 2025-07-31 20:35:24 +02:00
kziolkowski cfe604d3b7 set duck body scale to 1,1 2025-07-31 18:55:06 +02:00
kziolkowski ad16b86171 wip exchanging interaction label with outline 2025-07-27 13:17:34 +02:00
kziolkowski 5ee295256b Fixed Yeli Layering issue in indoor scene 2025-07-26 17:05:17 +02:00
kziolkowski ab23d41496 Fixed ysorting on ducks and trash 2025-07-26 16:51:05 +02:00
Felix Paul 12842a37ff Rigged Chuga 2025-07-25 20:19:39 +02:00
Jonathan ae0944fe00 Merge pull request 'feature/showcase_kathi' (#12) from feature/showcase_kathi into develop
Reviewed-on: #12
2025-07-23 12:37:47 +02:00
kziolkowski 486cfb5546 WIP trying to fix the last plant stage bug on repeated farming rounds 2025-07-17 22:10:08 +02:00
kziolkowski b92eb909ad Removed no longer needed print statements 2025-07-17 20:47:34 +02:00
kziolkowski d2c7302ab2 Fixed farming (the way it was before) 2025-07-17 20:44:28 +02:00
kziolkowski 5affc48dc5 Added scene transitions between fighting scene, credits, and start 2025-07-11 04:12:42 +02:00
kziolkowski cdb6b83124 Merge remote-tracking branch 'origin/feature/fight_system' into feature/showcase_kathi 2025-07-11 03:55:56 +02:00
kziolkowski a4f9511892 WIP fixing farming mechanic again 2025-07-11 03:55:36 +02:00
cblech cc7e8c5c4b Added Credits 2025-07-11 03:49:09 +02:00
kziolkowski 7a3e96d679 Merge remote-tracking branch 'origin/feature/fight_system' into feature/showcase_kathi 2025-07-11 03:13:08 +02:00
kziolkowski e38c391e1c Reworked flow to make it playable somehow 2025-07-11 03:12:29 +02:00
cblech c0e002f783 made you win waiting time shorter 2025-07-11 03:01:49 +02:00
cblech e8850ca7c5 Added chuga dialog to fight scene 2025-07-11 03:00:47 +02:00
kziolkowski eaedf8c396 WIP Beetroot 2025-07-11 03:00:43 +02:00
kziolkowski ffa20bbdf8 Added trash to first outdoor scene 2025-07-11 00:56:51 +02:00
kziolkowski b77b6e3a52 Added second outdoor scene after tutorial 2025-07-11 00:17:31 +02:00
cblech 69cb63ad89 merge auto fixes 2025-07-11 00:15:28 +02:00
cblech 11790434f5 Merge remote-tracking branch 'origin/feature/showcase_kathi' into feature/fight_system 2025-07-11 00:11:33 +02:00
kziolkowski c7d56301fc Added counter script to count ducks (and other things) 2025-07-10 23:58:33 +02:00
cblech 8bd2b09232 Added healing 2025-07-10 23:32:16 +02:00
kziolkowski 480d149ede SceneTransition Rework completed 2025-07-10 23:23:20 +02:00
cblech c6fc400994 Added second fight with mavkha 2025-07-10 19:30:57 +02:00
kziolkowski 16251db248 WIP new SceneTransition system 2025-07-10 19:07:46 +02:00
cblech 4752002caf Added action per round 2025-07-10 18:29:18 +02:00
cblech 0e028a2cb9 Renamed signal 2025-07-10 16:12:47 +02:00
cblech 01a281c2aa Fixed camera bounds 2025-07-10 16:04:12 +02:00
cblech 2a46d363e9 Changed music to forrest 2025-07-10 15:43:36 +02:00
cblech 92c3f5e054 Target only alive enemies 2025-07-10 15:25:11 +02:00
cblech 6727339e9a Added audio to fight 2025-07-10 04:39:33 +02:00
cblech e3d67722de Merge remote-tracking branch 'origin/Nao's-Sounds' into feature/fight_system 2025-07-10 03:47:40 +02:00
cblech b6fd6292e3 Basic fighting system 2025-07-10 03:38:48 +02:00
kziolkowski e8c24e18be Added indoor bedroom scene 2025-07-10 01:25:52 +02:00
kziolkowski 199d0873c8 Set up beetroot quest and added cat to indoor scene. 2025-07-10 01:19:00 +02:00
kziolkowski e84ef716f6 Merge remote-tracking branch 'origin/feature_sanel-dialogic' into feature/showcase_kathi
# Conflicts:
#	project.godot
2025-07-09 23:53:40 +02:00
kziolkowski 135ba0d198 Reworked Domovoi kitchen animation and added AnimationStarter script 2025-07-09 23:50:43 +02:00
Sanelschnitte 2940475062 Added dialog in Dialogic
basic char, text, choice - eventually needs more events and functions to work properly
2025-07-09 23:43:02 +02:00
kziolkowski 202f8809aa Fixed plant layering issue 2025-07-09 23:03:56 +02:00
kziolkowski 28bb81c0b0 Added fence gates and fixed dialogic toggle for inventory 2025-07-09 22:58:36 +02:00
kziolkowski b9c5de6dc3 Made labels visible on ducks 2025-07-09 22:49:07 +02:00
kziolkowski 8c18688a69 MVP duck setup 2025-07-09 22:40:03 +02:00
kziolkowski 2429958741 Added Disclaimer scene 2025-07-09 21:40:02 +02:00
kziolkowski 6b5a8ee126 WIP duck behaviour 2025-07-09 20:52:43 +02:00
kziolkowski 8d40529349 Changed StartScreen layout and added Quit button 2025-07-09 20:52:33 +02:00
kziolkowski 9e97cc3b80 WIP duck pushing behaviour 2025-07-08 23:53:50 +02:00
kziolkowski ebc3ee9cf9 Implemented basic duck running away from vesna mechanic 2025-07-08 22:23:28 +02:00
kziolkowski a1bbe44105 Merge remote-tracking branch 'origin/Nao's-Sounds' into feature/showcase_kathi 2025-07-08 21:33:59 +02:00
kziolkowski 3ce10c8ba9 Merge remote-tracking branch 'origin/Nao's-Sounds' into feature/showcase_kathi 2025-07-08 21:33:37 +02:00
kziolkowski 7e6163ed68 updated project.godot 2025-07-07 21:16:36 +02:00
kziolkowski 55d6222698 Added Camera to Indoor Common Room scene and renamend scene asset 2025-07-07 16:37:10 +02:00
kziolkowski d7ad2ba072 reverted / recreated inventory changes 2025-07-07 16:14:07 +02:00
kziolkowski 9c50a63dc3 Merge branch 'develop' into feature/showcase_kathi 2025-07-07 15:34:53 +02:00
kziolkowski 746ac58329 Merge pull request 'Feature: Quest system' (#11) from feature/quest_system into develop
Reviewed-on: #11
2025-07-07 15:24:28 +02:00
cblech a9cb20c8bc Fixed journal references 2025-07-07 04:51:35 +02:00
cblech 32249fde86 Made quest visuals 2025-07-07 04:41:14 +02:00
cblech 27e137bc02 Made quest usable 2025-07-07 04:41:03 +02:00
cblech 01daddee3b Made basic quest setup 2025-07-07 04:40:40 +02:00
Jonathan ba512508f8 Merge pull request 'feature/farm_scene_rework' (#10) from feature/farm_scene_rework into develop
Reviewed-on: #10
2025-07-07 03:46:51 +02:00
kziolkowski ec653aa847 readded camera ref to farming behaviour 2025-07-07 01:17:41 +02:00
kziolkowski 9acea54f28 Added duck sprite 2025-07-07 00:46:57 +02:00
kziolkowski 2f5296e05a Diversified field shapes 2025-07-06 23:48:01 +02:00
kziolkowski e8a58655ad Added grass textures and field borders 2025-07-06 23:28:02 +02:00
kziolkowski aa3789566c Duplicated sunflower field parallaxe layer to ensure seamlessness on big screens 2025-07-06 22:00:39 +02:00
kziolkowski 9c7ae20c18 Reworked Camera controls outside scene 2025-07-06 21:56:15 +02:00
Felix Paul ffc7ef2d61 Added Vesna and Yeli as 2D rigged chars, Chuga is still WIP 2025-07-06 20:22:12 +02:00
Nao 9f04c8819f SFX for Mavkas (Voices but more "ghostly") Added 2025-07-06 20:20:03 +02:00
Nao 6cd7be196b SFX Wolf + Mavka Voices (when hit in battle) Added 2025-07-06 20:11:50 +02:00
kziolkowski ab2cf3f497 wateringcanstate uid? 2025-07-06 12:26:07 +02:00
kziolkowski bf096aa716 visual improvements on farm scene 2025-07-06 12:25:44 +02:00
kziolkowski 09ef508f49 Fixed field creation bug 2025-07-06 10:57:38 +02:00
Nao 6de8368414 Farm Atmos Added 2025-07-05 23:23:39 +02:00
Nao d20085a67c SFX Water Attack Added (Vesna + Slimes) 2025-07-05 23:17:58 +02:00
kziolkowski a54003c658 Made ground noise texture more seamless 2025-07-05 23:10:23 +02:00
Nao 648ae8395b SFX added for Root Attack, Mavka + Vesna 2025-07-05 23:01:43 +02:00
kziolkowski fbad33213c Finalized new watering can mechanic with vfx and ui 2025-07-05 22:54:52 +02:00
Nao b68eb0953e SFX Duck Flügelflattern "Wings" + Schlappentornado Added 2025-07-05 21:51:19 +02:00
kziolkowski 35c7e9a25e WIP Wasserstand UI 2025-07-05 17:34:03 +02:00
kziolkowski 57896e37df Fixed background music loop and added audio mixers 2025-07-05 15:08:23 +02:00
kziolkowski 242879159a Fixed sfx binding in farming scene 2025-07-05 13:57:14 +02:00
kziolkowski 7ef9fe53b2 Added SignalVisualizer plugin 2025-07-05 13:56:59 +02:00
kziolkowski 600d9cfca1 Made items activate directly when picked up 2025-07-05 13:18:19 +02:00
kziolkowski 48796de1ba Changed dialogic background color to black and opacity to 168 sothat there is a visible dark overlay during dialogues 2025-07-05 12:37:39 +02:00
kziolkowski 4a0b2cd550 Improved Inventory UI (more in the middle, adapt to viewport size, new selector image) 2025-07-05 12:18:28 +02:00
kziolkowski ba429d7b84 wip improving inventory layout 2025-07-04 23:41:15 +02:00
kziolkowski e33776daeb ESC closes inventory now. 2025-07-04 23:15:45 +02:00
kziolkowski 9cfd3bd6f4 Improving farm outside scene performance 2025-07-02 21:36:12 +02:00
Nao 7eb18caaaf Added SFX for Healing magic 2025-07-02 21:24:21 +02:00
Nao e65e82f421 Added SFX Duck & Combat, moved Cat SFX to folder "animals" 2025-07-02 21:23:36 +02:00
kziolkowski c36d0b5866 reduced VRAM load by more than 50% 2025-07-02 17:54:23 +02:00
kziolkowski bcdba7b812 Fixed Vesna sliding issue 2025-07-02 15:48:39 +02:00
kziolkowski 7467ec56d9 Fixed ordering issues in indoor scene 2025-06-28 17:37:39 +02:00
kziolkowski 990494e8b0 Adjusted indoor speed 2025-06-28 17:19:41 +02:00
kziolkowski c6e0cfe4d9 Fixed Button layout behaviour 2025-06-28 17:17:26 +02:00
Nao 691e613a5c Added SFX for Combat 2025-06-28 17:04:20 +02:00
kziolkowski 4e010220bf Improved performance of the first indoor scene 2025-06-28 16:13:06 +02:00
kziolkowski ccc4eb759e Added new house image to farming scene 2025-06-21 16:09:35 +02:00
Nao 250f0d07ca SFX Slime Hit Variations (Pitch) Added 2025-06-17 15:43:33 +02:00
Nao f481f5bea7 SFX Added: UI (Dialog + Confirmation), Slime Hit 2025-06-17 15:39:02 +02:00
Jonathan 58c65fc928 Merge pull request 'First intro sequence with Yeli' (#9) from feature/yeli_intro into develop
Reviewed-on: #9
2025-06-17 02:40:12 +02:00
cblech 937e54e47a Merge branch 'feature/farming_visuals' into develop 2025-06-16 21:03:02 +02:00
kziolkowski 83c8a15e5a Added art for updated startscreen to project (but not yet to start menu scene) 2025-06-16 20:56:01 +02:00
kziolkowski 1fcddb7349 changed export presets for new builds 2025-06-16 20:56:01 +02:00
kziolkowski 26cae9b0f8 fixed missing ready plants 2025-06-16 20:56:01 +02:00
kziolkowski 0ebd0c46a0 Added indoor ending 2025-06-16 20:56:01 +02:00
kziolkowski 2226b859df Added WateringCan SFX 2025-06-16 20:56:00 +02:00
kziolkowski 07bcab4454 Added Farming SFX 2025-06-16 20:56:00 +02:00
kziolkowski 4c6d5350b2 Added Background music 2025-06-16 20:56:00 +02:00
kziolkowski f2c492de54 changed label settings for InteractionArea 2025-06-16 20:56:00 +02:00
kziolkowski e269b3ab44 WIP trying to toggle interactable colliders at runtime for fields 2025-06-16 20:56:00 +02:00
kziolkowski 89ae347a91 Added last farming timelines and fixed some minor issues 2025-06-16 20:56:00 +02:00
kziolkowski ddb5547b42 Updated Startscreen and added second tutorial step 2025-06-16 20:56:00 +02:00
kziolkowski afa06d3ee2 Fixed Yeli Y sorting issue and made fields rounded 2025-06-16 20:56:00 +02:00
kziolkowski dd656d5f5f Extended dialogic toggle 2025-06-16 20:56:00 +02:00
kziolkowski cdb62f3c2f Added Cozy Raven Splashscreen 2025-06-16 20:56:00 +02:00
kziolkowski b0f4592fdd Added first working timeline for yelis intro to farming scene 2025-06-16 20:56:00 +02:00
kziolkowski 406c11b581 import statements 2025-06-16 20:56:00 +02:00
kziolkowski 9812006234 Added Yeli sprites and dialogic test timeline as well as prefab 2025-06-16 20:56:00 +02:00
kziolkowski b8e456f7ef moved player2D prefab 2025-06-16 20:55:59 +02:00
Felix Paul 97e5485ef1 Added Yeli Idle and Talk animations 2025-06-16 20:55:59 +02:00
Felix Paul c77563d38e Rerender Harvesting and added Watering animations 2025-06-16 20:55:59 +02:00
Nao c322f7667a Placeholder for Quest Fail added 2025-06-16 20:55:59 +02:00
Nao 68be479a09 New SFX for Door added 2025-06-16 20:55:58 +02:00
Nao dc52d80a92 SFX Quest Complete Placeholder added (need more time) 2025-06-16 20:55:58 +02:00
Nao a84ca9c32c Farm Atmos Nighttime added 2025-06-16 20:55:58 +02:00
Nao 6b1dd04dd5 New SFX and Forest Atmos Nighttime added 2025-06-16 20:55:58 +02:00
kziolkowski 523624756e missing import files 2025-06-16 20:55:57 +02:00
Nao 72593ff7c7 SFX for kitchen added (cutlery, plates, etc) and a few renamed 2025-06-16 20:55:57 +02:00
kziolkowski d7dfd03241 Deleted unused material folder 2025-06-16 20:55:57 +02:00
Nao eac135508c SFX Snoring Added 2025-06-16 20:55:57 +02:00
ascheck 90cadbea07 ui elemente hinzugefügt für dialog 2025-06-16 20:55:57 +02:00
Nao 72c3454728 SFX Door Open and Close added 2025-06-16 20:55:57 +02:00
kziolkowski 83e25f44cd Fixed background parallaxe effect in outdoor scene 2025-06-16 20:55:57 +02:00
kziolkowski 31f19ad139 Code cleanup part 2 2025-06-16 20:55:57 +02:00
kziolkowski c6ec99e87a Project cleanup: Removed 3D scripts and scenes from project. Also renamed and moved some minor stuff. 2025-06-16 20:55:57 +02:00
Nao b8cb60aec9 Placeholder for Quest Fail added 2025-06-02 16:06:52 +02:00
Nao 55a3e56791 New SFX for Door added 2025-06-02 15:56:36 +02:00
kziolkowski 1f17cca62d changes from build process 2025-05-28 15:37:04 +02:00
ascheck 1629862759 Merge pull request 'ui elemente hinzugefügt für dialog' (#4) from feature/ui-elements into develop
Reviewed-on: #4
2025-05-27 21:23:34 +02:00
ascheck a53b4c4010 ui elemente hinzugefügt für dialog 2025-05-27 21:11:15 +02:00
kziolkowski 3e135afecb Added StartScreen and Scene Transition script 2025-05-24 23:50:06 +02:00
kziolkowski 25d8f217ff basic harvesting implemented 2025-05-24 23:16:41 +02:00
kziolkowski a477f9ef4f Implemented field creation and wateringcan fill up animation 2025-05-24 21:47:25 +02:00
kziolkowski b917fb5fbd changed player window size 2025-05-24 21:46:28 +02:00
Felix Paul 20ed134628 Added Yeli Idle and Talk animations 2025-05-21 02:12:36 +02:00
kziolkowski 47a1326292 state after magic clip done 2025-05-21 02:10:50 +02:00
kziolkowski 708aa8cca4 State after inventory clip (farming mechanic still WIP) 2025-05-21 00:13:58 +02:00
kziolkowski 83f4144b43 Reworked farming tools pickup animations 2025-05-20 23:05:26 +02:00
kziolkowski 2c677a8cda Inventory resource rework and slot aspect fix 2025-05-20 22:27:48 +02:00
cblech ec5d42068e fixed generic item on ground and added icon 2025-05-20 20:42:32 +02:00
cblech 8fdb395ae8 Added icon to slot 2025-05-20 19:49:37 +02:00
kziolkowski 60928c520c Inventory implemented 2025-05-20 18:27:50 +02:00
kziolkowski f3ed5e0ad0 Changes to ItemOnGround 2025-05-20 18:27:40 +02:00
kziolkowski ee897db52b added swaying plants shader 2025-05-20 18:27:20 +02:00
kziolkowski 41201d7406 Indoor room sprites added 2025-05-20 18:27:09 +02:00
kziolkowski 267172d026 WIP adding the inventory, also state after first clip 2025-05-20 01:00:13 +02:00
kziolkowski 917e6a5f7e Merge remote-tracking branch 'origin/inventory' into feature/farming_visuals
# Conflicts:
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B01-Idle-Harke/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/B02-Walk-Harke/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/D01-Idle-Harke/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F01-Idle-Harke/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/F02-Walk-Harke/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S01-Idle-Harke/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Gießkanne/0020.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0001.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0002.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0003.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0004.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0005.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0006.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0007.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0008.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0009.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0010.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0011.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0012.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0013.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0014.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0015.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0016.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0017.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0018.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0019.png.import
#	art/animation/Vesna2D/Vesna Anims Tools/S02-Walk-Harke/0020.png.import
#	prefabs/UI/Inventory/Inventory.tscn
#	scenes/Babushka_scene_farm_outside_2d.tscn
2025-05-20 00:19:42 +02:00
kziolkowski 23ef4e9a63 changed visibility order for plants 2025-05-20 00:18:49 +02:00
kziolkowski fa7cc66748 added farming animation mechanic 2025-05-19 23:49:00 +02:00
cblech 7b006d19ef Changed slot visuals 2025-05-19 23:36:45 +02:00
kziolkowski ad070514d7 Added item and interact animations 2025-05-19 22:50:28 +02:00
kziolkowski 80c48ea3df added pickup animation 2025-05-19 22:39:30 +02:00
cblech d76218f88f Moved Bar slot selection handling to inventory manager 2025-05-19 22:07:55 +02:00
kziolkowski 7276bdda38 Watering can animation plays when watering 2025-05-19 22:06:40 +02:00
kziolkowski 73dc3063c0 removed unnecessary icons from farming controls 2025-05-19 21:42:56 +02:00
cblech ae4f7d9c58 added missing imports 2025-05-19 21:36:10 +02:00
cblech 9a5caef52f Added inventory background and fixes 2025-05-19 21:35:55 +02:00
kziolkowski 15c2a84ecd Added babushka icon to project settings 2025-05-19 21:18:24 +02:00
kziolkowski 1094fb2142 Added tool animations to player 2025-05-19 21:17:50 +02:00
cblech 2ab1c38de9 cleaned Inventory ui 2025-05-19 17:17:01 +02:00
Felix Paul 3eb35d4e7d Rerender Harvesting and added Watering animations 2025-05-18 21:22:27 +02:00
Nao 4261789f13 SFX Quest Complete Placeholder added (need more time) 2025-05-18 20:34:00 +02:00
Nao ce18c89c09 Farm Atmos Nighttime added 2025-05-18 19:21:51 +02:00
Nao 7643cccebf New SFX and Forest Atmos Nighttime added 2025-05-18 19:04:02 +02:00
Nao 7e4d733d7c SFX for kitchen added (cutlery, plates, etc) and a few renamed 2025-05-18 17:52:59 +02:00
Nao 1dcd900737 SFX Snoring Added 2025-05-18 15:33:02 +02:00
Nao 74c3540c96 SFX Door Open and Close added 2025-05-18 15:10:08 +02:00
kziolkowski 6d80253d9c import updates to shaders and animations 2025-05-18 13:05:35 +02:00
kziolkowski 1b7c35879c Merge remote-tracking branch 'origin/Animations' into farming_mechanic 2025-05-18 12:34:21 +02:00
kziolkowski ff426b0494 Initial Farming setup complete 2025-05-18 12:32:56 +02:00
kziolkowski 90616537bc Watering fields 2025-05-18 11:44:33 +02:00
kziolkowski 0f8eb244e9 planting / growth works again 2025-05-17 19:04:46 +02:00
kziolkowski 38fdad7690 removed print statements 2025-05-17 18:24:15 +02:00
kziolkowski 0de6bc0c04 the most beautiful grid of fields you can imagine 2025-05-17 18:23:35 +02:00
Felix Paul fc2d6592a2 Added Vesna animations with tools 2025-05-17 17:47:52 +02:00
kziolkowski 222c10fe0a limited field creation to specified shape 2025-05-17 17:40:15 +02:00
kziolkowski 8af825bc18 InteractionAreas are now bound to SpriteSwitchers and farming tool interaction works 2025-05-17 16:38:32 +02:00
kziolkowski f7684b6c2a Deleted unused kenney assets 2025-05-17 14:02:20 +02:00
kziolkowski f9a3a8e389 generated import files for art assets 2025-05-17 13:49:07 +02:00
kziolkowski 6ad8fb00bd imported various art assets from owncloud 2025-05-17 13:47:54 +02:00
kziolkowski c4dbc104d4 music files added 2025-05-17 13:41:42 +02:00
kziolkowski 77c0babdca minor changes after merge 2025-05-17 13:37:56 +02:00
kziolkowski aa5a3483bb Merge remote-tracking branch 'origin/Nao's-Sounds' into farming_mechanic 2025-05-17 13:31:21 +02:00
kziolkowski 68b6031da1 Merge remote-tracking branch 'origin/Animations' into farming_mechanic 2025-05-17 13:30:58 +02:00
kziolkowski 4afb02440c Reworking 2D interactions 2025-05-16 23:16:27 +02:00
Nao de5657a9cd More SFX Added 2025-05-15 19:06:27 +02:00
kziolkowski ff85b67087 Made interaction area 2d work - and farming/growing work again. 2025-05-14 23:12:14 +02:00
Nao 6f3d843818 SFX and Atmos added (with variations) 2025-05-14 19:58:03 +02:00
Felix Paul 20f8eaaebd Added Vesna 2D image sequences 2025-05-14 12:48:37 +02:00
Felix Paul 69848e77c9 Added PickUp and Interact (Back) animations 2025-05-12 17:57:12 +02:00
kziolkowski 366923699c WIP 2D rework 2025-05-12 00:16:13 +02:00
kziolkowski 2a7f39e3fe Adjusted movement and included collider collisions as a constraint to the player 2025-05-11 23:20:35 +02:00
kziolkowski 296fed6e08 Added fields 2025-05-10 16:33:35 +02:00
kziolkowski 1af04631c5 Added 2d outdoor scene with parallaxe and mockup background 2025-05-10 16:29:23 +02:00
Felix Paul 9ec1bcab97 Added 2 animations
Added Fill Watercan and Hold Item animations
2025-05-05 20:24:26 +02:00
kziolkowski d11fc9f1f2 Cleaned up house mockup outline and removed spammy debug log 2025-04-30 18:49:46 +02:00
kziolkowski 16a65f7155 added flat indoor test and skybox to 3d test 2025-04-30 18:43:18 +02:00
kziolkowski 0fcc425ace built first 2d indoor scene + added kitchen 2025-04-30 17:36:38 +02:00
Felix Paul 21685d60d5 Added Vesna animations 2025-04-28 20:15:39 +02:00
kziolkowski 752cdb1502 Added new how sprite 2025-04-27 10:09:11 +02:00
kziolkowski 8299998a0f Added fence to the left part of the farm 2025-04-26 15:15:38 +02:00
kziolkowski 5ea6431216 Added tree prefabs and a forest border around the farm 2025-04-26 14:51:56 +02:00
Felix Paul 7cccd40290 Test
Einige animationen in 3D
2025-04-25 21:19:04 +02:00
kziolkowski 762a694be3 Spawning and watering is now bound to the cursor position (kinda) 2025-04-25 00:13:50 +02:00
kziolkowski 339428c312 Basic watering functionality implemented 2025-04-24 23:36:23 +02:00
kziolkowski 7e19268847 Merge remote-tracking branch 'origin/main' into c_sharp_setup
# Conflicts:
#	scripts/CSharp/Common/CharacterControls/VesnaBehaviour.cs.uid
#	scripts/CSharp/Common/Farming/FieldService.cs.uid
2025-04-24 21:22:56 +02:00
kziolkowski 20b37de108 generated files 2025-04-24 21:22:14 +02:00
kziolkowski 7d1187d218 Merge pull request 'inventory' (#3) from inventory into main
Reviewed-on: #3
Reviewed-by: kziolkowski <katharina.ziolkowski@gmail.com>
2025-04-24 21:18:13 +02:00
cblech 54b07cccad added autogenerated items 2025-04-17 16:10:31 +02:00
cblech fb0c14c3f1 Added item stacking 2025-04-17 16:10:12 +02:00
cblech fb362e10ad item count WIP 2025-04-17 03:24:31 +02:00
kziolkowski c73d3ff370 Reworked the fieldservice 2025-04-14 18:00:10 +02:00
cblech e99b574d1f Added pickup error text 2025-04-14 17:36:29 +02:00
cblech 1062193785 item on ground and pickup 2025-04-14 16:49:29 +02:00
cblech 9190b2a16a Merge remote-tracking branch 'origin/c_sharp_setup' into inventory 2025-04-14 04:53:29 +02:00
kziolkowski c200098f0c WIP reworking the field behaviour to implement watering. Broken state. 2025-04-14 00:08:19 +02:00
kziolkowski 0e775309fb Scene changes 2025-04-13 15:01:49 +02:00
kziolkowski 5db71adf03 Fields spawn on a grid 2025-04-13 14:56:25 +02:00
kziolkowski ce5d2b65d3 Separated farming controls from vesna behvaiour and adjusted the setup 2025-04-13 14:35:14 +02:00
kziolkowski 6016f1cf38 Removed Debug.Log 2025-04-09 18:18:19 +02:00
kziolkowski b5dcad5614 Vesna walking and idle animations work for all movement directions. 2025-04-09 18:14:05 +02:00
cblech 64f039ba19 Removed Lock files 2025-04-07 22:19:38 +02:00
cblech 8c1822557c Added Inventory into babushka farming scene 2025-04-07 19:59:55 +02:00
cblech dacf48b9fd Merge remote-tracking branch 'origin/c_sharp_setup' into inventory 2025-04-07 18:44:38 +02:00
cblech daa53e4aef Added slot select 2025-04-07 18:43:56 +02:00
cblech 77591425cb removed unused slots script 2025-04-07 02:56:08 +02:00
cblech 667ef6fb9b Added open close inventory 2025-04-07 02:52:57 +02:00
cblech c90687939c Move items 2025-04-07 02:11:08 +02:00
kziolkowski 8622f2d38d Added vesna animations 2025-04-07 00:17:50 +02:00
kziolkowski bd4f7edc20 Creating new plantable fields works 2025-04-06 23:22:05 +02:00
kziolkowski 45a58f701b Spawning fields works 2025-04-06 23:13:25 +02:00
kziolkowski f08f3a7978 Fixed Pickup behaviour 2025-04-06 22:03:13 +02:00
kziolkowski 26502f2d37 old style farming works again 2025-04-06 20:50:55 +02:00
kziolkowski a9b1c179cd folder refactoring, plant_base adjustments 2025-04-06 18:46:59 +02:00
kziolkowski d144f5c0c4 Added Sprite Swap and Horizontal flipping to character controller 2025-04-06 15:06:22 +02:00
kziolkowski 45d09cd618 Farming part I 2025-04-04 00:22:35 +02:00
kziolkowski f2902ad7c1 added 2d and 3d house prototypes 2025-03-30 23:25:37 +02:00
kziolkowski 7f8b444270 Translated Player Scripts from GDScript to C# 2025-03-27 23:12:16 +01:00
kziolkowski d4b7c0bf45 Translated some scripts from GDscript to C# 2025-03-26 00:26:29 +01:00
cblech 959ab68b4d Added basic inventory system 2025-03-25 19:39:06 +01:00
kziolkowski 4f312209b3 a bit of restructuring, renaming, and c sharp setup 2025-03-24 21:55:34 +01:00
kziolkowski e0b5a26ee6 created first c# setup 2025-03-21 19:50:01 +01:00
cblech 78471268db Show interaction popup (<E> Label) 2024-12-20 20:34:10 +01:00
cblech 84fcec8227 Added basic interaction functionality 2024-12-20 16:28:02 +01:00
cblech dc322d8943 Fixed camera 2024-12-20 14:52:24 +01:00
cblech 4ce0367f15 Added 3D Controller 2024-12-02 18:50:26 +01:00
cblech 421a90ec17 Added camera preview addon 2024-12-02 18:49:36 +01:00
kziolkowski ec10f0e54b created 3D abandoned street mockup scene 2024-11-12 22:42:34 +01:00
kziolkowski cbcbcbbcee abandoned street mockup done 2024-11-11 23:33:37 +01:00
kziolkowski dde4f18305 generated project changes 2024-11-11 22:37:43 +01:00
kziolkowski 267a0b9556 renamed old sprites 2024-11-11 22:37:01 +01:00
cblech 5ea5e5547c Added basic dialogic translation 2024-10-28 12:27:17 +01:00
cblech d231bb0773 Added Dialogic and a test dialog 2024-10-28 12:06:03 +01:00
cblech ea20332b4f Made very basic graphics test scene 2024-10-27 21:31:14 +01:00
cblech 6090e7b17b audio testing setup 2024-10-03 13:54:39 +02:00
4874 changed files with 326133 additions and 5 deletions
+11
View File
@@ -1,3 +1,14 @@
# Godot 4+ specific ignores
.godot/
/android/
/builds/
/_builds/
/_clips/
# temporary files
**/*.tmp
**/*.translation
**/*~lock~
# override config can be used by developers to override the settings without pushing changes to the repository
override.cfg
+13
View File
@@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/modules.xml
/contentModel.xml
/projectSettingsUpdater.xml
/.idea.Babushka.iml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+13
View File
@@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/projectSettingsUpdater.xml
/contentModel.xml
/.idea.Babushka.iml
/modules.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="XmlDuplicatedId" enabled="true" level="ERROR" enabled_by_default="true" />
</profile>
</component>
+10
View File
@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="GdSdk Master" type="GdScript">
<properties path="$USER_HOME$/.cache/JetBrains/Rider2025.1/projects/.idea.babushka.a4de4632/sdk/GdSdk Master" version="Master" date="2024-06-01T15:14:16.000+02:00" />
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="file://$USER_HOME$/.cache/JetBrains/Rider2025.1/projects/.idea.babushka.a4de4632/sdk/GdSdk Master" />
</SOURCES>
</library>
</component>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
+10
View File
@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="GdSdk Master" type="GdScript">
<properties path="$USER_HOME$/.cache/JetBrains/Rider2024.3/projects/babushka.51b917cd/sdk/GdSdk Master" version="Master" date="2024-06-01T15:14:16.000+02:00" />
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="file://$USER_HOME$/.cache/JetBrains/Rider2024.3/projects/babushka.51b917cd/sdk/GdSdk Master" />
</SOURCES>
</library>
</component>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+69
View File
@@ -0,0 +1,69 @@
extends Node
@export_node_path("Node2D")
var target_node_path = NodePath()
@export var flip_bend_direction = false
@export var joint_one_bone_index = -1
@export var joint_two_bone_index = -1
var _angle_a = 0.0
var _angle_b = 0.0
func _process(delta: float) -> void:
_update_two_bone_ik_angles()
func _update_two_bone_ik_angles():
assert(joint_one_bone_index != -1)
assert(joint_two_bone_index != -1)
if target_node_path.is_empty():
return
var target = get_node(target_node_path) as Node2D
var bone_a = get_parent().get_bone(joint_one_bone_index)
var bone_b = get_parent().get_bone(joint_two_bone_index)
var bone_a_len = bone_a.get_length()
var bone_b_len = bone_b.get_length()
var sin_angle2 = 0.0
var cos_angle2 = 1.0
_angle_b = 0.0
var cos_angle2_denom = 2.0 * bone_a_len * bone_b_len
if not is_zero_approx(cos_angle2_denom):
var target_len_sqr = _distance_squared_between(bone_a, target)
var bone_a_len_sqr = bone_a_len * bone_a_len
var bone_b_len_sqr = bone_b_len * bone_b_len
cos_angle2 = (target_len_sqr - bone_a_len_sqr - bone_b_len_sqr) / cos_angle2_denom
cos_angle2 = clamp(cos_angle2, -1.0, 1.0);
_angle_b = acos(cos_angle2)
if flip_bend_direction:
_angle_b = -_angle_b
sin_angle2 = sin(_angle_b)
var tri_adjacent = bone_a_len + bone_b_len * cos_angle2
var tri_opposite = bone_b_len * sin_angle2
var xform_inv = bone_a.get_parent().global_transform.affine_inverse()
var target_pos = xform_inv * target.global_position - bone_a.position
var tan_y = target_pos.y * tri_adjacent - target_pos.x * tri_opposite
var tan_x = target_pos.x * tri_adjacent + target_pos.y * tri_opposite
_angle_a = atan2(tan_y, tan_x)
var bone_a_angle = bone_a.get_bone_angle()
var bone_b_angle = bone_b.get_bone_angle()
bone_a.rotation = _angle_a - bone_a_angle
bone_b.rotation = _angle_b - angle_difference(bone_a_angle, bone_b_angle)
func _distance_squared_between(node_a: Node2D, node_b: Node2D) -> float:
return node_a.global_position.distance_squared_to(node_b.global_position)
+1
View File
@@ -0,0 +1 @@
uid://p5wxvjx24brs
+11
View File
@@ -0,0 +1,11 @@
<Project Sdk="Godot.NET.Sdk/4.5.1">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Folder Include="prefabs\UI\Inventory\" />
<Folder Include="scripts\CSharp\Low Code\Randomizer\" />
</ItemGroup>
</Project>
+8
View File
@@ -0,0 +1,8 @@
<Project Sdk="Godot.NET.Sdk/4.2.0">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
</Project>
+8
View File
@@ -0,0 +1,8 @@
<Project Sdk="Godot.NET.Sdk/4.2.2">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
</Project>
+19
View File
@@ -0,0 +1,19 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Babushka", "Babushka.csproj", "{349E0430-96B6-4673-8609-74416BA9B1F6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
ExportDebug|Any CPU = ExportDebug|Any CPU
ExportRelease|Any CPU = ExportRelease|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{349E0430-96B6-4673-8609-74416BA9B1F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{349E0430-96B6-4673-8609-74416BA9B1F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{349E0430-96B6-4673-8609-74416BA9B1F6}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
{349E0430-96B6-4673-8609-74416BA9B1F6}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
{349E0430-96B6-4673-8609-74416BA9B1F6}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
{349E0430-96B6-4673-8609-74416BA9B1F6}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
EndGlobalSection
EndGlobal
+6
View File
@@ -0,0 +1,6 @@
<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/CodeStyle/Naming/CSharpNaming/UserRules/=53eecf85_002Dd821_002D40e8_002Dac97_002Dfdb734542b84/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0B2502BD29F5EC4798EEFD2950AA7E06/Description/@EntryValue">Godot Signal</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0B2502BD29F5EC4798EEFD2950AA7E06/Text/@EntryValue">[Signal]
public delegate void $SignalName$EventHandler($END$);</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0B2502BD29F5EC4798EEFD2950AA7E06/Field/=SignalName/Expression/@EntryValue">suggestVariableName()</s:String></wpf:ResourceDictionary>
+38
View File
@@ -0,0 +1,38 @@
<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_003AArea2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fefbd3244e8e427e388f389cc304f90548d56b58a375097a197ac2eb8259990bb_003FArea2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArea3D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F8a54226fa2e1c9371a8091f24cfd744aef11fe6869527dc23b9b837623a29b9_003FArea3D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArray_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe37dc1faf08a4d5ea030ad59bdf77522523400_003Fa3_003Fe272a3a7_003FArray_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAudioStreamPlayer2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F848324b1c23114c3f5e8bbb5a42c4ade394c59a7a7a133a66b76581ca571_003FAudioStreamPlayer2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EFieldBehaviour2D_005FScriptMethods_002Egenerated_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F75d11718f1abbc2572fd32e4b83acbec9d79ac_003FBabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EFieldBehaviour2D_005FScriptMethods_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptMethods_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FLocal_003FTemp_003FSourceGeneratedDocuments_003F9509A9D00FD8A232B5E86A84_003FGodot_002ESourceGenerators_003FGodot_002ESourceGenerators_002EScriptMethodsGenerator_003FBabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptMethods_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptProperties_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FLocal_003FTemp_003FSourceGeneratedDocuments_003F9509A9D00FD8A232B5E86A84_003FGodot_002ESourceGenerators_003FGodot_002ESourceGenerators_002EScriptPropertiesGenerator_003FBabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptProperties_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptProperties_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F4298b0f293f987511fc1b7956ee691fd778f8378_003FBabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptProperties_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptProperties_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F4298b0f293f987511fc1b7956ee691fd778f8378_003FBabushka_002Escripts_002ECSharp_002ECommon_002EFarming_002EVesnaBehaviour2D_005FScriptProperties_002Egenerated_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EQuest_002EQuestListItemUi_005FScriptMethods_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F48fad7e7f3c9e292b3fdbddf9d363f0d1752aa_003FBabushka_002Escripts_002ECSharp_002ECommon_002EQuest_002EQuestListItemUi_005FScriptMethods_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABabushka_002Escripts_002ECSharp_002ECommon_002EQuest_002EQuestManager_005FScriptSignals_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FLocal_003FTemp_003FSourceGeneratedDocuments_003F9509A9D00FD8A232B5E86A84_003FGodot_002ESourceGenerators_003FGodot_002ESourceGenerators_002EScriptSignalsGenerator_003FBabushka_002Escripts_002ECSharp_002ECommon_002EQuest_002EQuestManager_005FScriptSignals_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACanvasItem_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fef7b819b226fab796d1dfe66d415dd7510bcac87675020ddb8f03a828e763_003FCanvasItem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACastHelpers_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F3c92637ae2e83da0a63791071c41eae291d594156062866d8621b7ed7245c_003FCastHelpers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACastHelpers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd111abf504bf42b5968a609b168fd093b2e200_003Fbb_003F1c116fcd_003FCastHelpers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACharacterBody2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fbba0bbd7a98ee58286e9484fbe86e01afff6232283f6efd3556eb7116453_003FCharacterBody2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACount_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Ffe5a7cee5a1771b89077bd73292de84439b4f816799e2ad6c2615c6ff5bd748e_003FCount_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADictionary_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fjonathan_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe37dc1faf08a4d5ea030ad59bdf77522523400_003Fd4_003Fbd338aeb_003FDictionary_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExportToolButtonAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe37dc1faf08a4d5ea030ad59bdf77522523400_003F31_003F3e05ef15_003FExportToolButtonAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMustBeVariantAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe37dc1faf08a4d5ea030ad59bdf77522523400_003Fda_003Fbb06d681_003FMustBeVariantAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGD_005Fconstants_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F4ef0bac6437b6a9567d44f62ae567d854fa7b8513ef7139ef349b49768bc9df_003FGD_005Fconstants_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Ff1d69ec2da76ccf9bc8a75c8e0fdca9a7ba1adf8c8c9d5047e2fa5991c02eca_003FNode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AResourceLoader_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F9f4e8eb124d11f8219cb513a19bed22b2120ed29f9d6785ba56e3367b48d581_003FResourceLoader_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AResourceLoader_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F9f4e8eb124d11f8219cb513a19bed22b2120ed29f9d6785ba56e3367b48d581_003FResourceLoader_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc7102cd0ffb8973777e61b1942c3fffac7e14016a511d055c3adf73ff91748_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F76fabf6f8acf4a0099cae0bcf8b218467f10_003F7e_003F28cee476_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGD_005Fconstants_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F4ef0bac6437b6a9567d44f62ae567d854fa7b8513ef7139ef349b49768bc9df_003FGD_005Fconstants_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Ff1d69ec2da76ccf9bc8a75c8e0fdca9a7ba1adf8c8c9d5047e2fa5991c02eca_003FNode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANullable_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F5acc345db3c207bc9d886a36ff14867ef8d65557432172c2a42f19aeac04d1b_003FNullable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AResourceLoader_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F9f4e8eb124d11f8219cb513a19bed22b2120ed29f9d6785ba56e3367b48d581_003FResourceLoader_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_003FJonathan_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_003AShape2D_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F3671dbbd9b17cdf2bf9075b468b6bd7e3ab13fc3be7a116484085d3b6cc9fe_003FShape2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003AC_0021_003FUsers_003FJonathan_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc7102cd0ffb8973777e61b1942c3fffac7e14016a511d055c3adf73ff91748_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=bc5a80e4_002D7ba6_002D4f7d_002Db896_002Dc591eec7ab12/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Tests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;NUnit3x::A6EF2269-9E64-40D4-BA0A-33CB234E2503::net9.0::BabushkaTest.Tests&lt;/TestId&gt;&#xD;
&lt;/TestAncestor&gt;&#xD;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>
+1
View File
@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 16 16" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m8 1a1 1 0 0 0 -1 1v5h-2c-1.108 0-2 .892-2 2v1h10v-1c0-1.108-.892-2-2-2h-2v-5a1 1 0 0 0 -1-1zm-5 10v4l10-1v-3z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 227 B

+37
View File
@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bmnff63evbdhv"
path="res://.godot/imported/Clear.svg-d661617e27b91e3580171e3447fde514.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/SignalVisualizer/Clear.svg"
dest_files=["res://.godot/imported/Clear.svg-d661617e27b91e3580171e3447fde514.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
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1,58 @@
class_name SignalConnection extends Object
# Properties
# |===================================|
# |===================================|
# |===================================|
var signal_id: int
var source_node_name: String
var destination_node_name: String
var method_signature: String
var description: String :
get:
return "ID: {signal_id} Source: {source_node_name} Destination: {destination_node_name} Method: {method_signature}".format({
"signal_id": signal_id,
"source_node_name": source_node_name,
"destination_node_name": destination_node_name,
"method_signature": method_signature,
})
var dictionary_key: String :
get:
return "{signal_id}__{source_node_name}__{destination_node_name}__{method_signature}".format({ "signal_id": signal_id, "source_node_name": source_node_name, "destination_node_name": destination_node_name, "method_signature": method_signature.replace("::", "_") })
var dictionary_representation: Dictionary :
get:
return {
"signal_id": signal_id,
"source_node_name": source_node_name,
"destination_node_name": destination_node_name,
"method_signature": method_signature,
}
# Lifecycle
# |===================================|
# |===================================|
# |===================================|
func _init(signal_id: int, source_node_name: String, destination_node_name: String, method_signature: String):
self.signal_id = signal_id
self.source_node_name = source_node_name
self.destination_node_name = destination_node_name
self.method_signature = method_signature
# Signals
# |===================================|
# |===================================|
# |===================================|
# Methods
# |===================================|
# |===================================|
# |===================================|
@@ -0,0 +1 @@
uid://dm613ct57qfwa
@@ -0,0 +1,56 @@
class_name SignalDescription extends Object
# Properties
# |===================================|
# |===================================|
# |===================================|
var id: int:
get:
if _source_id != null:
return _source_id
return get_instance_id()
var node_name: String
var signal_name: String
var description: String :
get:
return "ID: {id} Node: {node_name} Signal: {signal_name}".format({
"id": id,
"node_name": node_name,
"signal_name": signal_name,
})
var dictionary_representation: Dictionary :
get:
return {
"id": id,
"node_name": node_name,
"signal_name": signal_name,
}
var _source_id = null
# Lifecycle
# |===================================|
# |===================================|
# |===================================|
func _init(node_name: String, signal_name: String):
self.node_name = node_name
self.signal_name = signal_name
# Signals
# |===================================|
# |===================================|
# |===================================|
# Methods
# |===================================|
# |===================================|
# |===================================|
@@ -0,0 +1 @@
uid://dvgsocxisw3ae
@@ -0,0 +1,53 @@
class_name SignalGraph extends Object
# Properties
# |===================================|
# |===================================|
# |===================================|
var name: String
var signals: Array[SignalDescription]
var edges: Array[SignalConnection]
var description: String :
get:
return "Signals: {signals}\nEdges: {edges}".format({
"signals": signals.map(func (item): return item.description),
"edges": edges.map(func (item): return item.description),
})
var dictionary_representation: Dictionary :
get:
return {
"name": name,
"signals": signals.map(func (element): return element.dictionary_representation),
"edges": edges.map(func (element): return element.dictionary_representation),
}
# Lifecycle
# |===================================|
# |===================================|
# |===================================|
func _init(name: String, signals: Array[SignalDescription] = [], edges: Array[SignalConnection] = []):
self.name = name
self.signals = signals
self.edges = edges
# Signals
# |===================================|
# |===================================|
# |===================================|
# Methods
# |===================================|
# |===================================|
# |===================================|
func get_source_signal_for_edge(edge: SignalConnection) -> SignalDescription:
var result = signals.filter(func (item): return item.id == edge.signal_id)
if result.size() > 0:
return result[0]
return null
@@ -0,0 +1 @@
uid://2qj81iy1le0a
@@ -0,0 +1,170 @@
@tool
class_name SignalGraphUtility
static var SignalGraphNode = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node.tscn")
static var GraphNodeItem = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node_item.tscn")
const SOURCE_COLOR: Color = Color.SKY_BLUE
const DESTINATION_COLOR: Color = Color.CORAL
const CONNECTION_TYPE: int = 0
#region Methods
static func create_signal_graph(name: String, signals: Array, edges: Array) -> SignalGraph:
var signal_graph = SignalGraph.new(name)
for signal_item in signals:
var new_signal_description = SignalDescription.new(signal_item.node_name, signal_item.signal_name)
new_signal_description._source_id = signal_item.id
signal_graph.signals.append(new_signal_description)
for connection in edges:
var new_edge = SignalConnection.new(connection.signal_id, connection.source_node_name, connection.destination_node_name, connection.method_signature)
signal_graph.edges.append(new_edge)
return signal_graph
static func create_signal_graph_from_node(root_node: Node, is_persistent_only: bool = false):
var signal_graph = SignalGraph.new(root_node.scene_file_path)
var all_nodes: Array[Node] = _gather_nodes_from_node(root_node)
var signals: Array[SignalDescription] = []
var edges: Array[SignalConnection] = []
for node in all_nodes:
for signal_item in node.get_signal_list():
var existing_signals = []
var connection_list = node.get_signal_connection_list(signal_item["name"] as String)
if connection_list.size() > 0:
for connection in connection_list:
var enabled_flags = connection["flags"] == CONNECT_PERSIST if is_persistent_only else true
var should_display_connection = "name" in connection["callable"].get_object() and not connection["callable"].get_object().name.begins_with("@") and enabled_flags
if should_display_connection:
var signal_description: SignalDescription
var filtered_signals = existing_signals.filter(func (element): return element.signal_name == signal_item.name and element.node_name == node.name)
if filtered_signals.size() == 1:
signal_description = filtered_signals[0]
else:
signal_description = SignalDescription.new(node.name, signal_item.name)
existing_signals.append(signal_description)
signals.append(signal_description)
var signal_edge = SignalConnection.new(signal_description.id, signal_description.node_name, connection["callable"].get_object().name, connection["callable"].get_method())
if not signal_graph.edges.any(func (element): return element.signal_id == signal_description.id):
edges.append(signal_edge)
var temp_signals = {}
for item in signals:
temp_signals[item.id] = item
var temp_edges = {}
for item in edges:
temp_edges[item.dictionary_key] = item
signal_graph.signals.assign(temp_signals.keys().map(func (key): return temp_signals[key]))
signal_graph.edges.assign(temp_edges.keys().map(func (key): return temp_edges[key]))
return signal_graph
static func generate_signal_graph_nodes(signal_graph: SignalGraph, graph_node: GraphEdit, open_script_callable: Callable):
var graph_nodes: Dictionary = {}
for signal_item in signal_graph.signals:
var current_graph_node: SignalGraphNode
if graph_nodes.has(signal_item.node_name):
current_graph_node = graph_nodes[signal_item.node_name]
if not current_graph_node:
current_graph_node = SignalGraphNode.instantiate()
current_graph_node.title = signal_item.node_name
current_graph_node.name = _get_graph_node_name(signal_item.node_name)
graph_node.add_child(current_graph_node)
graph_nodes[signal_item.node_name] = current_graph_node
for edge in signal_graph.edges:
var destination_graph_node: SignalGraphNode
if graph_nodes.has(edge.destination_node_name):
destination_graph_node = graph_nodes[edge.destination_node_name]
else:
destination_graph_node = SignalGraphNode.instantiate()
destination_graph_node.title = edge.destination_node_name
destination_graph_node.name = _get_graph_node_name(edge.destination_node_name)
graph_node.add_child(destination_graph_node)
graph_nodes[edge.destination_node_name] = destination_graph_node
var source_signal = signal_graph.get_source_signal_for_edge(edge)
if source_signal != null:
var source_graph_node: SignalGraphNode = graph_nodes[edge.source_node_name] as SignalGraphNode
if not source_graph_node.has_source_signal_description(source_signal.signal_name, edge.destination_node_name):
var source_signal_label = Label.new()
source_signal_label.text = source_signal.signal_name
source_signal_label.name = "source_" + source_signal.signal_name + "_" + edge.destination_node_name
source_graph_node.add_child(source_signal_label)
var destination_signal_name = "destination_" + source_signal.signal_name + "_" + edge.method_signature.replace("::", "__")
var has_destination = destination_graph_node.has_destination_signal_description(source_signal.signal_name, edge.method_signature)
if not has_destination:
var destination_signal_item = GraphNodeItem.instantiate()
destination_signal_item.signal_data = SignalGraphNodeItem.Metadata.new(source_signal.signal_name, edge.method_signature, edge.destination_node_name)
destination_signal_item.text = edge.method_signature
destination_signal_item.name = destination_signal_name
destination_signal_item.open_script.connect(open_script_callable)
destination_graph_node.add_child(destination_signal_item)
for edge in signal_graph.edges:
var source_signal = signal_graph.get_source_signal_for_edge(edge)
if source_signal != null:
var source_graph_node: SignalGraphNode = graph_nodes[edge.source_node_name] as SignalGraphNode
var destination_graph_node: SignalGraphNode = graph_nodes[edge.destination_node_name] as SignalGraphNode
var from_port = source_graph_node.get_source_slot(source_signal.signal_name, edge.destination_node_name)
var to_port = destination_graph_node.get_destination_slot(source_signal.signal_name, edge.method_signature)
source_graph_node.set_slot(from_port, false, CONNECTION_TYPE, Color.BLACK, true, CONNECTION_TYPE, SOURCE_COLOR)
destination_graph_node.set_slot(to_port, true, CONNECTION_TYPE, DESTINATION_COLOR, false, CONNECTION_TYPE, Color.BLACK)
var from_slot_index = source_graph_node.get_next_source_slot(source_signal.signal_name, edge.destination_node_name)
var to_slot_index = destination_graph_node.get_next_destination_slot(source_signal.signal_name, edge.method_signature)
if from_port >= 0 and to_port >= 0:
graph_node.connect_node(source_graph_node.name, from_slot_index, destination_graph_node.name, to_slot_index)
else:
print(">>> Invalid Connection Request")
static func generate_signal_graph_tree(signal_graph: SignalGraph, tree_node: Tree):
var root = tree_node.create_item()
root.set_text(0, signal_graph.name)
var tree_items: Dictionary = {}
for signal_item in signal_graph.signals:
var node_tree_item: TreeItem
if tree_items.has(signal_item.node_name):
node_tree_item = tree_items[signal_item.node_name] as TreeItem
else:
node_tree_item = tree_node.create_item(root)
node_tree_item.set_text(0, signal_item.node_name)
tree_items[signal_item.node_name] = node_tree_item
var signal_tree_item = tree_node.create_item(node_tree_item)
signal_tree_item.set_text(0, signal_item.signal_name)
for edge in signal_graph.edges.filter(func (item): return item.signal_id == signal_item.id):
var signal_connection_tree_item = tree_node.create_item(signal_tree_item)
signal_connection_tree_item.set_text(0, edge.destination_node_name + "::" + edge.method_signature)
static func _get_graph_node_name(name: String) -> String:
return "{node_name}_graph_node".format({ "node_name": name })
static func _gather_nodes_from_node(root_node: Node) -> Array[Node]:
var node_list: Array[Node] = [root_node]
return node_list + __gather_nodes_from_node(root_node)
static func __gather_nodes_from_node(node: Node) -> Array[Node]:
var nodes: Array[Node] = []
for child in node.get_children(false):
nodes.append(child)
nodes += __gather_nodes_from_node(child)
return nodes
#endregion
@@ -0,0 +1 @@
uid://csw8uccbs0vuk
@@ -0,0 +1,146 @@
extends Node
# Properties
# |===================================|
# |===================================|
# |===================================|
var _signal_graph: SignalGraph
var _lambda_map: Dictionary = {}
# Lifecycle
# |===================================|
# |===================================|
# |===================================|
func _ready():
if OS.is_debug_build():
EngineDebugger.register_message_capture("signal_debugger", _on_signal_debugger_message_capture)
# Signals
# |===================================|
# |===================================|
# |===================================|
func _on_signal_debugger_message_capture(message: String, data: Array) -> bool:
if message == "start":
_signal_graph = generate_signal_graph()
for signal_item in _signal_graph.signals:
_connect_to_signal(signal_item)
EngineDebugger.send_message(
"signal_debugger:generated_graph",
[[_signal_graph.signals.map(func (item): return item.dictionary_representation), _signal_graph.edges.map(func (item): return item.dictionary_representation)]]
)
if message == "stop" and _signal_graph:
for signal_item in _signal_graph.signals:
_disconnect_from_signal(signal_item)
if message == "invoke_signal" and data.size() == 2:
var node_name = data[0]
var signal_name = data[1]
var root_node = get_tree().current_scene
var node = root_node if root_node.name == node_name else root_node.find_child(node_name)
if node:
var connection_list = node.get_signal_connection_list(signal_name)
for connection in connection_list:
var callable = connection["callable"]
var bound_args = callable.get_bound_arguments()
var bound_args_count = callable.get_bound_arguments_count()
var method = callable.get_method()
callable.callv([node])
return true
func _on_signal_execution(signal_name: String, node_name: String, args):
EngineDebugger.send_message(
"signal_debugger:signal_executed",
[Time.get_datetime_string_from_system(), node_name, signal_name]
)
# Methods
# |===================================|
# |===================================|
# |===================================|
func generate_signal_graph() -> SignalGraph:
var graph = SignalGraphUtility.create_signal_graph_from_node(get_tree().current_scene)
return graph
#var signal_graph = SignalGraph.new(get_tree().current_scene.name)
#var all_nodes: Array[Node] = _gather_nodes_in_scene()
#var signals: Array[SignalDescription] = []
#var edges: Array[SignalConnection] = []
#
#for node in all_nodes:
#for signal_item in node.get_signal_list():
#var existing_signals = []
#var connection_list = node.get_signal_connection_list(signal_item["name"] as String)
#if connection_list.size() > 0:
#for connection in connection_list:
#var should_display_connection = "name" in connection["callable"].get_object() and not connection["callable"].get_object().name.begins_with("@")
#if should_display_connection:
#var signal_description: SignalDescription
#var filtered_signals = existing_signals.filter(func (element): return element.signal_name == signal_item.name and element.node_name == node.name)
#if filtered_signals.size() == 1:
#signal_description = filtered_signals[0]
#else:
#signal_description = SignalDescription.new(node.name, signal_item.name)
#existing_signals.append(signal_description)
#signals.append(signal_description)
#
#var signal_edge = SignalConnection.new(signal_description.id, signal_description.node_name, connection["callable"].get_object().name, connection["callable"].get_method())
#if not signal_graph.edges.any(func (element): return element.signal_id == signal_description.id):
#edges.append(signal_edge)
#
#var temp_signals = {}
#for item in signals:
#temp_signals[item.id] = item
#
#var temp_edges = {}
#for item in edges:
#temp_edges[item.dictionary_key] = item
#
#signal_graph.signals.assign(temp_signals.keys().map(func (key): return temp_signals[key]))
#signal_graph.edges.assign(temp_edges.keys().map(func (key): return temp_edges[key]))
#
#return signal_graph
#func _gather_nodes_in_scene() -> Array[Node]:
#var scene_root = get_tree().current_scene
#var node_list: Array[Node] = [scene_root]
#return node_list + _gather_nodes_from_node(scene_root)
#
#func _gather_nodes_from_node(node: Node) -> Array[Node]:
#var nodes: Array[Node] = []
#for child in node.get_children(false):
#nodes.append(child)
#nodes += _gather_nodes_from_node(child)
#
#return nodes
func _connect_to_signal(signal_item: SignalDescription):
var root_node = get_tree().current_scene
var _execute: Callable = func (args = []): _on_signal_execution(signal_item.signal_name, signal_item.node_name, args)
if root_node.name == signal_item.node_name:
root_node.connect(signal_item.signal_name, _execute)
_lambda_map[signal_item] = _execute
else:
var child = root_node.find_child(signal_item.node_name)
if child:
child.connect(signal_item.signal_name, _execute)
_lambda_map[signal_item] = _execute
func _disconnect_from_signal(signal_item: SignalDescription):
var root_node = get_tree().current_scene
if root_node.name == signal_item.node_name:
var callable = _lambda_map[signal_item]
if callable:
root_node.disconnect(signal_item.signal_name, callable)
_lambda_map.erase(signal_item)
else:
var child = root_node.find_child(signal_item.node_name)
if child:
var callable = _lambda_map[signal_item]
if callable:
child.disconnect(signal_item.signal_name, callable)
_lambda_map.erase(signal_item)
@@ -0,0 +1 @@
uid://bmsqdh2cnmgw8
@@ -0,0 +1,97 @@
[gd_scene load_steps=5 format=3 uid="uid://cbsmvov8u78q"]
[ext_resource type="Script" path="res://addons/SignalVisualizer/Debugger/signal_debugger_panel.gd" id="1_66cpc"]
[ext_resource type="Texture2D" uid="uid://be3nwoioa311t" path="res://addons/SignalVisualizer/Play.svg" id="2_2wkuv"]
[ext_resource type="Texture2D" uid="uid://oo1oq2colx5b" path="res://addons/SignalVisualizer/Stop.svg" id="3_bg5eu"]
[ext_resource type="Texture2D" uid="uid://bmnff63evbdhv" path="res://addons/SignalVisualizer/Clear.svg" id="4_vg63r"]
[node name="SignalDebugger" type="Control"]
clip_contents = true
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_66cpc")
start_icon = ExtResource("2_2wkuv")
stop_icon = ExtResource("3_bg5eu")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
clip_contents = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
custom_minimum_size = Vector2(2.08165e-12, 50)
layout_mode = 2
theme_override_constants/separation = 8
[node name="ActionButton" type="Button" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
disabled = true
text = "Start"
icon = ExtResource("2_2wkuv")
[node name="ClearAllButton" type="Button" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Clear All"
icon = ExtResource("4_vg63r")
[node name="Spacer" type="Control" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="ClearLogsButton" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Clear Logs"
icon = ExtResource("4_vg63r")
[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="SignalTree" type="Tree" parent="VBoxContainer/HSplitContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(250, 2.08165e-12)
layout_mode = 2
columns = 2
allow_reselect = true
allow_rmb_select = true
hide_root = true
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HSplitContainer"]
layout_mode = 2
[node name="TabBar" type="TabBar" parent="VBoxContainer/HSplitContainer/VBoxContainer"]
layout_mode = 2
tab_count = 2
tab_0/title = "Signal Log"
tab_1/title = "Signal Graph"
[node name="LogLabel" type="RichTextLabel" parent="VBoxContainer/HSplitContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_colors/default_color = Color(0.690196, 0.690196, 0.690196, 1)
bbcode_enabled = true
scroll_following = true
[node name="Graph" type="GraphEdit" parent="VBoxContainer/HSplitContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 2
size_flags_vertical = 3
[connection signal="pressed" from="VBoxContainer/HBoxContainer/ActionButton" to="." method="_on_action_button_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/ClearAllButton" to="." method="_on_clear_all_button_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/ClearLogsButton" to="." method="_on_clear_logs_button_pressed"]
[connection signal="item_selected" from="VBoxContainer/HSplitContainer/SignalTree" to="." method="_on_signal_tree_item_selected"]
[connection signal="tab_changed" from="VBoxContainer/HSplitContainer/VBoxContainer/TabBar" to="." method="_on_tab_bar_tab_changed"]
@@ -0,0 +1,192 @@
@tool
class_name SignalDebuggerPanel extends Control
signal open_script(node_name: String, method_signature: String)
signal start_signal_debugging
signal stop_signal_debugging
var SignalGraphNode = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node.tscn")
var GraphNodeItem = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node_item.tscn")
const SOURCE_COLOR: Color = Color.SKY_BLUE
const DESTINATION_COLOR: Color = Color.CORAL
const CONNECTION_TYPE: int = 0
enum Tabs {
LOG,
GRAPH
}
# Properties
# |===================================|
# |===================================|
# |===================================|
@export var start_icon: Texture2D
@export var stop_icon: Texture2D
@onready var action_button: Button = %ActionButton
@onready var clear_all_button: Button = %ClearAllButton
@onready var signal_tree: Tree = %SignalTree
@onready var log_label: RichTextLabel = %LogLabel
@onready var graph_node: GraphEdit = %Graph
var is_started: bool = false :
get: return is_started
set(new_value):
is_started = new_value
_update_action_button()
var _signals: Array = []
var _signal_filter: Array = []
var _is_stack_trace_enabled: bool = false
var _debugger_tab_state: Tabs = Tabs.LOG
var _graph: SignalGraph
# Lifecycle
# |===================================|
# |===================================|
# |===================================|
func _ready():
disable()
_handle_tab_update(0)
# Signals
# |===================================|
# |===================================|
# |===================================|
func _on_action_button_pressed():
if is_started:
stop()
else:
start()
func _on_clear_all_button_pressed():
log_label.clear()
signal_tree.clear()
graph_node.clear_connections()
for child in graph_node.get_children():
if child is SignalGraphNode:
child.queue_free()
func _on_clear_logs_button_pressed():
log_label.clear()
func _on_signal_tree_item_selected():
# Updates the checkmark button
var selected_item = signal_tree.get_selected()
var is_checked = selected_item.is_checked(1)
selected_item.set_checked(1, (not is_checked))
# Add / Remove signal from filters
var selected_signal = _signals.filter(func (element): return element.signal_name == selected_item.get_text(0))[0]
if _signal_filter.has(selected_signal.signal_name):
var selected_index = _signal_filter.find(selected_signal.signal_name)
_signal_filter.remove_at(selected_index)
else:
_signal_filter.append(selected_signal.signal_name)
func _on_tab_bar_tab_changed(tab: int):
_handle_tab_update(tab)
func _on_stack_trace_button_pressed():
_is_stack_trace_enabled = not _is_stack_trace_enabled
func _on_open_signal_in_script(data: SignalGraphNodeItem.Metadata):
open_script.emit(data.node_name, data.method_signature)
# Methods
# |===================================|
# |===================================|
# |===================================|
func enable():
action_button.disabled = false
func disable():
action_button.disabled = true
func start():
if not is_started:
is_started = true
action_button.icon = stop_icon
start_signal_debugging.emit()
log_label.append_text("[color=#B0B0B0]Signal Debugging Started...[/color]")
log_label.newline()
log_label.newline()
func stop():
if is_started:
is_started = false
action_button.icon = start_icon
stop_signal_debugging.emit()
log_label.newline()
log_label.append_text("[color=#B0B0B0]Signal Debugging Stopped[/color]")
log_label.newline()
log_label.newline()
func create_tree_from_signals(signals: Array):
_signals = signals
var root = signal_tree.create_item()
root.set_text(0, "Signals")
var tree_items: Dictionary = {}
for signal_item in signals:
var node_tree_item: TreeItem
if tree_items.has(signal_item.node_name):
node_tree_item = tree_items[signal_item.node_name] as TreeItem
else:
node_tree_item = signal_tree.create_item(root)
node_tree_item.set_text(0, signal_item.node_name)
node_tree_item.set_selectable(0, false)
node_tree_item.set_selectable(1, false)
tree_items[signal_item.node_name] = node_tree_item
var signal_tree_item = signal_tree.create_item(node_tree_item)
signal_tree_item.set_text(0, signal_item.signal_name)
signal_tree_item.set_cell_mode(1, TreeItem.CELL_MODE_CHECK)
signal_tree_item.set_checked(1, true)
signal_tree_item.set_selectable(0, false)
signal_tree_item.set_selectable(1, true)
func create_signal_graph(signals: Array, edges: Array):
_graph = SignalGraphUtility.create_signal_graph(get_tree().edited_scene_root.scene_file_path, signals, edges)
SignalGraphUtility.generate_signal_graph_nodes(_graph, graph_node, _on_open_signal_in_script)
func log_signal_execution(time: String, node_name: String, signal_name: String):
if _signal_filter != null and _signal_filter.has(signal_name):
return
if not log_label.text.is_empty():
log_label.newline()
log_label.append_text(
"[color=#FFCC00]{time}[/color]\t\t{node_name}\t\t{signal_name}".format({ "time": time, "node_name": node_name, "signal_name": signal_name })
)
log_label.newline()
func _handle_tab_update(selected_tab_index: int):
match selected_tab_index:
1:
_debugger_tab_state = Tabs.GRAPH
_:
_debugger_tab_state = Tabs.LOG
match _debugger_tab_state:
Tabs.LOG:
log_label.show()
graph_node.hide()
Tabs.GRAPH:
log_label.hide()
graph_node.show()
func _update_action_button():
if is_started:
action_button.text = "Stop"
action_button.modulate = Color("#ff3b30")
else:
action_button.text = "Start"
action_button.modulate = Color.WHITE
@@ -0,0 +1 @@
uid://yg8cqm6f1prd
+1
View File
@@ -0,0 +1 @@
<svg height="24" width="24" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M11 1a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM6.732 5A2 2 0 0 1 7 6v1.117L9.268 6A2 2 0 0 1 9 5V3.883zM2 5a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1zm5 3.883V10a2 2 0 0 1-.268 1L9 12.117V11a2 2 0 0 1 .268-1zM11 10a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1z" fill="#8eef97"/></svg>

After

Width:  |  Height:  |  Size: 437 B

@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bxj8ep08wbnm6"
path="res://.godot/imported/GraphEdit.svg-90dae61e8e0b157ab8eff95fe4b91e53.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/SignalVisualizer/GraphEdit.svg"
dest_files=["res://.godot/imported/GraphEdit.svg-90dae61e8e0b157ab8eff95fe4b91e53.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
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
+1
View File
@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 16 16" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M4 12a1 1 0 0 0 1.555.832l6-4a1 1 0 0 0 0-1.664l-6-4A1 1 0 0 0 4 4z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 184 B

+37
View File
@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://be3nwoioa311t"
path="res://.godot/imported/Play.svg-a446691ffcef211028bb160b5a2d6ff1.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/SignalVisualizer/Play.svg"
dest_files=["res://.godot/imported/Play.svg-a446691ffcef211028bb160b5a2d6ff1.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
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
+162
View File
@@ -0,0 +1,162 @@
@tool
extends EditorPlugin
class SignalDebuggerPlugin extends EditorDebuggerPlugin:
var SignalDebuggerPanelScene = preload("res://addons/SignalVisualizer/Debugger/SignalDebugger.tscn")
signal open_script
signal start_signal_debugging
signal stop_signal_debugging
var debugger_panel
func _has_capture(prefix) -> bool:
return prefix == "signal_debugger"
func _capture(message, data, session_id) -> bool:
if message == "signal_debugger:signal_executed":
if data.size() == 3:
var time = data[0]
var node_name = data[1]
var signal_name = data[2]
debugger_panel.log_signal_execution(time, node_name, signal_name)
return true
if message == "signal_debugger:generated_graph":
if data.size() == 1:
var signals = data[0][0] as Array
var edges = data[0][1] as Array
debugger_panel.create_tree_from_signals(signals)
debugger_panel.create_signal_graph(signals, edges)
return true
return false
func _setup_session(session_id):
debugger_panel = SignalDebuggerPanelScene.instantiate()
var session = get_session(session_id)
debugger_panel.name = "Signal Debugger"
debugger_panel.open_script.connect(func (arg1, arg2): open_script.emit(arg1, arg2))
debugger_panel.start_signal_debugging.connect(func (): start_signal_debugging.emit())
debugger_panel.stop_signal_debugging.connect(func (): stop_signal_debugging.emit())
session.started.connect(
func ():
debugger_panel.enable()
)
session.stopped.connect(
func ():
debugger_panel.stop()
debugger_panel.disable()
stop_signal_debugging.emit()
)
session.add_session_tab(debugger_panel)
var SignalVisualizerDockScene = preload("res://addons/SignalVisualizer/Visualizer/signal_visualizer_dock.tscn")
class ScriptMethodReference:
var script_reference: Script
var line_number: int
# Properties
# |===================================|
# |===================================|
# |===================================|
var dock: Control
var debugger: SignalDebuggerPlugin
# Lifecycle
# |===================================|
# |===================================|
# |===================================|
func _enter_tree():
dock = SignalVisualizerDockScene.instantiate()
debugger = SignalDebuggerPlugin.new()
dock.open_script.connect(_on_open_signal_in_script)
add_control_to_bottom_panel(dock, "Signal Visualizer")
debugger.start_signal_debugging.connect(_on_debugger_start_signal_debugging)
debugger.stop_signal_debugging.connect(_on_debugger_stop_signal_debugging)
debugger.open_script.connect(_on_open_signal_in_script)
add_debugger_plugin(debugger)
if not ProjectSettings.has_setting("autoload/Signal_Debugger"):
add_autoload_singleton("Signal_Debugger", "res://addons/SignalVisualizer/Debugger/SignalDebugger.gd")
func _exit_tree():
remove_control_from_bottom_panel(dock)
dock.free()
remove_debugger_plugin(debugger)
remove_autoload_singleton("Signal_Debugger")
# Signals
# |===================================|
# |===================================|
# |===================================|
func _on_open_signal_in_script(node_name: String, method_signature: String):
var node: Node
if get_tree().edited_scene_root.name == node_name:
node = get_tree().edited_scene_root
else:
node = get_tree().edited_scene_root.find_child(node_name)
if node != null:
var script: Script = node.get_script()
if script != null:
var editor = get_editor_interface()
var method_reference = _find_method_reference_in_script(script, method_signature)
if method_reference != null:
editor.edit_script(method_reference.script_reference, method_reference.line_number, 0)
editor.set_main_screen_editor("Script")
else:
push_warning("Requested method in script ({script}) for node ({name}) is not available.".format({ "name": node_name, "script": script.name }))
else:
push_warning("Requested script for node ({name}) is not available.".format({ "name": node_name }))
else:
push_warning("Requested script for node ({name}) is not available.".format({ "name": node_name }))
func _on_debugger_start_signal_debugging():
for session in debugger.get_sessions():
session.send_message("signal_debugger:start", [])
func _on_debugger_stop_signal_debugging():
for session in debugger.get_sessions():
session.send_message("signal_debugger:stop", [])
# Methods
# |===================================|
# |===================================|
# |===================================|
func _find_method_reference_in_script(script: Script, method_signature: String) -> ScriptMethodReference:
var line_number = __find_method_line_number_in_script(script, method_signature)
if line_number == -1:
var base_script = script.get_base_script()
if base_script:
return _find_method_reference_in_script(base_script, method_signature)
var reference = ScriptMethodReference.new()
reference.script_reference = script
reference.line_number = line_number
return reference
func __find_method_line_number_in_script(script: Script, method_signature: String) -> int:
var line_number = 0
var found = false
for line in script.source_code.split("\n", true):
line_number += 1
if line.contains(method_signature):
found = true
return line_number
return -1
@@ -0,0 +1 @@
uid://43lcsn3nt3ri
+1
View File
@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 16 16" width="24" xmlns="http://www.w3.org/2000/svg"><rect x="3" y="3" height="10" width="10" rx="1" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 154 B

+37
View File
@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://oo1oq2colx5b"
path="res://.godot/imported/Stop.svg-e085086fb31c334bc2f02ca2bffba522.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/SignalVisualizer/Stop.svg"
dest_files=["res://.godot/imported/Stop.svg-e085086fb31c334bc2f02ca2bffba522.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
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1,31 @@
@tool
extends Label
# Properties
# |===================================|
# |===================================|
# |===================================|
# Lifecycle
# |===================================|
# |===================================|
# |===================================|
# Signals
# |===================================|
# |===================================|
# |===================================|
# Methods
# |===================================|
# |===================================|
# |===================================|
func get_text_size() -> Vector2:
return get_theme_default_font().get_string_size(text)
@@ -0,0 +1 @@
uid://d3lyqancfvwup
@@ -0,0 +1,94 @@
@tool
class_name SignalGraphNode extends GraphNode
# Properties
# |===================================|
# |===================================|
# |===================================|
var connections: Array = [] :
get: return connections
set(new_value):
connections = new_value
# Lifecycle
# |===================================|
# |===================================|
# |===================================|
func _ready():
selectable = true
resizable = true
draggable = true
# Signals
# |===================================|
# |===================================|
# |===================================|
func _on_resize_request(new_minsize):
size = new_minsize
# Methods
# |===================================|
# |===================================|
# |===================================|
func has_source_signal_description(signal_name: String, destination_node_name: String) -> bool:
for child in get_children():
if child.name == "source_" + signal_name + "_" + destination_node_name:
return true
return false
func get_source_slot(signal_name: String, destination_node_name: String) -> int:
var index = 0
for child in get_children():
if child.name == "source_" + signal_name + "_" + destination_node_name:
return index
index += 1
return -1
func get_next_source_slot(signal_name: String, destination_node_name: String) -> int:
var index = 0
for child in get_children():
if child.name.begins_with("source_"):
if child.name == "source_" + signal_name + "_" + destination_node_name:
return index
index += 1
return -1
func has_destination_signal_description(signal_name: String, method_signature: String) -> bool:
for child in get_children():
if child.name == "destination_" + signal_name + "_" + _sanitize_method_signature(method_signature):
return true
return false
func get_destination_slot(signal_name: String, method_signature: String) -> int:
var index = 0
for child in get_children():
if child.name == "destination_" + signal_name + "_" + _sanitize_method_signature(method_signature):
return index
index += 1
return -1
func get_next_destination_slot(signal_name: String, method_signature: String) -> int:
var index = 0
for child in get_children():
if child.name.begins_with("destination_"):
if child.name == "destination_" + signal_name + "_" + _sanitize_method_signature(method_signature):
return index
index += 1
return -1
func _sanitize_method_signature(signature: String) -> String:
return signature.replace("::", "__")
@@ -0,0 +1 @@
uid://bdwkkgkhgfrtk
@@ -0,0 +1,12 @@
[gd_scene load_steps=2 format=3 uid="uid://cq10iaub18e54"]
[ext_resource type="Script" path="res://addons/SignalVisualizer/Visualizer/signal_graph_node.gd" id="1_ovklj"]
[node name="SignalGraphNode" type="GraphNode"]
custom_minimum_size = Vector2(100, 50)
offset_right = 232.0
offset_bottom = 54.0
resizable = true
script = ExtResource("1_ovklj")
[connection signal="resize_request" from="." to="." method="_on_resize_request"]
@@ -0,0 +1,57 @@
@tool
class_name SignalGraphNodeItem extends Control
signal open_script(metadata: SignalGraphNodeItem.Metadata)
class Metadata:
var signal_name: String
var method_signature: String
var node_name: String
func _init(signal_name: String, method_signature: String, node_name: String):
self.signal_name = signal_name
self.method_signature = method_signature
self.node_name = node_name
# Properties
# |===================================|
# |===================================|
# |===================================|
@onready var label: Label = %DescriptionLabel
var signal_data: Metadata = null
var text: String = "" :
get: return text
set(new_value):
text = new_value
if label:
label.text = text
# Lifecycle
# |===================================|
# |===================================|
# |===================================|
func _ready():
_update()
# Signals
# |===================================|
# |===================================|
# |===================================|
func _on_open_signal_in_script_button_pressed():
open_script.emit(signal_data)
# Methods
# |===================================|
# |===================================|
# |===================================|
func _update():
label.text = text
var text_size = label.get_text_size()
custom_minimum_size = Vector2((text_size.x * 2) + 50, text_size.y * 3)
@@ -0,0 +1 @@
uid://c0n3sifmbiih0
@@ -0,0 +1,43 @@
[gd_scene load_steps=3 format=3 uid="uid://b2lwtwp6kpwtb"]
[ext_resource type="Script" path="res://addons/SignalVisualizer/Visualizer/signal_graph_node_item.gd" id="1_jrd34"]
[ext_resource type="Script" path="res://addons/SignalVisualizer/Visualizer/resizable_label.gd" id="2_4wwd5"]
[node name="SignalItem" type="Control"]
clip_contents = true
custom_minimum_size = Vector2(51, 23)
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_jrd34")
[node name="HBoxContainer" type="HBoxContainer" parent="."]
custom_minimum_size = Vector2(100, 50)
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="DescriptionLabel" type="Label" parent="HBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 50)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
vertical_alignment = 1
clip_text = true
script = ExtResource("2_4wwd5")
[node name="OpenSignalInScriptButton" type="Button" parent="HBoxContainer"]
layout_mode = 2
text = "Open"
flat = true
[connection signal="pressed" from="HBoxContainer/OpenSignalInScriptButton" to="." method="_on_open_signal_in_script_button_pressed"]
@@ -0,0 +1,67 @@
@tool
extends Control
signal open_script(node_name: String, method_signature: String)
var SignalGraphNode = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node.tscn")
var GraphNodeItem = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node_item.tscn")
# Properties
# |===================================|
# |===================================|
# |===================================|
const SOURCE_COLOR: Color = Color.SKY_BLUE
const DESTINATION_COLOR: Color = Color.CORAL
const CONNECTION_TYPE: int = 0
@onready var arrange_nodes_checkbox: CheckBox = %ArrangeNodesCheckBox
@onready var signal_details_checkbox: CheckBox = %SignalDetailsCheckBox
@onready var signal_tree: Tree = %SignalTree
@onready var graph: GraphEdit = %Graph
# Lifecycle
# |===================================|
# |===================================|
# |===================================|
# Signals
# |===================================|
# |===================================|
# |===================================|
func _on_clear_graph_button_pressed():
clear()
func _on_generate_graph_button_pressed():
clear()
var scene_signal_graph = SignalGraphUtility.create_signal_graph_from_node(get_tree().edited_scene_root, true)
SignalGraphUtility.generate_signal_graph_nodes(scene_signal_graph, graph, _on_open_signal_in_script)
SignalGraphUtility.generate_signal_graph_tree(scene_signal_graph, signal_tree)
if arrange_nodes_checkbox.button_pressed:
graph.arrange_nodes()
func _on_open_signal_in_script(data: SignalGraphNodeItem.Metadata):
open_script.emit(data.node_name, data.method_signature)
# Methods
# |===================================|
# |===================================|
# |===================================|
func clear():
_clear_graph_nodes()
_clear_tree()
func _clear_graph_nodes():
graph.clear_connections()
for child in graph.get_children():
if child is SignalGraphNode:
child.queue_free()
func _clear_tree():
signal_tree.clear()
@@ -0,0 +1 @@
uid://bbd48wbihmuos
@@ -0,0 +1,78 @@
[gd_scene load_steps=5 format=3 uid="uid://dppfamjc0ji40"]
[ext_resource type="Script" path="res://addons/SignalVisualizer/Visualizer/signal_visualizer_dock.gd" id="1_akar5"]
[ext_resource type="Texture2D" uid="uid://bmnff63evbdhv" path="res://addons/SignalVisualizer/Clear.svg" id="2_m8bsv"]
[ext_resource type="Texture2D" uid="uid://bxj8ep08wbnm6" path="res://addons/SignalVisualizer/GraphEdit.svg" id="3_dtmqs"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ae0jg"]
[node name="SignalVisualizerDock" type="Control"]
clip_contents = true
custom_minimum_size = Vector2(2.08165e-12, 200)
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_akar5")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
clip_contents = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
clip_contents = true
custom_minimum_size = Vector2(2.08165e-12, 50)
layout_mode = 2
theme_override_constants/separation = 8
alignment = 2
[node name="ArrangeNodesCheckBox" type="CheckBox" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Arrange Nodes"
[node name="SignalDetailsCheckBox" type="CheckBox" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Signal Details"
[node name="Panel" type="Panel" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/panel = SubResource("StyleBoxEmpty_ae0jg")
[node name="ClearGraphButton" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Clear Graph"
icon = ExtResource("2_m8bsv")
[node name="GenerateGraphButton" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Generate Graph"
icon = ExtResource("3_dtmqs")
[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="SignalTree" type="Tree" parent="VBoxContainer/HSplitContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(200, 2.08165e-12)
layout_mode = 2
column_titles_visible = true
[node name="Graph" type="GraphEdit" parent="VBoxContainer/HSplitContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[connection signal="pressed" from="VBoxContainer/HBoxContainer/ClearGraphButton" to="." method="_on_clear_graph_button_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/GenerateGraphButton" to="." method="_on_generate_graph_button_pressed"]
+7
View File
@@ -0,0 +1,7 @@
[plugin]
name="SignalVisualizer"
description="Visual the current scene's signal connections as a graph. Debug the current running scene's signals with automatic logging in a new debugger panel."
author="MiniGameDev"
version="1.7.0"
script="SignalVisualizer.gd"
@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M4 11V4h7" fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" stroke-opacity=".6"/></svg>

After

Width:  |  Height:  |  Size: 221 B

@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://btc01wc11tiid"
path="res://.godot/imported/GuiResizerTopLeft.svg-eb563f557424c74239e878a1213a5bf4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/anthonyec.camera_preview/GuiResizerTopLeft.svg"
dest_files=["res://.godot/imported/GuiResizerTopLeft.svg-eb563f557424c74239e878a1213a5bf4.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
svg/scale=2.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M12 11V4h-7" fill="none" stroke="#fff" stroke-opacity=".6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 223 B

@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://04l05jxuyt7k"
path="res://.godot/imported/GuiResizerTopRight.svg-cc1dc8556d51357c5eb0b01d09d8f049.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/anthonyec.camera_preview/GuiResizerTopRight.svg"
dest_files=["res://.godot/imported/GuiResizerTopRight.svg-cc1dc8556d51357c5eb0b01d09d8f049.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
svg/scale=2.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
+1
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 1v1l1 1v3h6v-3l1-1v-1zm1 6-2 3h10l-2-3zm2 4v2l1 2 1-2v-2z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 177 B

@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://do6d60od41vmg"
path="res://.godot/imported/Pin.svg-83b09f5c00a829c5d8b136bf5bae65bc.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/anthonyec.camera_preview/Pin.svg"
dest_files=["res://.godot/imported/Pin.svg-83b09f5c00a829c5d8b136bf5bae65bc.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
svg/scale=2.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
@@ -0,0 +1,7 @@
[plugin]
name="Little Camera Preview"
description="Shows a picture-in-picture preview of the selected 2D or 3D camera"
author="Anthony Cossins"
version="1.3"
script="plugin.gd"
+87
View File
@@ -0,0 +1,87 @@
@tool
extends EditorPlugin
const preview_scene = preload("res://addons/anthonyec.camera_preview/preview.tscn")
var preview: CameraPreview
var current_main_screen_name: String
func _enter_tree() -> void:
main_screen_changed.connect(_on_main_screen_changed)
EditorInterface.get_selection().selection_changed.connect(_on_editor_selection_changed)
# Initialise preview panel and add to main screen.
preview = preview_scene.instantiate() as CameraPreview
preview.request_hide()
var main_screen = EditorInterface.get_editor_main_screen()
main_screen.add_child(preview)
func _exit_tree() -> void:
if preview:
preview.queue_free()
func _ready() -> void:
# TODO: Currently there is no API to get the main screen name without
# listening to the `EditorPlugin.main_screen_changed` signal:
# https://github.com/godotengine/godot-proposals/issues/2081
EditorInterface.set_main_screen_editor("Script")
EditorInterface.set_main_screen_editor("3D")
func _on_main_screen_changed(screen_name: String) -> void:
current_main_screen_name = screen_name
# TODO: Bit of a hack to prevent pinned staying between view changes on the same scene.
preview.unlink_camera()
_on_editor_selection_changed()
func _on_editor_selection_changed() -> void:
if not is_main_screen_viewport():
# This hides the preview "container" and not the preview itself, allowing
# any locked previews to remain visible once switching back to 3D tab.
preview.visible = false
return
preview.visible = true
var selected_nodes = EditorInterface.get_selection().get_selected_nodes()
var selected_camera_3d: Camera3D = find_camera_3d_or_null(selected_nodes)
var selected_camera_2d: Camera2D = find_camera_2d_or_null(selected_nodes)
if selected_camera_3d and current_main_screen_name == "3D":
preview.link_with_camera_3d(selected_camera_3d)
preview.request_show()
elif selected_camera_2d and current_main_screen_name == "2D":
preview.link_with_camera_2d(selected_camera_2d)
preview.request_show()
else:
preview.request_hide()
func is_main_screen_viewport() -> bool:
return current_main_screen_name == "3D" or current_main_screen_name == "2D"
func find_camera_3d_or_null(nodes: Array[Node]) -> Camera3D:
var camera: Camera3D
for node in nodes:
if node is Camera3D:
camera = node as Camera3D
break
return camera
func find_camera_2d_or_null(nodes: Array[Node]) -> Camera2D:
var camera: Camera2D
for node in nodes:
if node is Camera2D:
camera = node as Camera2D
break
return camera
func _on_selected_camera_3d_tree_exiting() -> void:
preview.unlink_camera()
@@ -0,0 +1 @@
uid://dxnp5qcip84u7
+404
View File
@@ -0,0 +1,404 @@
@tool
class_name CameraPreview
extends Control
enum CameraType {
CAMERA_2D,
CAMERA_3D
}
enum PinnedPosition {
LEFT,
RIGHT,
}
enum InteractionState {
NONE,
RESIZE,
DRAG,
# Animation is split into 2 seperate states so that the tween is only
# invoked once in the "start" state.
START_ANIMATE_INTO_PLACE,
ANIMATE_INTO_PLACE,
}
const margin_3d: Vector2 = Vector2(10, 10)
const margin_2d: Vector2 = Vector2(20, 15)
const panel_margin: float = 2
const min_panel_size: float = 250
@onready var panel: Panel = %Panel
@onready var placeholder: Panel = %Placeholder
@onready var preview_camera_3d: Camera3D = %Camera3D
@onready var preview_camera_2d: Camera2D = %Camera2D
@onready var sub_viewport: SubViewport = %SubViewport
@onready var sub_viewport_text_rect: TextureRect = %TextureRect
@onready var resize_left_handle: Button = %ResizeLeftHandle
@onready var resize_right_handle: Button = %ResizeRightHandle
@onready var lock_button: Button = %LockButton
@onready var gradient: TextureRect = %Gradient
@onready var viewport_margin_container: MarginContainer = %ViewportMarginContainer
@onready var overlay_margin_container: MarginContainer = %OverlayMarginContainer
@onready var overlay_container: Control = %OverlayContainer
var camera_type: CameraType = CameraType.CAMERA_3D
var pinned_position: PinnedPosition = PinnedPosition.RIGHT
var viewport_ratio: float = 1
var editor_scale: float = EditorInterface.get_editor_scale()
var is_locked: bool
var show_controls: bool
var selected_camera_3d: Camera3D
var selected_camera_2d: Camera2D
var state: InteractionState = InteractionState.NONE
var initial_mouse_position: Vector2
var initial_panel_size: Vector2
var initial_panel_position: Vector2
func _ready() -> void:
# Set initial width.
panel.size.x = min_panel_size * editor_scale
# Setting texture to viewport in code instead of directly in the editor
# because otherwise an error "Path to node is invalid: Panel/SubViewport"
# on first load. This is harmless but doesn't look great.
#
# This is a known issue:
# https://github.com/godotengine/godot/issues/27790#issuecomment-499740220
sub_viewport_text_rect.texture = sub_viewport.get_texture()
# From what I can tell there's something wrong with how an editor theme
# scales when used within a plugin. It seems to ignore the screen scale.
# For instance, a 30x30px button will appear tiny on a retina display.
#
# Someone else had the issue with no luck:
# https://forum.godotengine.org/t/how-to-scale-plugin-controls-to-look-the-same-in-4k-as-1080p/36151
#
# And seems Dialogic also scales buttons manually:
# https://github.com/dialogic-godot/dialogic/blob/master/addons/dialogic/Editor/Common/sidebar.gd#L25C6-L38
#
# Maybe I don't know the correct way to do it, so for now the workaround is
# to set the correct size in code using screen scale.
var button_size = Vector2(30, 30) * editor_scale
var margin_size: float = panel_margin * editor_scale
resize_left_handle.size = button_size
resize_left_handle.pivot_offset = Vector2(0, 0) * editor_scale
resize_right_handle.size = button_size
resize_right_handle.pivot_offset = Vector2(30, 30) * editor_scale
lock_button.size = button_size
lock_button.pivot_offset = Vector2(0, 30) * editor_scale
viewport_margin_container.add_theme_constant_override("margin_left", margin_size)
viewport_margin_container.add_theme_constant_override("margin_top", margin_size)
viewport_margin_container.add_theme_constant_override("margin_right", margin_size)
viewport_margin_container.add_theme_constant_override("margin_bottom", margin_size)
overlay_margin_container.add_theme_constant_override("margin_left", margin_size)
overlay_margin_container.add_theme_constant_override("margin_top", margin_size)
overlay_margin_container.add_theme_constant_override("margin_right", margin_size)
overlay_margin_container.add_theme_constant_override("margin_bottom", margin_size)
# Parent node overlay size is not available on first ready, need to wait a
# frame for it to be drawn.
await get_tree().process_frame
# Anchors are set in code because setting them in the editor UI doesn't take
# editor scale into account.
resize_left_handle.position = Vector2(0, 0)
resize_right_handle.set_anchors_preset(Control.PRESET_TOP_LEFT)
resize_right_handle.position = Vector2(overlay_container.size.x - button_size.x, 0)
resize_right_handle.set_anchors_preset(Control.PRESET_TOP_RIGHT)
lock_button.position = Vector2(0, overlay_container.size.y - button_size.y)
lock_button.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
func _process(_delta: float) -> void:
if not visible: return
match state:
InteractionState.NONE:
panel.size = get_clamped_size(panel.size)
panel.position = get_pinned_position(pinned_position)
InteractionState.RESIZE:
var delta_mouse_position = initial_mouse_position - get_global_mouse_position()
var resized_size = panel.size
if pinned_position == PinnedPosition.LEFT:
resized_size = initial_panel_size - delta_mouse_position
if pinned_position == PinnedPosition.RIGHT:
resized_size = initial_panel_size + delta_mouse_position
panel.size = get_clamped_size(resized_size)
panel.position = get_pinned_position(pinned_position)
InteractionState.DRAG:
placeholder.size = panel.size
var global_mouse_position = get_global_mouse_position()
var offset = initial_mouse_position - initial_panel_position
panel.global_position = global_mouse_position - offset
if global_mouse_position.x < global_position.x + size.x / 2:
pinned_position = PinnedPosition.LEFT
else:
pinned_position = PinnedPosition.RIGHT
placeholder.position = get_pinned_position(pinned_position)
InteractionState.START_ANIMATE_INTO_PLACE:
var final_position: Vector2 = get_pinned_position(pinned_position)
var tween = get_tree().create_tween()
tween.set_ease(Tween.EASE_OUT)
tween.set_trans(Tween.TRANS_CUBIC)
tween.tween_property(panel, "position", final_position, 0.3)
tween.finished.connect(func():
panel.position = final_position
state = InteractionState.NONE
)
state = InteractionState.ANIMATE_INTO_PLACE
# I couldn't get `mouse_entered` and `mouse_exited` events to work
# nicely, so I use rect method instead. Plus using this method it's easy to
# grow the hit area size.
var panel_hover_rect = Rect2(panel.global_position, panel.size)
panel_hover_rect = panel_hover_rect.grow(40)
var mouse_position = get_global_mouse_position()
show_controls = state != InteractionState.NONE or panel_hover_rect.has_point(mouse_position)
# UI visibility.
resize_left_handle.visible = show_controls and pinned_position == PinnedPosition.RIGHT
resize_right_handle.visible = show_controls and pinned_position == PinnedPosition.LEFT
lock_button.visible = show_controls or is_locked
placeholder.visible = state == InteractionState.DRAG or state == InteractionState.ANIMATE_INTO_PLACE
gradient.visible = show_controls
# Sync camera settings.
if camera_type == CameraType.CAMERA_3D and selected_camera_3d:
sub_viewport.size = panel.size
# Sync position and rotation without using a `RemoteTransform` node
# because if you save a camera as a scene, the remote transform node will
# be stored within the scene. Also it's harder to keep the remote
# transform `remote_path` up-to-date with scene changes, which causes
# many errors.
preview_camera_3d.global_position = selected_camera_3d.global_position
preview_camera_3d.global_rotation = selected_camera_3d.global_rotation
preview_camera_3d.fov = selected_camera_3d.fov
preview_camera_3d.projection = selected_camera_3d.projection
preview_camera_3d.size = selected_camera_3d.size
preview_camera_3d.cull_mask = selected_camera_3d.cull_mask
preview_camera_3d.keep_aspect = selected_camera_3d.keep_aspect
preview_camera_3d.near = selected_camera_3d.near
preview_camera_3d.far = selected_camera_3d.far
preview_camera_3d.h_offset = selected_camera_3d.h_offset
preview_camera_3d.v_offset = selected_camera_3d.v_offset
preview_camera_3d.attributes = selected_camera_3d.attributes
preview_camera_3d.environment = selected_camera_3d.environment
if camera_type == CameraType.CAMERA_2D and selected_camera_2d:
var project_window_size = get_project_window_size()
var ratio = project_window_size.x / panel.size.x
# TODO: Is there a better way to fix this?
# The camera border is visible sometimes due to pixel rounding.
# Subtract 1px from right and bottom to hide this.
var hide_camera_border_fix = Vector2(1, 1)
sub_viewport.size = panel.size
sub_viewport.size_2d_override = (panel.size - hide_camera_border_fix) * ratio
sub_viewport.size_2d_override_stretch = true
preview_camera_2d.global_position = selected_camera_2d.global_position
preview_camera_2d.global_rotation = selected_camera_2d.global_rotation
preview_camera_2d.offset = selected_camera_2d.offset
preview_camera_2d.zoom = selected_camera_2d.zoom
preview_camera_2d.ignore_rotation = selected_camera_2d.ignore_rotation
preview_camera_2d.anchor_mode = selected_camera_2d.anchor_mode
preview_camera_2d.limit_left = selected_camera_2d.limit_left
preview_camera_2d.limit_right = selected_camera_2d.limit_right
preview_camera_2d.limit_top = selected_camera_2d.limit_top
preview_camera_2d.limit_bottom = selected_camera_2d.limit_bottom
func link_with_camera_3d(camera_3d: Camera3D) -> void:
# TODO: Camera may not be ready since this method is called in `_enter_tree`
# in the plugin because of a workaround for:
# https://github.com/godotengine/godot-proposals/issues/2081
if not preview_camera_3d:
return request_hide()
var is_different_camera = camera_3d != preview_camera_3d
# TODO: A bit messy.
if is_different_camera:
if preview_camera_3d.tree_exiting.is_connected(unlink_camera):
preview_camera_3d.tree_exiting.disconnect(unlink_camera)
if not camera_3d.tree_exiting.is_connected(unlink_camera):
camera_3d.tree_exiting.connect(unlink_camera)
sub_viewport.disable_3d = false
sub_viewport.world_3d = camera_3d.get_world_3d()
selected_camera_3d = camera_3d
camera_type = CameraType.CAMERA_3D
func link_with_camera_2d(camera_2d: Camera2D) -> void:
if not preview_camera_2d:
return request_hide()
var is_different_camera = camera_2d != preview_camera_2d
# TODO: A bit messy.
if is_different_camera:
if preview_camera_2d.tree_exiting.is_connected(unlink_camera):
preview_camera_2d.tree_exiting.disconnect(unlink_camera)
if not camera_2d.tree_exiting.is_connected(unlink_camera):
camera_2d.tree_exiting.connect(unlink_camera)
sub_viewport.disable_3d = true
sub_viewport.world_2d = camera_2d.get_world_2d()
selected_camera_2d = camera_2d
camera_type = CameraType.CAMERA_2D
func unlink_camera() -> void:
if selected_camera_3d:
selected_camera_3d = null
if selected_camera_2d:
selected_camera_2d = null
is_locked = false
lock_button.button_pressed = false
func request_hide() -> void:
if is_locked: return
visible = false
func request_show() -> void:
visible = true
func get_pinned_position(pinned_position: PinnedPosition) -> Vector2:
var margin: Vector2 = margin_3d * editor_scale
if camera_type == CameraType.CAMERA_2D:
margin = margin_2d * editor_scale
match pinned_position:
PinnedPosition.LEFT:
return Vector2.ZERO - Vector2(0, panel.size.y) - Vector2(-margin.x, margin.y)
PinnedPosition.RIGHT:
return size - panel.size - margin
_:
assert(false, "Unknown pinned position %s" % str(pinned_position))
return Vector2.ZERO
func get_clamped_size(desired_size: Vector2) -> Vector2:
var viewport_ratio = get_project_window_ratio()
var editor_viewport_size = get_editor_viewport_size()
var max_bounds = Vector2(
editor_viewport_size.x * 0.6,
editor_viewport_size.y * 0.8
)
var clamped_size = desired_size
# Apply aspect ratio.
clamped_size = Vector2(clamped_size.x, clamped_size.x * viewport_ratio)
# Clamp the max size while respecting the aspect ratio.
if clamped_size.y >= max_bounds.y:
clamped_size.x = max_bounds.y / viewport_ratio
clamped_size.y = max_bounds.y
if clamped_size.x >= max_bounds.x:
clamped_size.x = max_bounds.x
clamped_size.y = max_bounds.x * viewport_ratio
# Clamp the min size based on if it's portrait or landscape. Portrait min
# size should be based on it's height. Landscape min size is based on it's
# width instead. Applying min width to a portrait size would make it too big.
var is_portrait = viewport_ratio > 1
if is_portrait and clamped_size.y <= min_panel_size * editor_scale:
clamped_size.x = min_panel_size / viewport_ratio
clamped_size.y = min_panel_size
clamped_size = clamped_size * editor_scale
if not is_portrait and clamped_size.x <= min_panel_size * editor_scale:
clamped_size.x = min_panel_size
clamped_size.y = min_panel_size * viewport_ratio
clamped_size = clamped_size * editor_scale
# Round down to avoid sub-pixel artifacts, mainly seen around the margins.
return clamped_size.floor()
func get_project_window_size() -> Vector2:
var window_width = float(ProjectSettings.get_setting("display/window/size/viewport_width"))
var window_height = float(ProjectSettings.get_setting("display/window/size/viewport_height"))
return Vector2(window_width, window_height)
func get_project_window_ratio() -> float:
var project_window_size = get_project_window_size()
return project_window_size.y / project_window_size.x
func get_editor_viewport_size() -> Vector2:
var fallback_size = EditorInterface.get_editor_main_screen().size
# There isn't an API for getting the viewport node. Instead it has to be
# found by checking the parent's parent of the subviewport and find
# the correct node based on name and class.
var editor_sub_viewport_3d = EditorInterface.get_editor_viewport_3d(0)
var editor_viewport_container = editor_sub_viewport_3d.get_parent().get_parent().get_parent()
# Early return incase editor tree structure has changed.
if editor_viewport_container.get_class() != "Node3DEditorViewportContainer":
return fallback_size
return editor_viewport_container.size
func _on_resize_handle_button_down() -> void:
if state != InteractionState.NONE: return
state = InteractionState.RESIZE
initial_mouse_position = get_global_mouse_position()
initial_panel_size = panel.size
func _on_resize_handle_button_up() -> void:
state = InteractionState.NONE
func _on_drag_handle_button_down() -> void:
if state != InteractionState.NONE: return
state = InteractionState.DRAG
initial_mouse_position = get_global_mouse_position()
initial_panel_position = panel.global_position
func _on_drag_handle_button_up() -> void:
if state != InteractionState.DRAG: return
state = InteractionState.START_ANIMATE_INTO_PLACE
func _on_lock_button_pressed() -> void:
is_locked = !is_locked
@@ -0,0 +1 @@
uid://dcq364kglr71e
@@ -0,0 +1,200 @@
[gd_scene load_steps=8 format=3 uid="uid://xybmfvufjuv"]
[ext_resource type="Script" uid="uid://dcq364kglr71e" path="res://addons/anthonyec.camera_preview/preview.gd" id="1_6b32r"]
[ext_resource type="Texture2D" uid="uid://do6d60od41vmg" path="res://addons/anthonyec.camera_preview/Pin.svg" id="2_p0pa8"]
[ext_resource type="Texture2D" uid="uid://btc01wc11tiid" path="res://addons/anthonyec.camera_preview/GuiResizerTopLeft.svg" id="2_t64ej"]
[ext_resource type="Texture2D" uid="uid://04l05jxuyt7k" path="res://addons/anthonyec.camera_preview/GuiResizerTopRight.svg" id="3_6yuab"]
[sub_resource type="ViewportTexture" id="ViewportTexture_hchdq"]
viewport_path = NodePath("Panel/SubViewport")
[sub_resource type="Gradient" id="Gradient_11p6r"]
offsets = PackedFloat32Array(0, 0.3, 0.6, 1)
colors = PackedColorArray(0, 0, 0, 0.235294, 0, 0, 0, 0.0784314, 0, 0, 0, 0.0784314, 0, 0, 0, 0.235294)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_4dkve"]
gradient = SubResource("Gradient_11p6r")
width = 256
height = 256
fill_to = Vector2(2.08165e-12, 1)
[node name="Preview" type="Control"]
z_index = 999
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_6b32r")
[node name="Placeholder" type="Panel" parent="."]
unique_name_in_owner = true
visible = false
modulate = Color(1, 1, 1, 0.705882)
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -40.0
offset_top = -40.0
offset_right = 410.0
offset_bottom = 410.0
grow_horizontal = 0
grow_vertical = 0
[node name="Panel" type="Panel" parent="."]
unique_name_in_owner = true
clip_contents = true
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -520.0
offset_top = -908.889
offset_right = -20.0
offset_bottom = -20.0
grow_horizontal = 0
grow_vertical = 0
pivot_offset = Vector2(450, 300)
[node name="SubViewport" type="SubViewport" parent="Panel"]
unique_name_in_owner = true
handle_input_locally = false
gui_disable_input = true
size_2d_override_stretch = true
[node name="Camera3D" type="Camera3D" parent="Panel/SubViewport"]
unique_name_in_owner = true
current = true
[node name="Camera2D" type="Camera2D" parent="Panel/SubViewport"]
unique_name_in_owner = true
ignore_rotation = false
[node name="ViewportMarginContainer" type="MarginContainer" parent="Panel"]
unique_name_in_owner = true
clip_contents = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_constants/margin_left = 4
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 4
[node name="TextureRect" type="TextureRect" parent="Panel/ViewportMarginContainer"]
unique_name_in_owner = true
layout_mode = 2
texture = SubResource("ViewportTexture_hchdq")
expand_mode = 1
[node name="Gradient" type="TextureRect" parent="Panel"]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
texture = SubResource("GradientTexture2D_4dkve")
[node name="OverlayMarginContainer" type="MarginContainer" parent="Panel"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_constants/margin_left = 4
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 4
[node name="OverlayContainer" type="Control" parent="Panel/OverlayMarginContainer"]
unique_name_in_owner = true
clip_contents = true
layout_mode = 2
mouse_filter = 2
[node name="DragHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
focus_mode = 0
flat = true
[node name="ResizeLeftHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_right = 60.0
offset_bottom = 60.0
size_flags_horizontal = 0
size_flags_vertical = 0
mouse_default_cursor_shape = 12
icon = ExtResource("2_t64ej")
flat = true
icon_alignment = 1
expand_icon = true
[node name="ResizeRightHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -60.0
offset_bottom = 60.0
pivot_offset = Vector2(60, 60)
size_flags_horizontal = 8
size_flags_vertical = 0
mouse_default_cursor_shape = 11
icon = ExtResource("3_6yuab")
flat = true
icon_alignment = 1
expand_icon = true
[node name="LockButton" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_top = -60.0
offset_right = 60.0
pivot_offset = Vector2(0, 60)
size_flags_horizontal = 0
size_flags_vertical = 8
tooltip_text = "Always Show Preview"
toggle_mode = true
icon = ExtResource("2_p0pa8")
flat = true
icon_alignment = 1
expand_icon = true
[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_button_down"]
[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_button_up"]
[connection signal="renamed" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_renamed"]
[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeLeftHandle" to="." method="_on_resize_handle_button_down"]
[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeLeftHandle" to="." method="_on_resize_handle_button_up"]
[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeRightHandle" to="." method="_on_resize_handle_button_down"]
[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeRightHandle" to="." method="_on_resize_handle_button_up"]
[connection signal="pressed" from="Panel/OverlayMarginContainer/OverlayContainer/LockButton" to="." method="_on_lock_button_pressed"]
@@ -0,0 +1,58 @@
[gd_resource type="Resource" script_class="DialogicStyle" load_steps=18 format=3 uid="uid://b2fmyjrydbg57"]
[ext_resource type="Script" uid="uid://dfx2htp24tuvm" path="res://addons/dialogic/Resources/dialogic_style_layer.gd" id="1_hr3vo"]
[ext_resource type="PackedScene" uid="uid://c1k5m0w3r40xf" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.tscn" id="2_cpmol"]
[ext_resource type="PackedScene" uid="uid://cn674foxwedqu" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.tscn" id="3_12uyf"]
[ext_resource type="PackedScene" uid="uid://by6waso0mjpjp" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/textbox_with_speaker_portrait.tscn" id="4_ncyk4"]
[ext_resource type="PackedScene" uid="uid://dsbwnp5hegnu3" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.tscn" id="5_qg2g4"]
[ext_resource type="PackedScene" uid="uid://dhk6j6eb6e3q" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.tscn" id="6_c5ex4"]
[ext_resource type="PackedScene" uid="uid://cvgf4c6gg0tsy" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.tscn" id="7_bopa1"]
[ext_resource type="PackedScene" uid="uid://lx24i8fl6uo" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.tscn" id="8_oycnw"]
[ext_resource type="Script" uid="uid://b14h380mah4av" path="res://addons/dialogic/Resources/dialogic_style.gd" id="9_i6i1n"]
[sub_resource type="Resource" id="Resource_w535h"]
script = ExtResource("1_hr3vo")
[sub_resource type="Resource" id="Resource_j3dy5"]
script = ExtResource("1_hr3vo")
scene = ExtResource("2_cpmol")
[sub_resource type="Resource" id="Resource_d5o1r"]
script = ExtResource("1_hr3vo")
scene = ExtResource("3_12uyf")
[sub_resource type="Resource" id="Resource_g7mor"]
script = ExtResource("1_hr3vo")
scene = ExtResource("4_ncyk4")
[sub_resource type="Resource" id="Resource_wgjgg"]
script = ExtResource("1_hr3vo")
scene = ExtResource("5_qg2g4")
[sub_resource type="Resource" id="Resource_45hxc"]
script = ExtResource("1_hr3vo")
scene = ExtResource("6_c5ex4")
[sub_resource type="Resource" id="Resource_c53vn"]
script = ExtResource("1_hr3vo")
scene = ExtResource("7_bopa1")
[sub_resource type="Resource" id="Resource_n6k7j"]
script = ExtResource("1_hr3vo")
scene = ExtResource("8_oycnw")
[resource]
script = ExtResource("9_i6i1n")
name = "babushka_dialogue_style"
layer_list = Array[String](["10", "11", "12", "13", "14", "15", "16"])
layer_info = {
"": SubResource("Resource_w535h"),
"10": SubResource("Resource_j3dy5"),
"11": SubResource("Resource_d5o1r"),
"12": SubResource("Resource_g7mor"),
"13": SubResource("Resource_wgjgg"),
"14": SubResource("Resource_45hxc"),
"15": SubResource("Resource_c53vn"),
"16": SubResource("Resource_n6k7j")
}
metadata/_latest_layer = ""
+22
View File
@@ -0,0 +1,22 @@
@tool
extends EditorPlugin
func _enter_tree() -> void:
if !ProjectSettings.has_setting("babushka/hacks/speed_hack"):
ProjectSettings.set_setting("babushka/hacks/speed_hack",-1)
var property_info = {
"name": "babushka/hacks/speed_hack",
"type": TYPE_FLOAT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "-1,20,0.5"
}
ProjectSettings.add_property_info(property_info)
ProjectSettings.set_initial_value("babushka/hacks/speed_hack",-1)
func _exit_tree() -> void:
# Clean-up of the plugin goes here.
pass
@@ -0,0 +1 @@
uid://buwfplh0xji8q
+7
View File
@@ -0,0 +1,7 @@
[plugin]
name="BabushkaHelpers"
description=""
author="Cozy Raven"
version=""
script="babushkahelpers.gd"
+433
View File
@@ -0,0 +1,433 @@
class_name DialogicGameHandler
extends Node
## Class that is used as the Dialogic autoload.
## Autoload script that allows you to interact with all of Dialogic's systems:[br]
## - Holds all important information about the current state of Dialogic.[br]
## - Provides access to all the subsystems.[br]
## - Has methods to start/end timelines.[br]
## States indicating different phases of dialog.
enum States {
IDLE, ## Dialogic is awaiting input to advance.
REVEALING_TEXT, ## Dialogic is currently revealing text.
ANIMATING, ## Some animation is happening.
AWAITING_CHOICE, ## Dialogic awaits the selection of a choice
WAITING ## Dialogic is currently awaiting something.
}
## Flags indicating what to clear when calling [method clear].
enum ClearFlags {
FULL_CLEAR = 0, ## Clears all subsystems
KEEP_VARIABLES = 1, ## Clears all subsystems and info except for variables
TIMELINE_INFO_ONLY = 2 ## Doesn't clear subsystems but current timeline and index
}
## Reference to the currently executed timeline.
var current_timeline: DialogicTimeline = null
## Copy of the [member current_timeline]'s events.
var current_timeline_events: Array = []
## Index of the event the timeline handling is currently at.
var current_event_idx: int = 0
## Contains all information that subsystems consider relevant for
## the current situation
var current_state_info: Dictionary = {}
## Current state (see [member States] enum).
var current_state := States.IDLE:
get:
return current_state
set(new_state):
current_state = new_state
state_changed.emit(new_state)
## Emitted when [member current_state] change.
signal state_changed(new_state:States)
## When `true`, many dialogic processes won't continue until it's `false` again.
var paused := false:
set(value):
paused = value
if paused:
for subsystem in get_children():
if subsystem is DialogicSubsystem:
(subsystem as DialogicSubsystem).pause()
dialogic_paused.emit()
else:
for subsystem in get_children():
if subsystem is DialogicSubsystem:
(subsystem as DialogicSubsystem).resume()
dialogic_resumed.emit()
## Emitted when [member paused] changes to `true`.
signal dialogic_paused
## Emitted when [member paused] changes to `false`.
signal dialogic_resumed
## Emitted when the timeline ends.
## This can be a timeline ending or [method end_timeline] being called.
signal timeline_ended
## Emitted when a timeline starts by calling either [method start]
## or [method start_timeline].
signal timeline_started
## Emitted when an event starts being executed.
## The event may not have finished executing yet.
signal event_handled(resource: DialogicEvent)
## Emitted when a [class SignalEvent] event was reached.
signal signal_event(argument: Variant)
## Emitted when a signal event gets fired from a [class TextEvent] event.
signal text_signal(argument: String)
# Careful, this section is repopulated automatically at certain moments.
#region SUBSYSTEMS
var Audio := preload("res://addons/dialogic/Modules/Audio/subsystem_audio.gd").new():
get: return get_subsystem("Audio")
var Backgrounds := preload("res://addons/dialogic/Modules/Background/subsystem_backgrounds.gd").new():
get: return get_subsystem("Backgrounds")
var Portraits := preload("res://addons/dialogic/Modules/Character/subsystem_portraits.gd").new():
get: return get_subsystem("Portraits")
var PortraitContainers := preload("res://addons/dialogic/Modules/Character/subsystem_containers.gd").new():
get: return get_subsystem("PortraitContainers")
var Choices := preload("res://addons/dialogic/Modules/Choice/subsystem_choices.gd").new():
get: return get_subsystem("Choices")
var Expressions := preload("res://addons/dialogic/Modules/Core/subsystem_expression.gd").new():
get: return get_subsystem("Expressions")
var Animations := preload("res://addons/dialogic/Modules/Core/subsystem_animation.gd").new():
get: return get_subsystem("Animations")
var Inputs := preload("res://addons/dialogic/Modules/Core/subsystem_input.gd").new():
get: return get_subsystem("Inputs")
var Glossary := preload("res://addons/dialogic/Modules/Glossary/subsystem_glossary.gd").new():
get: return get_subsystem("Glossary")
var History := preload("res://addons/dialogic/Modules/History/subsystem_history.gd").new():
get: return get_subsystem("History")
var Jump := preload("res://addons/dialogic/Modules/Jump/subsystem_jump.gd").new():
get: return get_subsystem("Jump")
var Save := preload("res://addons/dialogic/Modules/Save/subsystem_save.gd").new():
get: return get_subsystem("Save")
var Settings := preload("res://addons/dialogic/Modules/Settings/subsystem_settings.gd").new():
get: return get_subsystem("Settings")
var Styles := preload("res://addons/dialogic/Modules/Style/subsystem_styles.gd").new():
get: return get_subsystem("Styles")
var Text := preload("res://addons/dialogic/Modules/Text/subsystem_text.gd").new():
get: return get_subsystem("Text")
var TextInput := preload("res://addons/dialogic/Modules/TextInput/subsystem_text_input.gd").new():
get: return get_subsystem("TextInput")
var VAR := preload("res://addons/dialogic/Modules/Variable/subsystem_variables.gd").new():
get: return get_subsystem("VAR")
var Voice := preload("res://addons/dialogic/Modules/Voice/subsystem_voice.gd").new():
get: return get_subsystem("Voice")
#endregion
## Autoloads are added first, so this happens REALLY early on game startup.
func _ready() -> void:
_collect_subsystems()
clear()
#region TIMELINE & EVENT HANDLING
################################################################################
## Method to start a timeline AND ensure that a layout scene is present.
## For argument info, checkout [method start_timeline].
## -> returns the layout node
func start(timeline:Variant, label:Variant="") -> Node:
# If we don't have a style subsystem, default to just start_timeline()
if not has_subsystem('Styles'):
printerr("[Dialogic] You called Dialogic.start() but the Styles subsystem is missing!")
clear(ClearFlags.KEEP_VARIABLES)
start_timeline(timeline, label)
return null
# Otherwise make sure there is a style active.
var scene: Node = null
if !self.Styles.has_active_layout_node():
scene = self.Styles.load_style()
else:
scene = self.Styles.get_layout_node()
scene.show()
if not scene.is_node_ready():
scene.ready.connect(clear.bind(ClearFlags.KEEP_VARIABLES))
scene.ready.connect(start_timeline.bind(timeline, label))
else:
start_timeline(timeline, label)
return scene
## Method to start a timeline without adding a layout scene.
## @timeline can be either a loaded timeline resource or a path to a timeline file.
## @label_or_idx can be a label (string) or index (int) to skip to immediatly.
func start_timeline(timeline:Variant, label_or_idx:Variant = "") -> void:
# load the resource if only the path is given
if typeof(timeline) == TYPE_STRING:
#check the lookup table if it's not a full file name
if (timeline as String).contains("res://"):
timeline = load((timeline as String))
else:
timeline = DialogicResourceUtil.get_timeline_resource((timeline as String))
if timeline == null:
printerr("[Dialogic] There was an error loading this timeline. Check the filename, and the timeline for errors")
return
(timeline as DialogicTimeline).process()
current_timeline = timeline
current_timeline_events = current_timeline.events
for event in current_timeline_events:
event.dialogic = self
current_event_idx = -1
if typeof(label_or_idx) == TYPE_STRING:
if label_or_idx:
if has_subsystem('Jump'):
Jump.jump_to_label((label_or_idx as String))
elif typeof(label_or_idx) == TYPE_INT:
if label_or_idx >-1:
current_event_idx = label_or_idx -1
timeline_started.emit()
handle_next_event()
## Preloader function, prepares a timeline and returns an object to hold for later
## [param timeline_resource] can be either a path (string) or a loaded timeline (resource)
func preload_timeline(timeline_resource:Variant) -> Variant:
# I think ideally this should be on a new thread, will test
if typeof(timeline_resource) == TYPE_STRING:
timeline_resource = load((timeline_resource as String))
if timeline_resource == null:
printerr("[Dialogic] There was an error preloading this timeline. Check the filename, and the timeline for errors")
return null
(timeline_resource as DialogicTimeline).process()
return timeline_resource
## Clears and stops the current timeline.
func end_timeline() -> void:
await clear(ClearFlags.TIMELINE_INFO_ONLY)
_on_timeline_ended()
timeline_ended.emit()
## Handles the next event.
func handle_next_event(_ignore_argument: Variant = "") -> void:
handle_event(current_event_idx+1)
## Handles the event at the given index [param event_index].
## You can call this manually, but if another event is still executing, it might have unexpected results.
func handle_event(event_index:int) -> void:
if not current_timeline:
return
_cleanup_previous_event()
if paused:
await dialogic_resumed
if event_index >= len(current_timeline_events):
end_timeline()
return
#actually process the event now, since we didnt earlier at runtime
#this needs to happen before we create the copy DialogicEvent variable, so it doesn't throw an error if not ready
if current_timeline_events[event_index].event_node_ready == false:
current_timeline_events[event_index]._load_from_string(current_timeline_events[event_index].event_node_as_text)
current_event_idx = event_index
if not current_timeline_events[event_index].event_finished.is_connected(handle_next_event):
current_timeline_events[event_index].event_finished.connect(handle_next_event)
set_meta('previous_event', current_timeline_events[event_index])
current_timeline_events[event_index].execute(self)
event_handled.emit(current_timeline_events[event_index])
## Resets Dialogic's state fully or partially.
## By using the clear flags from the [member ClearFlags] enum you can specify
## what info should be kept.
## For example, at timeline end usually it doesn't clear node or subsystem info.
func clear(clear_flags := ClearFlags.FULL_CLEAR) -> void:
_cleanup_previous_event()
if !clear_flags & ClearFlags.TIMELINE_INFO_ONLY:
for subsystem in get_children():
if subsystem is DialogicSubsystem:
(subsystem as DialogicSubsystem).clear_game_state(clear_flags)
var timeline := current_timeline
current_timeline = null
current_event_idx = -1
current_timeline_events = []
current_state = States.IDLE
# Resetting variables
if timeline:
await timeline.clean()
## Cleanup after previous event (if any).
func _cleanup_previous_event():
if has_meta('previous_event') and get_meta('previous_event') is DialogicEvent:
var event := get_meta('previous_event') as DialogicEvent
if event.event_finished.is_connected(handle_next_event):
event.event_finished.disconnect(handle_next_event)
event._clear_state()
remove_meta("previous_event")
#endregion
#region SAVING & LOADING
################################################################################
## Returns a dictionary containing all necessary information to later recreate the same state with load_full_state.
## The [subsystem Save] subsystem might be more useful for you.
## However, this can be used to integrate the info into your own save system.
func get_full_state() -> Dictionary:
if current_timeline:
current_state_info['current_event_idx'] = current_event_idx
current_state_info['current_timeline'] = current_timeline.resource_path
else:
current_state_info['current_event_idx'] = -1
current_state_info['current_timeline'] = null
for subsystem in get_children():
(subsystem as DialogicSubsystem).save_game_state()
return current_state_info.duplicate(true)
## This method tries to load the state from the given [param state_info].
## Will automatically start a timeline and add a layout if a timeline was running when
## the dictionary was retrieved with [method get_full_state].
func load_full_state(state_info:Dictionary) -> void:
clear()
current_state_info = state_info
## The Style subsystem needs to run first for others to load correctly.
var scene: Node = null
if has_subsystem('Styles'):
get_subsystem('Styles').load_game_state()
scene = self.Styles.get_layout_node()
var load_subsystems := func() -> void:
for subsystem in get_children():
if subsystem.name == 'Styles':
continue
(subsystem as DialogicSubsystem).load_game_state()
if null != scene and not scene.is_node_ready():
scene.ready.connect(load_subsystems)
else:
await get_tree().process_frame
load_subsystems.call()
if current_state_info.get('current_timeline', null):
start_timeline(current_state_info.current_timeline, current_state_info.get('current_event_idx', 0))
else:
end_timeline.call_deferred()
#endregion
#region SUB-SYTSEMS
################################################################################
func _collect_subsystems() -> void:
var subsystem_nodes := [] as Array[DialogicSubsystem]
for indexer in DialogicUtil.get_indexers():
for subsystem in indexer._get_subsystems():
var subsystem_node := add_subsystem(str(subsystem.name), str(subsystem.script))
subsystem_nodes.push_back(subsystem_node)
for subsystem in subsystem_nodes:
subsystem.post_install()
## Returns `true` if a subystem with the given [param subsystem_name] exists.
func has_subsystem(subsystem_name:String) -> bool:
return has_node(subsystem_name)
## Returns the subsystem node of the given [param subsystem_name] or null if it doesn't exist.
func get_subsystem(subsystem_name:String) -> DialogicSubsystem:
return get_node(subsystem_name)
## Adds a subsystem node with the given [param subsystem_name] and [param script_path].
func add_subsystem(subsystem_name:String, script_path:String) -> DialogicSubsystem:
var node: Node = Node.new()
node.name = subsystem_name
node.set_script(load(script_path))
node = node as DialogicSubsystem
node.dialogic = self
add_child(node)
return node
#endregion
#region HELPERS
################################################################################
## This handles the `Layout End Behaviour` setting that can be changed in the Dialogic settings.
func _on_timeline_ended() -> void:
if self.Styles.has_active_layout_node() and self.Styles.get_layout_node().is_inside_tree():
match ProjectSettings.get_setting('dialogic/layout/end_behaviour', 0):
0:
self.Styles.get_layout_node().get_parent().remove_child(self.Styles.get_layout_node())
self.Styles.get_layout_node().queue_free()
1:
@warning_ignore("unsafe_method_access")
self.Styles.get_layout_node().hide()
func print_debug_moment() -> void:
if not current_timeline:
return
printerr("\tAt event ", current_event_idx+1, " (",current_timeline_events[current_event_idx].event_name, ' Event) in timeline "', DialogicResourceUtil.get_unique_identifier(current_timeline.resource_path), '" (',current_timeline.resource_path,').')
print("\n")
#endregion
@@ -0,0 +1 @@
uid://c0ucrkws16k4i
@@ -0,0 +1,291 @@
@tool
class_name DialogicResourceUtil
static var label_cache := {}
static var event_cache: Array[DialogicEvent] = []
static var special_resources := {}
static func update() -> void:
update_directory('.dch')
update_directory('.dtl')
update_label_cache()
#region RESOURCE DIRECTORIES
################################################################################
static func get_directory(extension:String) -> Dictionary:
extension = extension.trim_prefix('.')
if Engine.has_meta(extension+'_directory'):
return Engine.get_meta(extension+'_directory', {})
var directory: Dictionary = ProjectSettings.get_setting("dialogic/directories/"+extension+'_directory', {})
Engine.set_meta(extension+'_directory', directory)
return directory
static func set_directory(extension:String, directory:Dictionary) -> void:
extension = extension.trim_prefix('.')
if Engine.is_editor_hint():
ProjectSettings.set_setting("dialogic/directories/"+extension+'_directory', directory)
ProjectSettings.save()
Engine.set_meta(extension+'_directory', directory)
static func update_directory(extension:String) -> void:
var directory := get_directory(extension)
for resource in list_resources_of_type(extension):
if not resource in directory.values():
directory = add_resource_to_directory(resource, directory)
var keys_to_remove := []
for key in directory:
if not ResourceLoader.exists(directory[key]):
keys_to_remove.append(key)
for key in keys_to_remove:
directory.erase(key)
set_directory(extension, directory)
static func add_resource_to_directory(file_path:String, directory:Dictionary) -> Dictionary:
var suggested_name := file_path.get_file().trim_suffix("."+file_path.get_extension())
while suggested_name in directory:
suggested_name = file_path.trim_suffix("/"+suggested_name+"."+file_path.get_extension()).get_file().path_join(suggested_name)
directory[suggested_name] = file_path
return directory
## Returns the unique identifier for the given resource path.
## Returns an empty string if no identifier was found.
static func get_unique_identifier(file_path:String) -> String:
var identifier: String = get_directory(file_path.get_extension()).find_key(file_path)
if typeof(identifier) == TYPE_STRING:
return identifier
return ""
## Returns the resource associated with the given unique identifier.
## The expected extension is needed to use the right directory.
static func get_resource_from_identifier(identifier:String, extension:String) -> Resource:
var path: String = get_directory(extension).get(identifier, '')
if ResourceLoader.exists(path):
return load(path)
return null
static func change_unique_identifier(file_path:String, new_identifier:String) -> void:
var directory := get_directory(file_path.get_extension())
var key: String = directory.find_key(file_path)
while key != null:
if key == new_identifier:
break
directory.erase(key)
directory[new_identifier] = file_path
key = directory.find_key(file_path)
set_directory(file_path.get_extension(), directory)
static func change_resource_path(old_path:String, new_path:String) -> void:
var directory := get_directory(new_path.get_extension())
var key: String = directory.find_key(old_path)
while key != null:
directory[key] = new_path
key = directory.find_key(old_path)
set_directory(new_path.get_extension(), directory)
static func remove_resource(file_path:String) -> void:
var directory := get_directory(file_path.get_extension())
var key: String = directory.find_key(file_path)
while key != null:
directory.erase(key)
key = directory.find_key(file_path)
set_directory(file_path.get_extension(), directory)
static func is_identifier_unused(extension:String, identifier:String) -> bool:
return not identifier in get_directory(extension)
#endregion
#region LABEL CACHE
################################################################################
# The label cache is only for the editor so we don't have to scan all timelines
# whenever we want to suggest labels. This has no use in game and is not always perfect.
static func get_label_cache() -> Dictionary:
if not label_cache.is_empty():
return label_cache
label_cache = DialogicUtil.get_editor_setting('label_ref', {})
return label_cache
static func set_label_cache(cache:Dictionary) -> void:
label_cache = cache
static func update_label_cache() -> void:
var cache := get_label_cache()
var timelines := get_timeline_directory().values()
for timeline in cache:
if !timeline in timelines:
cache.erase(timeline)
set_label_cache(cache)
#endregion
#region EVENT CACHE
################################################################################
## Dialogic keeps a list that has each event once. This allows retrieval of that list.
static func get_event_cache() -> Array:
if not event_cache.is_empty():
return event_cache
event_cache = update_event_cache()
return event_cache
static func update_event_cache() -> Array:
event_cache = []
for indexer in DialogicUtil.get_indexers():
# build event cache
for event in indexer._get_events():
if not ResourceLoader.exists(event):
continue
if not 'event_end_branch.gd' in event and not 'event_text.gd' in event:
event_cache.append(load(event).new())
# Events are checked in order while testing them. EndBranch needs to be first, Text needs to be last
event_cache.push_front(DialogicEndBranchEvent.new())
event_cache.push_back(DialogicTextEvent.new())
return event_cache
#endregion
#region SPECIAL RESOURCES
################################################################################
static func update_special_resources() -> void:
special_resources.clear()
for indexer in DialogicUtil.get_indexers():
var additions := indexer._get_special_resources()
for resource_type in additions:
if not resource_type in special_resources:
special_resources[resource_type] = {}
special_resources[resource_type].merge(additions[resource_type])
static func list_special_resources(type:String, filter := {}) -> Dictionary:
if special_resources.is_empty():
update_special_resources()
if type in special_resources:
if filter.is_empty():
return special_resources[type]
else:
var results := {}
for i in special_resources[type]:
if match_resource_filter(special_resources[type][i], filter):
results[i] = special_resources[type][i]
return results
return {}
static func match_resource_filter(dict:Dictionary, filter:Dictionary) -> bool:
for i in filter:
if not i in dict:
return false
if typeof(filter[i]) == TYPE_ARRAY:
if not dict[i] in filter[i]:
return false
else:
if not dict[i] == filter[i]:
return false
return true
static func guess_special_resource(type: String, string: String, default := {}, filter := {}, ignores:PackedStringArray=[]) -> Dictionary:
if string.is_empty():
return default
if special_resources.is_empty():
update_special_resources()
var resources := list_special_resources(type, filter)
if resources.is_empty():
printerr("[Dialogic] No ", type, "s found, but attempted to use one.")
return default
if string.begins_with('res://'):
for i in resources.values():
if i.path == string:
return i
printerr("[Dialogic] Unable to find ", type, " at path '", string, "'.")
return default
string = string.to_lower()
if string in resources:
return resources[string]
if not ignores.is_empty():
var regex := RegEx.create_from_string(r" ?\b(" + "|".join(ignores) + r")\b")
for name in resources:
if regex.sub(name, "") == regex.sub(string, ""):
return resources[name]
## As a last effort check against the unfiltered list
if string in special_resources[type]:
push_warning("[Dialogic] Using ", type, " '", string,"' when not supposed to.")
return special_resources[type][string]
printerr("[Dialogic] Unable to identify ", type, " based on string '", string, "'.")
return default
#endregion
#region HELPERS
################################################################################
static func get_character_directory() -> Dictionary:
return get_directory('dch')
static func get_timeline_directory() -> Dictionary:
return get_directory('dtl')
static func get_timeline_resource(timeline_identifier:String) -> DialogicTimeline:
return get_resource_from_identifier(timeline_identifier, 'dtl')
static func get_character_resource(character_identifier:String) -> DialogicCharacter:
return get_resource_from_identifier(character_identifier, 'dch')
static func list_resources_of_type(extension:String) -> Array:
var all_resources := scan_folder('res://', extension)
return all_resources
static func scan_folder(path:String, extension:String) -> Array:
var list: Array = []
if DirAccess.dir_exists_absolute(path):
var dir := DirAccess.open(path)
dir.list_dir_begin()
var file_name := dir.get_next()
while file_name != "":
if dir.current_is_dir() and not file_name.begins_with("."):
list += scan_folder(path.path_join(file_name), extension)
else:
if file_name.ends_with(extension):
list.append(path.path_join(file_name))
file_name = dir.get_next()
return list
#endregion
@@ -0,0 +1 @@
uid://cp8y6bxaigt7e
+676
View File
@@ -0,0 +1,676 @@
@tool
class_name DialogicUtil
## Script that container helper methods for both editor and game execution.
## Used whenever the same thing is needed in different parts of the plugin.
#region EDITOR
## This method should be used instead of EditorInterface.get_editor_scale(), because if you use that
## it will run perfectly fine from the editor, but crash when the game is exported.
static func get_editor_scale() -> float:
return get_dialogic_plugin().get_editor_interface().get_editor_scale()
## Although this does in fact always return a EditorPlugin node,
## that class is apparently not present in export and referencing it here creates a crash.
static func get_dialogic_plugin() -> Node:
for child in Engine.get_main_loop().get_root().get_children():
if child.get_class() == "EditorNode":
return child.get_node('DialogicPlugin')
return null
#endregion
## Returns the autoload when in-game.
static func autoload() -> DialogicGameHandler:
if Engine.is_editor_hint():
return null
if not Engine.get_main_loop().root.has_node("Dialogic"):
return null
return Engine.get_main_loop().root.get_node("Dialogic")
#region FILE SYSTEM
################################################################################
static func listdir(path: String, files_only:= true, _throw_error:= true, full_file_path:= false, include_imports := false) -> Array:
var files: Array = []
if path.is_empty(): path = "res://"
if DirAccess.dir_exists_absolute(path):
var dir := DirAccess.open(path)
dir.list_dir_begin()
var file_name := dir.get_next()
while file_name != "":
if not file_name.begins_with("."):
if files_only:
if not dir.current_is_dir() and (not file_name.ends_with('.import') or include_imports):
if full_file_path:
files.append(path.path_join(file_name))
else:
files.append(file_name)
else:
if full_file_path:
files.append(path.path_join(file_name))
else:
files.append(file_name)
file_name = dir.get_next()
dir.list_dir_end()
return files
static func get_module_path(name:String, builtin:=true) -> String:
if builtin:
return "res://addons/dialogic/Modules".path_join(name)
else:
return ProjectSettings.get_setting('dialogic/extensions_folder', 'res://addons/dialogic_additions').path_join(name)
## This is a private and editor-only function.
##
## Populates the [class DialogicGameHandler] with new custom subsystems by
## directly manipulating the file's content and then importing the file.
static func _update_autoload_subsystem_access() -> void:
if not Engine.is_editor_hint():
printerr("[Dialogic] This function is only available in the editor.")
return
var script: Script = load("res://addons/dialogic/Core/DialogicGameHandler.gd")
var new_subsystem_access_list := "#region SUBSYSTEMS\n"
for indexer: DialogicIndexer in get_indexers(true, true):
for subsystem: Dictionary in indexer._get_subsystems().duplicate(true):
new_subsystem_access_list += '\nvar {name} := preload("{script}").new():\n\tget: return get_subsystem("{name}")\n'.format(subsystem)
new_subsystem_access_list += "\n#endregion"
script.source_code = RegEx.create_from_string(r"#region SUBSYSTEMS\n#*\n((?!#endregion)(.*\n))*#endregion").sub(script.source_code, new_subsystem_access_list)
ResourceSaver.save(script)
Engine.get_singleton("EditorInterface").get_resource_filesystem().reimport_files(["res://addons/dialogic/Core/DialogicGameHandler.gd"])
static func get_indexers(include_custom := true, force_reload := false) -> Array[DialogicIndexer]:
if Engine.get_main_loop().has_meta('dialogic_indexers') and !force_reload:
return Engine.get_main_loop().get_meta('dialogic_indexers')
var indexers: Array[DialogicIndexer] = []
for file in listdir(DialogicUtil.get_module_path(''), false):
var possible_script: String = DialogicUtil.get_module_path(file).path_join("index.gd")
if ResourceLoader.exists(possible_script):
indexers.append(load(possible_script).new())
if include_custom:
var extensions_folder: String = ProjectSettings.get_setting('dialogic/extensions_folder', "res://addons/dialogic_additions/")
for file in listdir(extensions_folder, false, false):
var possible_script: String = extensions_folder.path_join(file + "/index.gd")
if ResourceLoader.exists(possible_script):
indexers.append(load(possible_script).new())
Engine.get_main_loop().set_meta('dialogic_indexers', indexers)
return indexers
## Turns a [param file_path] from `some_file.png` to `Some File`.
static func pretty_name(file_path: String) -> String:
var _name := file_path.get_file().trim_suffix("." + file_path.get_extension())
_name = _name.replace('_', ' ')
_name = _name.capitalize()
return _name
#endregion
#region EDITOR SETTINGS & COLORS
################################################################################
static func set_editor_setting(setting:String, value:Variant) -> void:
var cfg := ConfigFile.new()
if FileAccess.file_exists('user://dialogic/editor_settings.cfg'):
cfg.load('user://dialogic/editor_settings.cfg')
cfg.set_value('DES', setting, value)
if !DirAccess.dir_exists_absolute('user://dialogic'):
DirAccess.make_dir_absolute('user://dialogic')
cfg.save('user://dialogic/editor_settings.cfg')
static func get_editor_setting(setting:String, default:Variant=null) -> Variant:
var cfg := ConfigFile.new()
if !FileAccess.file_exists('user://dialogic/editor_settings.cfg'):
return default
if !cfg.load('user://dialogic/editor_settings.cfg') == OK:
return default
return cfg.get_value('DES', setting, default)
static func get_color_palette(default:bool = false) -> Dictionary:
var defaults := {
'Color1': Color('#3b8bf2'), # Blue
'Color2': Color('#00b15f'), # Green
'Color3': Color('#e868e2'), # Pink
'Color4': Color('#9468e8'), # Purple
'Color5': Color('#574fb0'), # DarkPurple
'Color6': Color('#1fa3a3'), # Aquamarine
'Color7': Color('#fa952a'), # Orange
'Color8': Color('#de5c5c'), # Red
'Color9': Color('#7c7c7c'), # Gray
}
if default:
return defaults
return get_editor_setting('color_palette', defaults)
static func get_color(value:String) -> Color:
var colors := get_color_palette()
return colors[value]
#endregion
#region TIMER PROCESS MODE
################################################################################
static func is_physics_timer() -> bool:
return ProjectSettings.get_setting('dialogic/timer/process_in_physics', false)
static func update_timer_process_callback(timer:Timer) -> void:
timer.process_callback = Timer.TIMER_PROCESS_PHYSICS if is_physics_timer() else Timer.TIMER_PROCESS_IDLE
#endregion
#region MULTITWEEN
################################################################################
static func multitween(tweened_value:Variant, item:Node, property:String, part:String) -> void:
var parts: Dictionary = item.get_meta(property+'_parts', {})
parts[part] = tweened_value
if not item.has_meta(property+'_base_value') and not 'base' in parts:
item.set_meta(property+'_base_value', item.get(property))
var final_value: Variant = parts.get('base', item.get_meta(property+'_base_value', item.get(property)))
for key in parts:
if key == 'base':
continue
else:
final_value += parts[key]
item.set(property, final_value)
item.set_meta(property+'_parts', parts)
#endregion
#region TRANSLATIONS
################################################################################
static func get_next_translation_id() -> String:
ProjectSettings.set_setting('dialogic/translation/id_counter', ProjectSettings.get_setting('dialogic/translation/id_counter', 16)+1)
return '%x' % ProjectSettings.get_setting('dialogic/translation/id_counter', 16)
#endregion
#region VARIABLES
################################################################################
enum VarTypes {ANY, STRING, FLOAT, INT, BOOL}
static func get_default_variables() -> Dictionary:
return ProjectSettings.get_setting('dialogic/variables', {})
# helper that converts a nested variable dictionary into an array with paths
static func list_variables(dict:Dictionary, path := "", type:=VarTypes.ANY) -> Array:
var array := []
for key in dict.keys():
if typeof(dict[key]) == TYPE_DICTIONARY:
array.append_array(list_variables(dict[key], path+key+".", type))
else:
if type == VarTypes.ANY or get_variable_value_type(dict[key]) == type:
array.append(path+key)
return array
static func get_variable_value_type(value:Variant) -> VarTypes:
match typeof(value):
TYPE_STRING:
return VarTypes.STRING
TYPE_FLOAT:
return VarTypes.FLOAT
TYPE_INT:
return VarTypes.INT
TYPE_BOOL:
return VarTypes.BOOL
return VarTypes.ANY
static func get_variable_type(path:String, dict:Dictionary={}) -> VarTypes:
if dict.is_empty():
dict = get_default_variables()
return get_variable_value_type(_get_value_in_dictionary(path, dict))
## This will set a value in a dictionary (or a sub-dictionary based on the path)
## e.g. it could set "Something.Something.Something" in {'Something':{'Something':{'Someting':"value"}}}
static func _set_value_in_dictionary(path:String, dictionary:Dictionary, value):
if '.' in path:
var from := path.split('.')[0]
if from in dictionary.keys():
dictionary[from] = _set_value_in_dictionary(path.trim_prefix(from+"."), dictionary[from], value)
else:
if path in dictionary.keys():
dictionary[path] = value
return dictionary
## This will get a value in a dictionary (or a sub-dictionary based on the path)
## e.g. it could get "Something.Something.Something" in {'Something':{'Something':{'Someting':"value"}}}
static func _get_value_in_dictionary(path:String, dictionary:Dictionary, default= null) -> Variant:
if '.' in path:
var from := path.split('.')[0]
if from in dictionary.keys():
return _get_value_in_dictionary(path.trim_prefix(from+"."), dictionary[from], default)
else:
if path in dictionary.keys():
return dictionary[path]
return default
#endregion
#region STYLES
################################################################################
static func get_default_layout_base() -> PackedScene:
return load(DialogicUtil.get_module_path('DefaultLayoutParts').path_join("Base_Default/default_layout_base.tscn"))
static func get_fallback_style() -> DialogicStyle:
return load(DialogicUtil.get_module_path('DefaultLayoutParts').path_join("Style_VN_Default/default_vn_style.tres"))
static func get_default_style() -> DialogicStyle:
var default: String = ProjectSettings.get_setting('dialogic/layout/default_style', '')
if !ResourceLoader.exists(default):
return get_fallback_style()
return load(default)
static func get_style_by_name(name:String) -> DialogicStyle:
if name.is_empty():
return get_default_style()
var styles: Array = ProjectSettings.get_setting('dialogic/layout/style_list', [])
for style in styles:
if not ResourceLoader.exists(style):
continue
if load(style).name == name:
return load(style)
return get_default_style()
#endregion
#region SCENE EXPORT OVERRIDES
################################################################################
static func apply_scene_export_overrides(node:Node, export_overrides:Dictionary, apply := true) -> void:
var default_info := get_scene_export_defaults(node)
if !node.script:
return
var property_info: Array[Dictionary] = node.script.get_script_property_list()
for i in property_info:
if i['usage'] & PROPERTY_USAGE_EDITOR:
if i['name'] in export_overrides:
if str_to_var(export_overrides[i['name']]) == null and typeof(node.get(i['name'])) == TYPE_STRING:
node.set(i['name'], export_overrides[i['name']])
else:
node.set(i['name'], str_to_var(export_overrides[i['name']]))
elif i['name'] in default_info:
node.set(i['name'], default_info.get(i['name']))
if apply:
if node.has_method('apply_export_overrides'):
node.apply_export_overrides()
static func get_scene_export_defaults(node:Node) -> Dictionary:
if !node.script:
return {}
if Engine.get_main_loop().has_meta('dialogic_scene_export_defaults') and \
node.script.resource_path in Engine.get_main_loop().get_meta('dialogic_scene_export_defaults'):
return Engine.get_main_loop().get_meta('dialogic_scene_export_defaults')[node.script.resource_path]
if !Engine.get_main_loop().has_meta('dialogic_scene_export_defaults'):
Engine.get_main_loop().set_meta('dialogic_scene_export_defaults', {})
var defaults := {}
var property_info: Array[Dictionary] = node.script.get_script_property_list()
for i in property_info:
if i['usage'] & PROPERTY_USAGE_EDITOR:
defaults[i['name']] = node.get(i['name'])
Engine.get_main_loop().get_meta('dialogic_scene_export_defaults')[node.script.resource_path] = defaults
return defaults
#endregion
#region MAKE CUSTOM
static func make_file_custom(original_file:String, target_folder:String, new_file_name := "", new_folder_name := "") -> String:
if not ResourceLoader.exists(original_file):
push_error("[Dialogic] Unable to make file with invalid path custom!")
return ""
if new_folder_name:
target_folder = target_folder.path_join(new_folder_name)
DirAccess.make_dir_absolute(target_folder)
if new_file_name.is_empty():
new_file_name = "custom_" + original_file.get_file()
if not new_file_name.ends_with(original_file.get_extension()):
new_file_name += "." + original_file.get_extension()
var target_file := target_folder.path_join(new_file_name)
customize_file(original_file, target_file)
get_dialogic_plugin().get_editor_interface().get_resource_filesystem().scan_sources()
return target_file
static func customize_file(original_file:String, target_file:String) -> String:
#print("\nCUSTOMIZE FILE")
#printt(original_file, "->", target_file)
DirAccess.copy_absolute(original_file, target_file)
var file := FileAccess.open(target_file, FileAccess.READ)
var file_text := file.get_as_text()
file.close()
# If we are customizing a scene, we check for any resources used in that scene that are in the same folder.
# Those will be copied as well and the scene will be modified to point to them.
if file_text.begins_with('[gd_'):
var base_path: String = original_file.get_base_dir()
var remove_uuid_regex := r'\[gd_.* (?<uid>uid="uid:[^"]*")'
var result := RegEx.create_from_string(remove_uuid_regex).search(file_text)
if result:
file_text = file_text.replace(result.get_string("uid"), "")
# This regex also removes the UID referencing the original resource
var file_regex := r'(uid="[^"]*" )?\Qpath="'+base_path+r'\E(?<file>[^"]*)"'
result = RegEx.create_from_string(file_regex).search(file_text)
while result:
var found_file_name := result.get_string('file')
var found_file_path := base_path.path_join(found_file_name)
var target_file_path := target_file.get_base_dir().path_join(found_file_name)
# Files found in this file will ALSO be customized.
customize_file(found_file_path, target_file_path)
file_text = file_text.replace(found_file_path, target_file_path)
result = RegEx.create_from_string(file_regex).search(file_text)
file = FileAccess.open(target_file, FileAccess.WRITE)
file.store_string(file_text)
file.close()
return target_file
#endregion
#region INSPECTOR FIELDS
################################################################################
static func setup_script_property_edit_node(property_info: Dictionary, value:Variant, property_changed:Callable) -> Control:
var input: Control = null
match property_info['type']:
TYPE_BOOL:
input = CheckBox.new()
if value != null:
input.button_pressed = value
input.toggled.connect(DialogicUtil._on_export_bool_submitted.bind(property_info.name, property_changed))
TYPE_COLOR:
input = ColorPickerButton.new()
if value != null:
input.color = value
input.color_changed.connect(DialogicUtil._on_export_color_submitted.bind(property_info.name, property_changed))
input.custom_minimum_size.x = get_editor_scale() * 50
TYPE_INT:
if property_info['hint'] & PROPERTY_HINT_ENUM:
input = OptionButton.new()
for x in property_info['hint_string'].split(','):
input.add_item(x.split(':')[0])
if value != null:
input.select(value)
input.item_selected.connect(DialogicUtil._on_export_int_enum_submitted.bind(property_info.name, property_changed))
else:
input = SpinBox.new()
input.value_changed.connect(DialogicUtil._on_export_number_submitted.bind(property_info.name, property_changed))
if property_info.hint_string == 'int':
input.step = 1
input.allow_greater = true
input.allow_lesser = true
elif ',' in property_info.hint_string:
input.min_value = int(property_info.hint_string.get_slice(',', 0))
input.max_value = int(property_info.hint_string.get_slice(',', 1))
if property_info.hint_string.count(',') > 1:
input.step = int(property_info.hint_string.get_slice(',', 2))
if value != null:
input.value = value
TYPE_FLOAT:
input = SpinBox.new()
input.step = 0.01
if ',' in property_info.hint_string:
input.min_value = float(property_info.hint_string.get_slice(',', 0))
input.max_value = float(property_info.hint_string.get_slice(',', 1))
if property_info.hint_string.count(',') > 1:
input.step = float(property_info.hint_string.get_slice(',', 2))
input.value_changed.connect(DialogicUtil._on_export_number_submitted.bind(property_info.name, property_changed))
if value != null:
input.value = value
TYPE_VECTOR2, TYPE_VECTOR3, TYPE_VECTOR4:
var vectorSize: String = type_string(typeof(value))[-1]
input = load("res://addons/dialogic/Editor/Events/Fields/field_vector" + vectorSize + ".tscn").instantiate()
input.property_name = property_info['name']
input.set_value(value)
input.value_changed.connect(DialogicUtil._on_export_vector_submitted.bind(property_changed))
TYPE_STRING:
if property_info['hint'] & PROPERTY_HINT_FILE or property_info['hint'] & PROPERTY_HINT_DIR:
input = load("res://addons/dialogic/Editor/Events/Fields/field_file.tscn").instantiate()
input.file_filter = property_info['hint_string']
input.file_mode = FileDialog.FILE_MODE_OPEN_FILE
if property_info['hint'] == PROPERTY_HINT_DIR:
input.file_mode = FileDialog.FILE_MODE_OPEN_DIR
input.property_name = property_info['name']
input.placeholder = "Default"
input.hide_reset = true
if value != null:
input.set_value(value)
input.value_changed.connect(DialogicUtil._on_export_file_submitted.bind(property_changed))
elif property_info['hint'] & PROPERTY_HINT_ENUM:
input = OptionButton.new()
var options: PackedStringArray = []
for x in property_info['hint_string'].split(','):
options.append(x.split(':')[0].strip_edges())
input.add_item(options[-1])
if value != null:
input.select(options.find(value))
input.item_selected.connect(DialogicUtil._on_export_string_enum_submitted.bind(property_info.name, options, property_changed))
else:
input = LineEdit.new()
if value != null:
input.text = value
input.text_submitted.connect(DialogicUtil._on_export_input_text_submitted.bind(property_info.name, property_changed))
TYPE_DICTIONARY:
input = load("res://addons/dialogic/Editor/Events/Fields/field_dictionary.tscn").instantiate()
input.property_name = property_info["name"]
input.value_changed.connect(_on_export_dict_submitted.bind(property_changed))
TYPE_OBJECT:
input = load("res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn").instantiate()
input.hint_text = "Objects/Resources as settings are currently not supported. \nUse @export_file('*.extension') instead and load the resource once needed."
_:
input = LineEdit.new()
if value != null:
input.text = value
input.text_submitted.connect(_on_export_input_text_submitted.bind(property_info.name, property_changed))
return input
static func _on_export_input_text_submitted(text:String, property_name:String, callable: Callable) -> void:
callable.call(property_name, var_to_str(text))
static func _on_export_bool_submitted(value:bool, property_name:String, callable: Callable) -> void:
callable.call(property_name, var_to_str(value))
static func _on_export_color_submitted(color:Color, property_name:String, callable: Callable) -> void:
callable.call(property_name, var_to_str(color))
static func _on_export_int_enum_submitted(item:int, property_name:String, callable: Callable) -> void:
callable.call(property_name, var_to_str(item))
static func _on_export_number_submitted(value:float, property_name:String, callable: Callable) -> void:
callable.call(property_name, var_to_str(value))
static func _on_export_file_submitted(property_name:String, value:String, callable: Callable) -> void:
callable.call(property_name, var_to_str(value))
static func _on_export_string_enum_submitted(value:int, property_name:String, list:PackedStringArray, callable: Callable):
callable.call(property_name, var_to_str(list[value]))
static func _on_export_vector_submitted(property_name:String, value:Variant, callable: Callable) -> void:
callable.call(property_name, var_to_str(value))
static func _on_export_dict_submitted(property_name:String, value:Variant, callable: Callable) -> void:
callable.call(property_name, var_to_str(value))
#endregion
#region EVENT DEFAULTS
################################################################################
static func get_custom_event_defaults(event_name:String) -> Dictionary:
if Engine.is_editor_hint():
return ProjectSettings.get_setting('dialogic/event_default_overrides', {}).get(event_name, {})
else:
if !Engine.get_main_loop().has_meta('dialogic_event_defaults'):
Engine.get_main_loop().set_meta('dialogic_event_defaults', ProjectSettings.get_setting('dialogic/event_default_overrides', {}))
return Engine.get_main_loop().get_meta('dialogic_event_defaults').get(event_name, {})
#endregion
#region CONVERSION
################################################################################
static func str_to_bool(boolstring:String) -> bool:
return true if boolstring == "true" else false
static func logical_convert(value:Variant) -> Variant:
if typeof(value) == TYPE_STRING:
if value.is_valid_int():
return value.to_int()
if value.is_valid_float():
return value.to_float()
if value == 'true':
return true
if value == 'false':
return false
return value
## Takes [param source] and builds a dictionary of keys only.
## The values are `null`.
static func str_to_hash_set(source: String) -> Dictionary:
var dictionary := Dictionary()
for character in source:
dictionary[character] = null
return dictionary
#endregion
static func get_character_suggestions(_search_text:String, current_value:DialogicCharacter = null, allow_none := true, allow_all:= false, editor_node:Node = null) -> Dictionary:
var suggestions := {}
var icon := load("res://addons/dialogic/Editor/Images/Resources/character.svg")
if allow_none and current_value:
suggestions['(No one)'] = {'value':'', 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
if allow_all:
suggestions['ALL'] = {'value':'--All--', 'tooltip':'All currently joined characters leave', 'editor_icon':["GuiEllipsis", "EditorIcons"]}
# Get characters in the current timeline and place them at the top of suggestions.
if editor_node:
var recent_characters := []
var timeline_node := editor_node.get_parent().find_parent("Timeline") as DialogicEditor
for event_node in timeline_node.find_child("Timeline").get_children():
if event_node == editor_node:
break
if event_node.resource is DialogicCharacterEvent or event_node.resource is DialogicTextEvent:
recent_characters.append(event_node.resource.character)
recent_characters.reverse()
for character in recent_characters:
if character and not character.get_character_name() in suggestions:
suggestions[character.get_character_name()] = {'value': character.get_character_name(), 'tooltip': character.resource_path, 'icon': icon.duplicate()}
var character_directory := DialogicResourceUtil.get_character_directory()
for resource in character_directory.keys():
suggestions[resource] = {'value': resource, 'tooltip': character_directory[resource], 'icon': icon}
return suggestions
static func get_portrait_suggestions(search_text:String, character:DialogicCharacter, allow_empty := false, empty_text := "Don't Change") -> Dictionary:
var icon := load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")
var suggestions := {}
if allow_empty:
suggestions[empty_text] = {'value':'', 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
if "{" in search_text:
suggestions[search_text] = {'value':search_text, 'editor_icon':["Variant", "EditorIcons"]}
if character != null:
for portrait in character.portraits:
suggestions[portrait] = {'value':portrait, 'icon':icon}
return suggestions
static func get_portrait_position_suggestions(search_text := "") -> Dictionary:
var icon := load(DialogicUtil.get_module_path("Character").path_join('portrait_position.svg'))
var setting: String = ProjectSettings.get_setting('dialogic/portraits/position_suggestion_names', 'leftmost, left, center, right, rightmost')
var suggestions := {}
if not search_text.is_empty():
suggestions[search_text] = {'value':search_text.strip_edges(), 'editor_icon':["GuiScrollArrowRight", "EditorIcons"]}
for position_id in setting.split(','):
suggestions[position_id.strip_edges()] = {'value':position_id.strip_edges(), 'icon':icon}
if not search_text.is_empty() and position_id.strip_edges().begins_with(search_text):
suggestions.erase(search_text)
return suggestions
+1
View File
@@ -0,0 +1 @@
uid://bomsqbbajkvag
@@ -0,0 +1,41 @@
class_name DialogicSubsystem
extends Node
var dialogic: DialogicGameHandler = null
enum LoadFlags {FULL_LOAD, ONLY_DNODES}
# To be overriden by sub-classes
# Called once after every subsystem has been added to the tree
func post_install() -> void:
pass
# To be overriden by sub-classes
# Fill in everything that should be cleared (for example before loading a different state)
func clear_game_state(_clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
pass
# To be overriden by sub-classes
# Fill in everything that should be loaded using the dialogic_game_handler.current_state_info
# This is called when a save is loaded
func load_game_state(_load_flag:=LoadFlags.FULL_LOAD) -> void:
pass
# To be overriden by sub-classes
# Fill in everything that should be saved into the dialogic_game_handler.current_state_info
# This is called when a save is saved
func save_game_state() -> void:
pass
# To be overriden by sub-classes
func pause() -> void:
pass
# To be overriden by sub-classes
func resume() -> void:
pass
@@ -0,0 +1 @@
uid://vqjpqkl5rmom
+152
View File
@@ -0,0 +1,152 @@
@tool
class_name DialogicIndexer
extends RefCounted
## Script that indexes events, subsystems, settings pages and more. [br]
## Place a script of this type in every folder in "addons/Events". [br]
## Overwrite the methods to return the contents of that folder.
var this_folder: String = get_script().resource_path.get_base_dir()
## Overwrite if this module contains any events. [br]
## Return an array with all the paths to the event scripts.[br]
## You can use the [property this_folder].path_join('my_event.gd')
func _get_events() -> Array:
if ResourceLoader.exists(this_folder.path_join('event.gd')):
return [this_folder.path_join('event.gd')]
return []
## Overwrite if this module contains any subsystems.
## Should return an array of dictionaries each with the following keys: [br]
## "name" -> name for this subsystem[br]
## "script" -> array of preview images[br]
func _get_subsystems() -> Array[Dictionary]:
return []
func _get_editors() -> Array[String]:
return []
func _get_settings_pages() -> Array:
return []
func _get_character_editor_sections() -> Array:
return []
#region TEXT EFFECTS & MODIFIERS
## Should return array of dictionaries with the following keys:[br]
## "command" -> the text e.g. "speed"[br]
## "node_path" or "subsystem" -> whichever contains your effect method[br]
## "method" -> name of the effect method[br]
func _get_text_effects() -> Array[Dictionary]:
return []
## Should return array of dictionaries with the same arguments as _get_text_effects()
func _get_text_modifiers() -> Array[Dictionary]:
return []
#endregion
## Return a list of resources, scripts, etc.
## These can later be retrieved with DialogicResourceUtil.
## Each dictionary should contain (at least "type" and "path").
## E.g. {"type":"Animation", "path": "res://..."}
func _get_special_resources() -> Dictionary:
return {}
## Return a list of dictionaries, each
func _get_portrait_scene_presets() -> Array[Dictionary]:
return []
#region HELPERS
################################################################################
func list_dir(subdir:='') -> Array:
return Array(DirAccess.get_files_at(this_folder.path_join(subdir))).map(func(file):return this_folder.path_join(subdir).path_join(file))
func list_special_resources(subdir:='', extension:="") -> Dictionary:
var dict := {}
for i in list_dir(subdir):
if extension.is_empty() or i.ends_with(extension):
dict[DialogicUtil.pretty_name(i).to_lower()] = {"path":i}
return dict
func list_animations(subdir := "") -> Dictionary:
var full_animation_list := {}
for path in list_dir(subdir):
if not path.ends_with(".gd") and not path.ends_with(".gdc"):
continue
var anim_object: DialogicAnimation = load(path).new()
var versions := anim_object._get_named_variations()
for version_name in versions:
full_animation_list[version_name] = versions[version_name]
full_animation_list[version_name]["path"] = path
anim_object.queue_free()
return full_animation_list
#endregion
#region STYLES & LAYOUTS
################################################################################
func _get_style_presets() -> Array[Dictionary]:
return []
## Should return an array of dictionaries with the following keys:[br]
## "path" -> the path to the scene[br]
## "name" -> name for this layout[br]
## "description"-> description of this layout. list what features/events are supported[br]
## "preview_image"-> array of preview images[br]
func _get_layout_parts() -> Array[Dictionary]:
return []
## Helper that allows scanning sub directories that might be layout parts or styles
func scan_for_layout_parts() -> Array[Dictionary]:
var dir := DirAccess.open(this_folder)
var style_list: Array[Dictionary] = []
if !dir:
return style_list
dir.list_dir_begin()
var dir_name := dir.get_next()
while dir_name != "":
if !dir.current_is_dir() or !dir.file_exists(dir_name.path_join('part_config.cfg')):
dir_name = dir.get_next()
continue
var config := ConfigFile.new()
config.load(this_folder.path_join(dir_name).path_join('part_config.cfg'))
var default_image_path: String = this_folder.path_join(dir_name).path_join('preview.png')
style_list.append(
{
'type': config.get_value('style', 'type', 'Unknown type'),
'name': config.get_value('style', 'name', 'Unnamed Layout'),
'path': this_folder.path_join(dir_name).path_join(config.get_value('style', 'scene', '')),
'author': config.get_value('style', 'author', 'Anonymous'),
'description': config.get_value('style', 'description', 'No description'),
'preview_image': [config.get_value('style', 'image', default_image_path)],
'style_path':config.get_value('style', 'style_path', ''),
'icon':this_folder.path_join(dir_name).path_join(config.get_value('style', 'icon', '')),
})
if not style_list[-1].style_path.begins_with('res://'):
style_list[-1].style_path = this_folder.path_join(dir_name).path_join(style_list[-1].style_path)
dir_name = dir.get_next()
return style_list
#endregion
+1
View File
@@ -0,0 +1 @@
uid://qn2wgcosejiw
@@ -0,0 +1,91 @@
@tool
extends DialogicCharacterEditorPortraitSection
## Section that allows setting values of exported scene variables
## for custom portrait scenes
var current_portrait_data := {}
var last_scene := ""
func _get_title() -> String:
return "Settings"
func _load_portrait_data(data:Dictionary) -> void:
_recheck(data, true)
## Recheck section visibility and reload export fields.
## This allows reacting to changes of the portrait_scene setting.
func _recheck(data: Dictionary, force:=false):
if last_scene == data.get("scene", "") and not force:
current_portrait_data = data
last_scene = data.get("scene", "")
return
last_scene = data.get("scene", "")
current_portrait_data = data
for child in $Grid.get_children():
child.get_parent().remove_child(child)
child.queue_free()
var scene: Variant = null
if current_portrait_data.get('scene', '').is_empty():
if ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
scene = load(character_editor.def_portrait_path)
else:
scene = load(ProjectSettings.get_setting('dialogic/portraits/default_portrait', ''))
else:
scene = load(current_portrait_data.get('scene'))
if not scene:
return
scene = scene.instantiate()
var skip := false
for i in scene.script.get_script_property_list():
if i['usage'] & PROPERTY_USAGE_EDITOR and !skip:
var label := Label.new()
label.text = i['name'].capitalize()
$Grid.add_child(label)
var current_value: Variant = scene.get(i['name'])
if current_portrait_data.has('export_overrides') and current_portrait_data['export_overrides'].has(i['name']):
current_value = str_to_var(current_portrait_data.export_overrides[i['name']])
if current_value == null and typeof(scene.get(i['name'])) == TYPE_STRING:
current_value = current_portrait_data['export_overrides'][i['name']]
var input: Node = DialogicUtil.setup_script_property_edit_node(i, current_value, set_export_override)
input.size_flags_horizontal = SIZE_EXPAND_FILL
$Grid.add_child(input)
if i['usage'] & PROPERTY_USAGE_GROUP:
if i['name'] == 'Main' or i["name"] == "Private":
skip = true
continue
else:
skip = false
if $Grid.get_child_count():
get_parent().get_child(get_index()-1).show()
show()
else:
hide()
get_parent().get_child(get_index()-1).hide()
get_parent().get_child(get_index()+1).hide()
## On any change, save the export override to the portrait items metadata.
func set_export_override(property_name:String, value:String = "") -> void:
var data: Dictionary = selected_item.get_metadata(0)
if !data.has('export_overrides'):
data['export_overrides'] = {}
if !value.is_empty():
data.export_overrides[property_name] = value
else:
data.export_overrides.erase(property_name)
changed.emit()
update_preview.emit()
@@ -0,0 +1 @@
uid://bipo5hdfv3epn
@@ -0,0 +1,16 @@
[gd_scene load_steps=2 format=3 uid="uid://cfcs7lb6gqnmd"]
[ext_resource type="Script" uid="uid://bipo5hdfv3epn" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.gd" id="1_isys8"]
[node name="Settings" type="VBoxContainer"]
custom_minimum_size = Vector2(0, 35)
offset_right = 367.0
offset_bottom = 82.0
script = ExtResource("1_isys8")
[node name="Grid" type="GridContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/h_separation = 10
columns = 2
@@ -0,0 +1,44 @@
@tool
extends DialogicCharacterEditorPortraitSection
## Tab that allows setting size, offset and mirror of a portrait.
func _get_title() -> String:
return "Scale, Offset & Mirror"
func _load_portrait_data(data:Dictionary) -> void:
%IgnoreScale.set_pressed_no_signal(data.get('ignore_char_scale', false))
%PortraitScale.value = data.get('scale', 1.0)*100
%PortraitOffset.set_value(data.get('offset', Vector2()))
%PortraitOffset._load_display_info({'step':1})
%PortraitMirror.set_pressed_no_signal(data.get('mirror', false))
func _on_portrait_scale_value_changed(value:float) -> void:
var data: Dictionary = selected_item.get_metadata(0)
data['scale'] = value/100.0
update_preview.emit()
changed.emit()
func _on_portrait_mirror_toggled(button_pressed:bool)-> void:
var data: Dictionary = selected_item.get_metadata(0)
data['mirror'] = button_pressed
update_preview.emit()
changed.emit()
func _on_ignore_scale_toggled(button_pressed:bool) -> void:
var data: Dictionary = selected_item.get_metadata(0)
data['ignore_char_scale'] = button_pressed
update_preview.emit()
changed.emit()
func _on_portrait_offset_value_changed(property:String, value:Vector2) -> void:
var data: Dictionary = selected_item.get_metadata(0)
data['offset'] = value
update_preview.emit()
changed.emit()
@@ -0,0 +1 @@
uid://we5rp2oyvypl
@@ -0,0 +1,64 @@
[gd_scene load_steps=3 format=3 uid="uid://crke8suvv52c6"]
[ext_resource type="Script" uid="uid://we5rp2oyvypl" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.gd" id="1_76vf2"]
[ext_resource type="PackedScene" uid="uid://dtimnsj014cu" path="res://addons/dialogic/Editor/Events/Fields/field_vector2.tscn" id="2_c8kyi"]
[node name="Layout" type="HFlowContainer"]
offset_right = 428.0
offset_bottom = 128.0
size_flags_horizontal = 3
script = ExtResource("1_76vf2")
[node name="Label3" type="Label" parent="."]
layout_mode = 2
text = "Ignore Main Scale: "
[node name="IgnoreScale" type="CheckBox" parent="."]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "This portrait will ignore the main scale."
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="Label" type="Label" parent="HBoxContainer"]
layout_mode = 2
text = "Scale:"
[node name="PortraitScale" type="SpinBox" parent="HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "A scale to be applied on top of the main scale
(unless ignore main scale is pressed)."
value = 100.0
allow_greater = true
suffix = "%"
[node name="HBoxContainer2" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="Label2" type="Label" parent="HBoxContainer2"]
layout_mode = 2
text = "Offset:"
[node name="PortraitOffset" parent="HBoxContainer2" instance=ExtResource("2_c8kyi")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Offset that is applied on top of the main portrait offset."
[node name="MirrorOption" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="Label" type="Label" parent="MirrorOption"]
layout_mode = 2
text = "Mirror:"
[node name="PortraitMirror" type="CheckBox" parent="MirrorOption"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Mirroring that is applied on top of the main portrait mirror."
[connection signal="toggled" from="IgnoreScale" to="." method="_on_ignore_scale_toggled"]
[connection signal="value_changed" from="HBoxContainer/PortraitScale" to="." method="_on_portrait_scale_value_changed"]
[connection signal="value_changed" from="HBoxContainer2/PortraitOffset" to="." method="_on_portrait_offset_value_changed"]
[connection signal="toggled" from="MirrorOption/PortraitMirror" to="." method="_on_portrait_mirror_toggled"]
@@ -0,0 +1,101 @@
@tool
extends DialogicCharacterEditorPortraitSection
## Tab that allows setting a custom scene for a portrait.
func _get_title() -> String:
return "Scene"
func _init() -> void:
hint_text = "You can use a custom scene for this portrait."
func _start_opened() -> bool:
return true
func _ready() -> void:
%ChangeSceneButton.icon = get_theme_icon("Loop", "EditorIcons")
%ScenePicker.file_filter = "*.tscn, *.scn; Scenes"
%ScenePicker.resource_icon = get_theme_icon('PackedScene', 'EditorIcons')
%ScenePicker.placeholder = 'Default scene'
%OpenSceneButton.icon = get_theme_icon("ExternalLink", "EditorIcons")
func _load_portrait_data(data:Dictionary) -> void:
reload_ui(data)
func _on_open_scene_button_pressed() -> void:
var data: Dictionary = selected_item.get_metadata(0)
if ResourceLoader.exists(data.get("scene", "")):
DialogicUtil.get_dialogic_plugin().get_editor_interface().open_scene_from_path(data.get("scene", ""))
await get_tree().process_frame
EditorInterface.set_main_screen_editor("2D")
func _on_change_scene_button_pressed() -> void:
%PortraitSceneBrowserWindow.popup_centered_ratio(0.6)
func _on_portrait_scene_browser_activate_part(part_info: Dictionary) -> void:
%PortraitSceneBrowserWindow.hide()
match part_info.type:
"General":
set_scene_path(part_info.path)
"Preset":
find_parent("EditorView").godot_file_dialog(
create_new_portrait_scene.bind(part_info),
'*.tscn,*.scn',
EditorFileDialog.FILE_MODE_SAVE_FILE,
"Select where to save the new scene",
part_info.path.get_file().trim_suffix("."+part_info.path.get_extension())+"_"+character_editor.current_resource.get_character_name().to_lower())
"Custom":
find_parent("EditorView").godot_file_dialog(
set_scene_path,
'*.tscn, *.scn',
EditorFileDialog.FILE_MODE_OPEN_FILE,
"Select custom portrait scene",)
"Default":
set_scene_path("")
func create_new_portrait_scene(target_file: String, info: Dictionary) -> void:
var path := make_portrait_preset_custom(target_file, info)
set_scene_path(path)
func make_portrait_preset_custom(target_file:String, info: Dictionary) -> String:
var previous_file: String = info.path
var result_path := DialogicUtil.make_file_custom(previous_file, target_file.get_base_dir(), target_file.get_file())
return result_path
func set_scene_path(path:String) -> void:
var data: Dictionary = selected_item.get_metadata(0)
data['scene'] = path
update_preview.emit()
changed.emit()
reload_ui(data)
func reload_ui(data: Dictionary) -> void:
var path: String = data.get('scene', '')
%OpenSceneButton.hide()
if path.is_empty():
%SceneLabel.text = "Default Portrait Scene"
%SceneLabel.tooltip_text = "Can be changed in the settings."
%SceneLabel.add_theme_color_override("font_color", get_theme_color("readonly_color", "Editor"))
elif %PortraitSceneBrowser.is_premade_portrait_scene(path):
%SceneLabel.text = %PortraitSceneBrowser.portrait_scenes_info[path].name
%SceneLabel.tooltip_text = path
%SceneLabel.add_theme_color_override("font_color", get_theme_color("accent_color", "Editor"))
else:
%SceneLabel.text = path.get_file()
%SceneLabel.tooltip_text = path
%SceneLabel.add_theme_color_override("font_color", get_theme_color("property_color_x", "Editor"))
%OpenSceneButton.show()
@@ -0,0 +1 @@
uid://cgaygwgc73gbp
@@ -0,0 +1,72 @@
[gd_scene load_steps=6 format=3 uid="uid://djq4aasoihexj"]
[ext_resource type="Script" uid="uid://cgaygwgc73gbp" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd" id="1_ht8lu"]
[ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/field_file.tscn" id="2_k8xs0"]
[ext_resource type="PackedScene" uid="uid://b1wn8r84uh11b" path="res://addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.tscn" id="3_ngvgq"]
[sub_resource type="Image" id="Image_m6kd3"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_f5xt2"]
image = SubResource("Image_m6kd3")
[node name="Scene" type="GridContainer"]
offset_right = 298.0
offset_bottom = 86.0
size_flags_horizontal = 3
script = ExtResource("1_ht8lu")
[node name="HBox" type="HBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
[node name="ChangeSceneButton" type="Button" parent="HBox"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Change Scene"
icon = SubResource("ImageTexture_f5xt2")
[node name="SceneLabel" type="Label" parent="HBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
mouse_filter = 0
theme_override_colors/font_color = Color(0, 0, 0, 1)
text = "asdsdasdasd"
clip_text = true
[node name="ScenePicker" parent="HBox" instance=ExtResource("2_k8xs0")]
unique_name_in_owner = true
visible = false
layout_mode = 2
size_flags_horizontal = 3
file_filter = "*.tscn, *.scn; Scenes"
placeholder = "Default scene"
[node name="OpenSceneButton" type="Button" parent="HBox"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Open/Edit Scene"
icon = SubResource("ImageTexture_f5xt2")
[node name="PortraitSceneBrowserWindow" type="Window" parent="."]
unique_name_in_owner = true
title = "Portrait Scene Browser"
position = Vector2i(0, 36)
visible = false
wrap_controls = true
transient = true
popup_window = true
[node name="PortraitSceneBrowser" parent="PortraitSceneBrowserWindow" instance=ExtResource("3_ngvgq")]
unique_name_in_owner = true
[connection signal="pressed" from="HBox/ChangeSceneButton" to="." method="_on_change_scene_button_pressed"]
[connection signal="pressed" from="HBox/OpenSceneButton" to="." method="_on_open_scene_button_pressed"]
[connection signal="activate_part" from="PortraitSceneBrowserWindow/PortraitSceneBrowser" to="." method="_on_portrait_scene_browser_activate_part"]
@@ -0,0 +1,80 @@
@tool
extends DialogicCharacterEditorPortraitSection
## Portrait Settings Section that only shows the MAIN settings of a portrait scene.
var current_portrait_data := {}
var last_scene := ""
func _show_title() -> bool:
return false
func _load_portrait_data(data:Dictionary) -> void:
_recheck(data, true)
func _recheck(data:Dictionary, force := false) -> void:
get_parent().get_child(get_index()+1).hide()
if last_scene == data.get("scene", "") and not force:
current_portrait_data = data
last_scene = data.get("scene", "")
return
last_scene = data.get("scene", "")
current_portrait_data = data
load_portrait_scene_export_variables()
func load_portrait_scene_export_variables() -> void:
for child in $Grid.get_children():
child.queue_free()
var scene: Variant = null
if current_portrait_data.get('scene', '').is_empty():
if ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
scene = load(character_editor.def_portrait_path)
else:
scene = load(ProjectSettings.get_setting('dialogic/portraits/default_portrait', ''))
else:
scene = load(current_portrait_data.get('scene'))
if not scene:
return
scene = scene.instantiate()
var skip := true
for i in scene.script.get_script_property_list():
if i['usage'] & PROPERTY_USAGE_EDITOR and !skip:
var label := Label.new()
label.text = i['name'].capitalize()
$Grid.add_child(label)
var current_value: Variant = scene.get(i['name'])
if current_portrait_data.has('export_overrides') and current_portrait_data['export_overrides'].has(i['name']):
current_value = str_to_var(current_portrait_data['export_overrides'][i['name']])
if current_value == null and typeof(scene.get(i['name'])) == TYPE_STRING:
current_value = current_portrait_data['export_overrides'][i['name']]
var input: Node = DialogicUtil.setup_script_property_edit_node(i, current_value, set_export_override)
input.size_flags_horizontal = SIZE_EXPAND_FILL
$Grid.add_child(input)
if i['usage'] & PROPERTY_USAGE_GROUP:
if i['name'] == 'Main':
skip = false
else:
skip = true
continue
func set_export_override(property_name:String, value:String = "") -> void:
var data: Dictionary = selected_item.get_metadata(0)
if !data.has('export_overrides'):
data['export_overrides'] = {}
if !value.is_empty():
data['export_overrides'][property_name] = value
else:
data['export_overrides'].erase(property_name)
changed.emit()
update_preview.emit()
@@ -0,0 +1 @@
uid://bj2w8wy64e87i
@@ -0,0 +1,15 @@
[gd_scene load_steps=2 format=3 uid="uid://ba5w02lm3ewkj"]
[ext_resource type="Script" uid="uid://bj2w8wy64e87i" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.gd" id="1_mttrr"]
[node name="MainExports" type="VBoxContainer"]
offset_right = 374.0
offset_bottom = 82.0
script = ExtResource("1_mttrr")
[node name="Grid" type="GridContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/h_separation = 10
columns = 2
@@ -0,0 +1,53 @@
@tool
extends DialogicCharacterEditorMainSection
var min_width := 200
## The general character settings tab
func _get_title() -> String:
return "General"
func _start_opened() -> bool:
return true
func _ready() -> void:
# Connecting all necessary signals
%ColorPickerButton.custom_minimum_size.x = DialogicUtil.get_editor_scale() * 30
%ColorPickerButton.color_changed.connect(character_editor.something_changed)
%DisplayNameLineEdit.text_changed.connect(character_editor.something_changed)
%NicknameLineEdit.text_changed.connect(character_editor.something_changed)
%DescriptionTextEdit.text_changed.connect(character_editor.something_changed)
min_width = get_minimum_size().x
resized.connect(_on_resized)
func _load_character(resource:DialogicCharacter) -> void:
%DisplayNameLineEdit.text = resource.display_name
%ColorPickerButton.color = resource.color
%NicknameLineEdit.text = ""
for nickname in resource.nicknames:
%NicknameLineEdit.text += nickname +", "
%NicknameLineEdit.text = %NicknameLineEdit.text.trim_suffix(', ')
%DescriptionTextEdit.text = resource.description
func _save_changes(resource:DialogicCharacter) -> DialogicCharacter:
resource.display_name = %DisplayNameLineEdit.text
resource.color = %ColorPickerButton.color
var nicknames := []
for n_name in %NicknameLineEdit.text.split(','):
nicknames.append(n_name.strip_edges())
resource.nicknames = nicknames
resource.description = %DescriptionTextEdit.text
return resource
func _on_resized() -> void:
if size.x > min_width+20:
self.columns = 2
else:
self.columns = 1
@@ -0,0 +1 @@
uid://cj21jlbij33mx
@@ -0,0 +1,114 @@
[gd_scene load_steps=5 format=3 uid="uid://bnkck3hocbkk5"]
[ext_resource type="Script" uid="uid://cj21jlbij33mx" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_section_general.gd" id="1_3e1i1"]
[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_cxfqm"]
[sub_resource type="Image" id="Image_ywoka"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_hx3oq"]
image = SubResource("Image_ywoka")
[node name="General" type="GridContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 7.5
offset_top = 38.5
offset_right = -7.5
offset_bottom = -7.5
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/h_separation = 6
theme_override_constants/v_separation = 6
columns = 2
script = ExtResource("1_3e1i1")
[node name="HBox" type="HBoxContainer" parent="."]
layout_mode = 2
size_flags_vertical = 0
[node name="Label2" type="Label" parent="HBox"]
layout_mode = 2
size_flags_vertical = 0
text = "Display Name"
[node name="HintTooltip" parent="HBox" instance=ExtResource("2_cxfqm")]
layout_mode = 2
tooltip_text = "This name will be displayed on the name label. You can use a dialogic variable. E.g. :{Player.name}"
texture = SubResource("ImageTexture_hx3oq")
hint_text = "This name will be displayed on the name label. You can use a dialogic variable. E.g. :{Player.name}"
[node name="DisplayName" type="HBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
[node name="DisplayNameLineEdit" type="LineEdit" parent="DisplayName"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
caret_blink = true
caret_blink_interval = 0.5
[node name="HintTooltip4" parent="DisplayName" instance=ExtResource("2_cxfqm")]
layout_mode = 2
tooltip_text = "This color can be used on the name label and for occurences of the characters name in text (autocolor names)."
texture = SubResource("ImageTexture_hx3oq")
hint_text = "This color can be used on the name label and for occurences of the characters name in text (autocolor names)."
[node name="ColorPickerButton" type="ColorPickerButton" parent="DisplayName"]
unique_name_in_owner = true
custom_minimum_size = Vector2(30, 0)
layout_mode = 2
color = Color(1, 1, 1, 1)
edit_alpha = false
[node name="HBox2" type="HBoxContainer" parent="."]
layout_mode = 2
size_flags_vertical = 0
[node name="Label3" type="Label" parent="HBox2"]
layout_mode = 2
size_flags_vertical = 0
text = "Nicknames"
[node name="HintTooltip2" parent="HBox2" instance=ExtResource("2_cxfqm")]
layout_mode = 2
tooltip_text = "If autocolor names is enabled, these will be colored in the characters color as well."
texture = SubResource("ImageTexture_hx3oq")
hint_text = "If autocolor names is enabled, these will be colored in the characters color as well."
[node name="NicknameLineEdit" type="LineEdit" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
caret_blink = true
caret_blink_interval = 0.5
[node name="HBox3" type="HBoxContainer" parent="."]
layout_mode = 2
size_flags_vertical = 0
[node name="Label4" type="Label" parent="HBox3"]
layout_mode = 2
size_flags_vertical = 0
text = "Description"
[node name="HintTooltip3" parent="HBox3" instance=ExtResource("2_cxfqm")]
layout_mode = 2
tooltip_text = "No effect, just for you."
texture = SubResource("ImageTexture_hx3oq")
hint_text = "No effect, just for you."
[node name="DescriptionTextEdit" type="TextEdit" parent="."]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 65)
layout_mode = 2
size_flags_horizontal = 3
wrap_mode = 1
@@ -0,0 +1,77 @@
@tool
extends DialogicCharacterEditorMainSection
## The general portrait settings section
var loading := false
func _get_title() -> String:
return "Portraits"
func _ready() -> void:
# Connecting all necessary signals
%DefaultPortraitPicker.value_changed.connect(default_portrait_changed)
%MainScale.value_changed.connect(main_portrait_settings_update)
%MainOffset._load_display_info({'step':1})
%MainOffset.value_changed.connect(main_portrait_settings_update)
%MainMirror.toggled.connect(main_portrait_settings_update)
# Setting up Default Portrait Picker
%DefaultPortraitPicker.resource_icon = load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")
%DefaultPortraitPicker.get_suggestions_func = suggest_portraits
## Make sure preview get's updated when portrait settings change
func main_portrait_settings_update(_something=null, _value=null) -> void:
if loading:
return
character_editor.current_resource.scale = %MainScale.value/100.0
character_editor.current_resource.offset = %MainOffset.current_value
character_editor.current_resource.mirror = %MainMirror.button_pressed
character_editor.update_preview()
character_editor.something_changed()
func default_portrait_changed(property:String, value:String) -> void:
character_editor.current_resource.default_portrait = value
character_editor.update_default_portrait_star(value)
func set_default_portrait(portrait_name:String) -> void:
%DefaultPortraitPicker.set_value(portrait_name)
default_portrait_changed("", portrait_name)
func _load_character(resource:DialogicCharacter) -> void:
loading = true
%DefaultPortraitPicker.set_value(resource.default_portrait)
%MainScale.value = 100*resource.scale
%MainOffset.set_value(resource.offset)
%MainMirror.button_pressed = resource.mirror
loading = false
func _save_changes(resource:DialogicCharacter) -> DialogicCharacter:
# Portrait settings
if %DefaultPortraitPicker.current_value in resource.portraits.keys():
resource.default_portrait = %DefaultPortraitPicker.current_value
elif !resource.portraits.is_empty():
resource.default_portrait = resource.portraits.keys()[0]
else:
resource.default_portrait = ""
resource.scale = %MainScale.value/100.0
resource.offset = %MainOffset.current_value
resource.mirror = %MainMirror.button_pressed
return resource
## Get suggestions for DefaultPortraitPicker
func suggest_portraits(search:String) -> Dictionary:
var suggestions := {}
for portrait in character_editor.get_updated_portrait_dict().keys():
suggestions[portrait] = {'value':portrait}
return suggestions
@@ -0,0 +1 @@
uid://x5a7jcmpdwvn
@@ -0,0 +1,59 @@
[gd_scene load_steps=4 format=3 uid="uid://cmrgbo8qi145o"]
[ext_resource type="Script" uid="uid://x5a7jcmpdwvn" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.gd" id="1_6sxsl"]
[ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn" id="2_birla"]
[ext_resource type="PackedScene" uid="uid://dtimnsj014cu" path="res://addons/dialogic/Editor/Events/Fields/field_vector2.tscn" id="3_vcvin"]
[node name="Portraits" type="GridContainer"]
offset_right = 453.0
offset_bottom = 141.0
theme_override_constants/h_separation = 1
theme_override_constants/v_separation = 6
columns = 2
script = ExtResource("1_6sxsl")
[node name="Label5" type="Label" parent="."]
layout_mode = 2
size_flags_vertical = 0
text = "Default"
[node name="DefaultPortraitPicker" parent="." instance=ExtResource("2_birla")]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Select Default Portrait"
fit_text_length = false
[node name="Label" type="Label" parent="."]
layout_mode = 2
size_flags_vertical = 0
text = "Main Scale"
[node name="MainScale" type="SpinBox" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 8
value = 100.0
allow_greater = true
alignment = 1
suffix = "%"
[node name="Label2" type="Label" parent="."]
layout_mode = 2
size_flags_vertical = 0
text = "Main Offset"
[node name="MainOffset" parent="." instance=ExtResource("3_vcvin")]
unique_name_in_owner = true
layout_mode = 2
alignment = 2
[node name="Label3" type="Label" parent="."]
layout_mode = 2
size_flags_vertical = 0
text = "Main Mirror"
[node name="MainMirror" type="CheckBox" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 8
@@ -0,0 +1,688 @@
@tool
extends DialogicEditor
## Editor for editing character resources.
signal character_loaded(resource_path:String)
signal portrait_selected()
# Current state
var loading := false
var current_previewed_scene: Variant = null
var current_scene_path: String = ""
# References
var selected_item: TreeItem
var def_portrait_path: String = DialogicUtil.get_module_path('Character').path_join('default_portrait.tscn')
######### EDITOR STUFF and LOADING/SAVING ######################################
#region Resource Logic
## Method is called once editors manager is ready to accept registers.
func _register() -> void:
## Makes the editor open this when a .dch file is selected.
## Then _open_resource() is called.
editors_manager.register_resource_editor("dch", self)
## Add an "add character" button
var add_character_button: Button = editors_manager.add_icon_button(
load("res://addons/dialogic/Editor/Images/Toolbar/add-character.svg"),
'Add Character',
self)
add_character_button.pressed.connect(_on_create_character_button_pressed)
add_character_button.shortcut = Shortcut.new()
add_character_button.shortcut.events.append(InputEventKey.new())
add_character_button.shortcut.events[0].keycode = KEY_2
add_character_button.shortcut.events[0].ctrl_pressed = true
## By default show the no character screen
$NoCharacterScreen.show()
func _get_title() -> String:
return "Character"
func _get_icon() -> Texture:
return load("res://addons/dialogic/Editor/Images/Resources/character.svg")
## Called when a character is opened somehow
func _open_resource(resource:Resource) -> void:
if resource == null:
$NoCharacterScreen.show()
return
## Update resource
current_resource = (resource as DialogicCharacter)
## Make sure changes in the ui won't trigger saving
loading = true
## Load other main tabs
for child in %MainSettingsSections.get_children():
if child is DialogicCharacterEditorMainSection:
child._load_character(current_resource)
## Clear and then load Portrait section
%PortraitSearch.text = ""
load_portrait_tree()
loading = false
character_loaded.emit(resource.resource_path)
%CharacterName.text = DialogicResourceUtil.get_unique_identifier(resource.resource_path)
$NoCharacterScreen.hide()
%PortraitChangeInfo.hide()
## Called when the character is opened.
func _open(extra_info:Variant="") -> void:
if !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
def_portrait_path = ProjectSettings.get_setting('dialogic/portraits/default_portrait', '')
else:
def_portrait_path = DialogicUtil.get_module_path('Character').path_join('default_portrait.tscn')
if current_resource == null:
$NoCharacterScreen.show()
return
update_preview(true)
%PortraitChangeInfo.hide()
func _clear() -> void:
current_resource = null
current_resource_state = ResourceStates.SAVED
$NoCharacterScreen.show()
func _save() -> void:
if ! visible or not current_resource:
return
## Portrait list
current_resource.portraits = get_updated_portrait_dict()
## Main tabs
for child in %MainSettingsSections.get_children():
if child is DialogicCharacterEditorMainSection:
current_resource = child._save_changes(current_resource)
ResourceSaver.save(current_resource, current_resource.resource_path)
current_resource_state = ResourceStates.SAVED
DialogicResourceUtil.update_directory('dch')
## Saves a new empty character to the given path
func new_character(path: String) -> void:
var resource := DialogicCharacter.new()
resource.resource_path = path
resource.display_name = path.get_file().trim_suffix("."+path.get_extension())
resource.color = Color(1,1,1,1)
resource.default_portrait = ""
resource.custom_info = {}
ResourceSaver.save(resource, path)
EditorInterface.get_resource_filesystem().update_file(path)
DialogicResourceUtil.update_directory('dch')
editors_manager.edit_resource(resource)
#endregion
######### INTERFACE ############################################################
#region Interface
func _ready() -> void:
if get_parent() is SubViewport:
return
DialogicUtil.get_dialogic_plugin().resource_saved.connect(_on_some_resource_saved)
# NOTE: This check is required because up to 4.2 this signal is not exposed.
if DialogicUtil.get_dialogic_plugin().has_signal("scene_saved"):
DialogicUtil.get_dialogic_plugin().scene_saved.connect(_on_some_resource_saved)
$NoCharacterScreen.color = get_theme_color("dark_color_2", "Editor")
$NoCharacterScreen.show()
setup_portrait_list_tab()
_on_fit_preview_toggle_toggled(DialogicUtil.get_editor_setting('character_preview_fit', true))
%PreviewLabel.add_theme_color_override("font_color", get_theme_color("readonly_color", "Editor"))
%PortraitChangeWarning.add_theme_color_override("font_color", get_theme_color("warning_color", "Editor"))
%RealPreviewPivot.texture = get_theme_icon("EditorPivot", "EditorIcons")
%MainSettingsCollapse.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
set_portrait_settings_position(DialogicUtil.get_editor_setting('portrait_settings_position', true))
await find_parent('EditorView').ready
## Add general tabs
add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_section_general.tscn").instantiate(), %MainSettingsSections)
add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.tscn").instantiate(), %MainSettingsSections)
add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.tscn").instantiate(), %PortraitSettingsSection)
add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.tscn").instantiate(), %PortraitSettingsSection)
add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn").instantiate(), %PortraitSettingsSection)
add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.tscn").instantiate(), %PortraitSettingsSection)
## Load custom sections from modules
for indexer in DialogicUtil.get_indexers():
for path in indexer._get_character_editor_sections():
var scene: Control = load(path).instantiate()
if scene is DialogicCharacterEditorMainSection:
add_settings_section(scene, %MainSettingsSections)
elif scene is DialogicCharacterEditorPortraitSection:
add_settings_section(scene, %PortraitSettingsSection)
## Add a section (a control) either to the given settings section (Main or Portraits)
## - sets up the title of the section
## - connects to various signals
func add_settings_section(edit:Control, parent:Node) -> void:
edit.changed.connect(something_changed)
edit.character_editor = self
if edit.has_signal('update_preview'):
edit.update_preview.connect(update_preview)
var button: Button
if edit._show_title():
var hbox := HBoxContainer.new()
hbox.name = edit._get_title()+"BOX"
button = Button.new()
button.flat = true
button.theme_type_variation = "DialogicSection"
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
button.text = edit._get_title()
button.icon_alignment = HORIZONTAL_ALIGNMENT_RIGHT
button.pressed.connect(_on_section_button_pressed.bind(button))
button.focus_mode = Control.FOCUS_NONE
button.icon = get_theme_icon("CodeFoldDownArrow", "EditorIcons")
button.add_theme_color_override('icon_normal_color', get_theme_color("font_color", "DialogicSection"))
hbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
hbox.add_child(button)
if !edit.hint_text.is_empty():
var hint: Node = load("res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn").instantiate()
hint.hint_text = edit.hint_text
hbox.add_child(hint)
parent.add_child(hbox)
parent.add_child(edit)
parent.add_child(HSeparator.new())
if button and !edit._start_opened():
_on_section_button_pressed(button)
func get_settings_section_by_name(name:String, main:=true) -> Node:
var parent := %MainSettingsSections
if not main:
parent = %PortraitSettingsSection
if parent.has_node(name):
return parent.get_node(name)
elif parent.has_node(name+"BOX/"+name):
return parent.get_node(name+"BOX/"+name)
else:
return null
func _on_section_button_pressed(button:Button) -> void:
var section_header := button.get_parent()
var section := section_header.get_parent().get_child(section_header.get_index()+1)
if section.visible:
button.icon = get_theme_icon("CodeFoldedRightArrow", "EditorIcons")
section.visible = false
else:
button.icon = get_theme_icon("CodeFoldDownArrow", "EditorIcons")
section.visible = true
if section_header.get_parent().get_child_count() > section_header.get_index()+2 and section_header.get_parent().get_child(section_header.get_index()+2) is Separator:
section_header.get_parent().get_child(section_header.get_index()+2).visible = section_header.get_parent().get_child(section_header.get_index()+1).visible
func something_changed(fake_argument = "", fake_arg2 = null) -> void:
if not loading:
current_resource_state = ResourceStates.UNSAVED
func _on_main_settings_collapse_toggled(button_pressed:bool) -> void:
%MainSettingsTitle.visible = !button_pressed
%MainSettingsScroll.visible = !button_pressed
if button_pressed:
%MainSettings.hide()
%MainSettingsCollapse.icon = get_theme_icon("GuiVisibilityHidden", "EditorIcons")
else:
%MainSettings.show()
%MainSettingsCollapse.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
func _on_switch_portrait_settings_position_pressed() -> void:
set_portrait_settings_position(!%RightSection.vertical)
func set_portrait_settings_position(is_below:bool) -> void:
%RightSection.vertical = is_below
DialogicUtil.set_editor_setting('portrait_settings_position', is_below)
if is_below:
%SwitchPortraitSettingsPosition.icon = get_theme_icon("ControlAlignRightWide", "EditorIcons")
else:
%SwitchPortraitSettingsPosition.icon = get_theme_icon("ControlAlignBottomWide", "EditorIcons")
#endregion
########## PORTRAIT SECTION ####################################################
#region Portrait Section
func setup_portrait_list_tab() -> void:
%PortraitTree.editor = self
## Portrait section styling/connections
%AddPortraitButton.icon = get_theme_icon("Add", "EditorIcons")
%AddPortraitButton.pressed.connect(add_portrait)
%AddPortraitGroupButton.icon = load("res://addons/dialogic/Editor/Images/Pieces/add-folder.svg")
%AddPortraitGroupButton.pressed.connect(add_portrait_group)
%ImportPortraitsButton.icon = get_theme_icon("Load", "EditorIcons")
%ImportPortraitsButton.pressed.connect(open_portrait_folder_select)
%PortraitSearch.right_icon = get_theme_icon("Search", "EditorIcons")
%PortraitSearch.text_changed.connect(filter_portrait_list)
%PortraitTree.item_selected.connect(load_selected_portrait)
%PortraitTree.item_edited.connect(_on_item_edited)
%PortraitTree.item_activated.connect(_on_item_activated)
func open_portrait_folder_select() -> void:
find_parent("EditorView").godot_file_dialog(
import_portraits_from_folder, "*.svg, *.png",
EditorFileDialog.FILE_MODE_OPEN_DIR)
func import_portraits_from_folder(path:String) -> void:
var parent: TreeItem = %PortraitTree.get_root()
if %PortraitTree.get_selected() and %PortraitTree.get_selected() != parent and %PortraitTree.get_selected().get_metadata(0).has('group'):
parent = %PortraitTree.get_selected()
var dir := DirAccess.open(path)
dir.list_dir_begin()
var file_name: String = dir.get_next()
var files := []
while file_name != "":
if not dir.current_is_dir():
var file_lower := file_name.to_lower()
if '.svg' in file_lower or '.png' in file_lower:
if not '.import' in file_lower:
files.append(file_name)
file_name = dir.get_next()
var prefix: String = files[0]
for file in files:
while true:
if file.begins_with(prefix):
break
if prefix.is_empty():
break
prefix = prefix.substr(0, len(prefix)-1)
for file in files:
%PortraitTree.add_portrait_item(file.trim_prefix(prefix).trim_suffix('.'+file.get_extension()),
{'scene':"",'export_overrides':{'image':var_to_str(path.path_join(file))}, 'scale':1, 'offset':Vector2(), 'mirror':false}, parent)
## Handle selection
if parent.get_child_count():
parent.get_first_child().select(0)
else:
# Call anyways to clear preview and hide portrait settings section
load_selected_portrait()
something_changed()
func add_portrait(portrait_name:String='New portrait', portrait_data:Dictionary={'scene':"", 'export_overrides':{'image':''}, 'scale':1, 'offset':Vector2(), 'mirror':false}) -> void:
var parent: TreeItem = %PortraitTree.get_root()
if %PortraitTree.get_selected():
if %PortraitTree.get_selected().get_metadata(0) and %PortraitTree.get_selected().get_metadata(0).has('group'):
parent = %PortraitTree.get_selected()
else:
parent = %PortraitTree.get_selected().get_parent()
var item: TreeItem = %PortraitTree.add_portrait_item(portrait_name, portrait_data, parent)
item.set_meta('new', true)
item.set_editable(0, true)
item.select(0)
%PortraitTree.call_deferred('edit_selected')
something_changed()
func add_portrait_group() -> void:
var parent_item: TreeItem = %PortraitTree.get_root()
if %PortraitTree.get_selected() and %PortraitTree.get_selected().get_metadata(0).has('group'):
parent_item = %PortraitTree.get_selected()
var item: TreeItem = %PortraitTree.add_portrait_group("Group", parent_item)
item.set_meta('new', true)
item.set_editable(0, true)
item.select(0)
%PortraitTree.call_deferred('edit_selected')
func load_portrait_tree() -> void:
%PortraitTree.clear_tree()
var root: TreeItem = %PortraitTree.create_item()
for portrait in current_resource.portraits.keys():
var portrait_label: String = portrait
var parent: TreeItem = %PortraitTree.get_root()
if '/' in portrait:
parent = %PortraitTree.create_necessary_group_items(portrait)
portrait_label = portrait.split('/')[-1]
%PortraitTree.add_portrait_item(portrait_label, current_resource.portraits[portrait], parent)
update_default_portrait_star(current_resource.default_portrait)
if root.get_child_count():
root.get_first_child().select(0)
while %PortraitTree.get_selected().get_child_count():
%PortraitTree.get_selected().get_child(0).select(0)
else:
# Call anyways to clear preview and hide portrait settings section
load_selected_portrait()
func filter_portrait_list(filter_term := "") -> void:
filter_branch(%PortraitTree.get_root(), filter_term)
func filter_branch(parent: TreeItem, filter_term: String) -> bool:
var anything_visible := false
for item in parent.get_children():
if item.get_metadata(0).has('group'):
item.visible = filter_branch(item, filter_term)
anything_visible = item.visible
elif filter_term.is_empty() or filter_term.to_lower() in item.get_text(0).to_lower():
item.visible = true
anything_visible = true
else:
item.visible = false
return anything_visible
## This is used to save the portrait data
func get_updated_portrait_dict() -> Dictionary:
return list_portraits(%PortraitTree.get_root().get_children())
func list_portraits(tree_items: Array[TreeItem], dict := {}, path_prefix := "") -> Dictionary:
for item in tree_items:
if item.get_metadata(0).has('group'):
dict = list_portraits(item.get_children(), dict, path_prefix+item.get_text(0)+"/")
else:
dict[path_prefix +item.get_text(0)] = item.get_metadata(0)
return dict
func load_selected_portrait() -> void:
if selected_item and is_instance_valid(selected_item):
selected_item.set_editable(0, false)
selected_item = %PortraitTree.get_selected()
if selected_item and selected_item.get_metadata(0) != null and !selected_item.get_metadata(0).has('group'):
%PortraitSettingsSection.show()
var current_portrait_data: Dictionary = selected_item.get_metadata(0)
portrait_selected.emit(%PortraitTree.get_full_item_name(selected_item), current_portrait_data)
update_preview()
for child in %PortraitSettingsSection.get_children():
if child is DialogicCharacterEditorPortraitSection:
child.selected_item = selected_item
child._load_portrait_data(current_portrait_data)
else:
%PortraitSettingsSection.hide()
update_preview()
func delete_portrait_item(item: TreeItem) -> void:
if item.get_next_visible(true) and item.get_next_visible(true) != item:
item.get_next_visible(true).select(0)
else:
selected_item = null
load_selected_portrait()
item.free()
something_changed()
func duplicate_item(item: TreeItem) -> void:
var new_item: TreeItem = %PortraitTree.add_portrait_item(item.get_text(0)+'_duplicated', item.get_metadata(0).duplicate(true), item.get_parent())
new_item.set_meta('new', true)
new_item.select(0)
func _input(event: InputEvent) -> void:
if !is_visible_in_tree() or (get_viewport().gui_get_focus_owner()!= null and !name+'/' in str(get_viewport().gui_get_focus_owner().get_path())):
return
if event is InputEventKey and event.pressed:
if event.keycode == KEY_F2 and %PortraitTree.get_selected():
%PortraitTree.get_selected().set_editable(0, true)
%PortraitTree.edit_selected()
get_viewport().set_input_as_handled()
elif event.keycode == KEY_DELETE and get_viewport().gui_get_focus_owner() is Tree and %PortraitTree.get_selected():
delete_portrait_item(%PortraitTree.get_selected())
get_viewport().set_input_as_handled()
func _on_portrait_right_click_menu_index_pressed(id: int) -> void:
# RENAME BUTTON
if id == 0:
_on_item_activated()
# DELETE BUTTON
if id == 2:
delete_portrait_item(%PortraitTree.get_selected())
# DUPLICATE ITEM
elif id == 1:
duplicate_item(%PortraitTree.get_selected())
elif id == 4:
get_settings_section_by_name("Portraits").set_default_portrait(%PortraitTree.get_full_item_name(%PortraitTree.get_selected()))
## This removes/and adds the DEFAULT star on the portrait list
func update_default_portrait_star(default_portrait_name: String) -> void:
var item_list: Array = %PortraitTree.get_root().get_children()
if item_list.is_empty() == false:
while true:
var item: TreeItem = item_list.pop_back()
if item.get_button_by_id(0, 2) != -1:
item.erase_button(0, item.get_button_by_id(0, 2))
if %PortraitTree.get_full_item_name(item) == default_portrait_name:
item.add_button(0, get_theme_icon("Favorites", "EditorIcons"), 2, true, "Default")
item_list.append_array(item.get_children())
if item_list.is_empty():
break
func _on_item_edited() -> void:
selected_item = %PortraitTree.get_selected()
something_changed()
if selected_item:
if %PreviewLabel.text.trim_prefix('Preview of "').trim_suffix('"') == current_resource.default_portrait:
current_resource.default_portrait = %PortraitTree.get_full_item_name(selected_item)
selected_item.set_editable(0, false)
if !selected_item.has_meta('new') and %PortraitTree.get_full_item_name(selected_item) != selected_item.get_meta('previous_name'):
report_name_change(selected_item)
%PortraitChangeInfo.show()
update_preview()
func _on_item_activated() -> void:
if %PortraitTree.get_selected() == null:
return
%PortraitTree.get_selected().set_editable(0, true)
%PortraitTree.edit_selected()
func report_name_change(item: TreeItem) -> void:
if item.get_metadata(0).has('group'):
for s_item in item.get_children():
if s_item.get_metadata(0).has('group') or !s_item.has_meta('new'):
report_name_change(s_item)
else:
if item.get_meta('previous_name') == %PortraitTree.get_full_item_name(item):
return
editors_manager.reference_manager.add_portrait_ref_change(
item.get_meta('previous_name'),
%PortraitTree.get_full_item_name(item),
[DialogicResourceUtil.get_unique_identifier(current_resource.resource_path)])
item.set_meta('previous_name', %PortraitTree.get_full_item_name(item))
%PortraitChangeInfo.show()
#endregion
########### PREVIEW ############################################################
#region Preview
func update_preview(force := false, ignore_settings_reload := false) -> void:
%ScenePreviewWarning.hide()
if selected_item and is_instance_valid(selected_item) and selected_item.get_metadata(0) != null and !selected_item.get_metadata(0).has('group'):
%PreviewLabel.text = 'Preview of "'+%PortraitTree.get_full_item_name(selected_item)+'"'
var current_portrait_data: Dictionary = selected_item.get_metadata(0)
if not force and current_previewed_scene != null \
and scene_file_path == current_portrait_data.get('scene') \
and current_previewed_scene.has_method('_should_do_portrait_update') \
and is_instance_valid(current_previewed_scene.get_script()) \
and current_previewed_scene._should_do_portrait_update(current_resource, selected_item.get_text(0)):
# We keep the same scene.
pass
else:
for node in %RealPreviewPivot.get_children():
node.queue_free()
current_previewed_scene = null
current_scene_path = ""
var scene_path := def_portrait_path
if not current_portrait_data.get('scene', '').is_empty():
scene_path = current_portrait_data.get('scene')
if ResourceLoader.exists(scene_path):
current_previewed_scene = load(scene_path).instantiate()
current_scene_path = scene_path
if not current_previewed_scene == null:
%RealPreviewPivot.add_child(current_previewed_scene)
if not current_previewed_scene == null:
var scene: Node = current_previewed_scene
scene.show_behind_parent = true
DialogicUtil.apply_scene_export_overrides(scene, current_portrait_data.get('export_overrides', {}))
var mirror: bool = current_portrait_data.get('mirror', false) != current_resource.mirror
var scale: float = current_portrait_data.get('scale', 1) * current_resource.scale
if current_portrait_data.get('ignore_char_scale', false):
scale = current_portrait_data.get('scale', 1)
var offset: Vector2 = current_portrait_data.get('offset', Vector2()) + current_resource.offset
if is_instance_valid(scene.get_script()) and scene.script.is_tool():
if scene.has_method('_update_portrait'):
## Create a fake duplicate resource that has all the portrait changes applied already
var preview_character := current_resource.duplicate()
preview_character.portraits = get_updated_portrait_dict()
scene._update_portrait(preview_character, %PortraitTree.get_full_item_name(selected_item))
if scene.has_method('_set_mirror'):
scene._set_mirror(mirror)
if !%FitPreview_Toggle.button_pressed:
scene.position = Vector2() + offset
scene.scale = Vector2(1,1)*scale
else:
if not scene.get_script() == null and scene.script.is_tool() and scene.has_method('_get_covered_rect'):
var rect: Rect2 = scene._get_covered_rect()
var available_rect: Rect2 = %FullPreviewAvailableRect.get_rect()
scene.scale = Vector2(1,1) * min(available_rect.size.x/rect.size.x, available_rect.size.y/rect.size.y)
%RealPreviewPivot.position = (rect.position)*-1*scene.scale
%RealPreviewPivot.position.x = %FullPreviewAvailableRect.size.x/2
scene.position = Vector2()
else:
%ScenePreviewWarning.show()
else:
%PreviewLabel.text = 'Nothing to preview'
if not ignore_settings_reload:
for child in %PortraitSettingsSection.get_children():
if child is DialogicCharacterEditorPortraitSection:
child._recheck(current_portrait_data)
else:
%PreviewLabel.text = 'No portrait to preview.'
for node in %RealPreviewPivot.get_children():
node.queue_free()
current_previewed_scene = null
current_scene_path = ""
func _on_some_resource_saved(file:Variant) -> void:
if current_previewed_scene == null:
return
if file is Resource and file == current_previewed_scene.script:
update_preview(true)
if typeof(file) == TYPE_STRING and file == current_previewed_scene.get_meta("path", ""):
update_preview(true)
func _on_full_preview_available_rect_resized() -> void:
if %FitPreview_Toggle.button_pressed:
update_preview(false, true)
func _on_create_character_button_pressed() -> void:
editors_manager.show_add_resource_dialog(
new_character,
'*.dch; DialogicCharacter',
'Create new character',
'character',
)
func _on_fit_preview_toggle_toggled(button_pressed):
%FitPreview_Toggle.set_pressed_no_signal(button_pressed)
if button_pressed:
%FitPreview_Toggle.icon = get_theme_icon("ScrollContainer", "EditorIcons")
%FitPreview_Toggle.tooltip_text = "Real scale"
else:
%FitPreview_Toggle.tooltip_text = "Fit into preview"
%FitPreview_Toggle.icon = get_theme_icon("CenterContainer", "EditorIcons")
DialogicUtil.set_editor_setting('character_preview_fit', button_pressed)
update_preview(false, true)
#endregion
## Open the reference manager
func _on_reference_manger_button_pressed() -> void:
editors_manager.reference_manager.open()
%PortraitChangeInfo.hide()

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