Compare commits
572 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 628da80ed3 | |||
| 0dfbd73978 | |||
| 8a93b00e72 | |||
| ba7d550c3f | |||
| b65a3bbd6d | |||
| bcbc074c86 | |||
| 745f54b375 | |||
| 2c8a024528 | |||
| 76eb408a40 | |||
| ff143f07e7 | |||
| b50ca5b919 | |||
| 59d313d97d | |||
| 9032272599 | |||
| a2ef3bfaf0 | |||
| 1ab1071246 | |||
| b0ee8c4c58 | |||
| 522fdfa365 | |||
| 4f6c5ab9c1 | |||
| 11e24e249e | |||
| f3080df4b7 | |||
| f6e83e7fef | |||
| 3bdc8cd03f | |||
| f862827d98 | |||
| a401cbe5b9 | |||
| 48660b167c | |||
| e5b2b8b8ab | |||
| 2fa8aa9fbc | |||
| 6deb6e29fd | |||
| edc133749f | |||
| 5f75bde317 | |||
| 7310bfbf6e | |||
| fef8380a57 | |||
| f42c2c86b1 | |||
| f1a2e6166b | |||
| c0111e4c17 | |||
| 2ed9dbbc52 | |||
| 4f660c4af8 | |||
| 13fcf059e9 | |||
| 7f58aeae76 | |||
| 3456340bb7 | |||
| 30750d2e3c | |||
| 950731b225 | |||
| feb591f56a | |||
| aac0992590 | |||
| 21cf2d1a6a | |||
| 3f5cfed477 | |||
| 64c15c2e63 | |||
| a98c842ee0 | |||
| abed751d29 | |||
| ce113e16da | |||
| c55e527bf4 | |||
| c509a52001 | |||
| 9e0e87ef17 | |||
| 7ffbb8e68d | |||
| 6375383373 | |||
| b810ea1d9f | |||
| b232cb31c3 | |||
| a6260efc07 | |||
| 3d19509826 | |||
| 113dd0820f | |||
| 7e148cd9fe | |||
| 59489e4a17 | |||
| fc00559483 | |||
| 982178d89a | |||
| 09f55d3aa7 | |||
| c89685c936 | |||
| 79108fbe15 | |||
| 9499c27444 | |||
| 0434667c22 | |||
| a736adaafb | |||
| bef54420e4 | |||
| 9267278ce8 | |||
| 9ba83dfddd | |||
| 7c5ba2d6bb | |||
| 0c5fc26d2f | |||
| e51e0a24d2 | |||
| 7bb523f9e7 | |||
| a3651d6268 | |||
| 0892a13e66 | |||
| 61caa6eea6 | |||
| 2ec693eed2 | |||
| b7d1f9d8d5 | |||
| 12bf632932 | |||
| 0d985e5bf6 | |||
| e86b3e81dd | |||
| 14a73daa75 | |||
| 996470f5e6 | |||
| 1dd4a694d0 | |||
| 9b8933e698 | |||
| 9f4cda0e6d | |||
| 1c865730a3 | |||
| aa4f1c55b5 | |||
| aad6c44567 | |||
| 8e9359d6f8 | |||
| 7696271727 | |||
| 229dd04bc1 | |||
| 8407ce4c1f | |||
| b9a52dadcc | |||
| 0ecae5a4d9 | |||
| c288af296c | |||
| e65330786e | |||
| c7a4aea70b | |||
| b6caf4dbed | |||
| 51a145a994 | |||
| 41b30a4274 | |||
| a1fb71119f | |||
| 67d5b67c21 | |||
| 684131f495 | |||
| f32033cb5f | |||
| 1ad463ccb0 | |||
| 451ea82706 | |||
| 3f4dfccbc6 | |||
| 3c370e30a0 | |||
| 073a74bb22 | |||
| 75e4bdd7fd | |||
| f37f7c7ceb | |||
| 70383fc16e | |||
| 88f3c90eec | |||
| 9d818e5079 | |||
| e9cd4ce276 | |||
| 638ebaff46 | |||
| b4013b1ff2 | |||
| c695115a4a | |||
| c68727aebe | |||
| bee6249c6f | |||
| fda2b95b38 | |||
| 4bbaab1a2a | |||
| 4ed6f4e7d9 | |||
| ce29711614 | |||
| 2947011a1a | |||
| 2a4425b9db | |||
| 72079044bd | |||
| b205e45096 | |||
| d17e58bcd3 | |||
| 02cbc761bc | |||
| fb6a42a874 | |||
| 4bd433b1b0 | |||
| c02a2d1383 | |||
| c5f8b157ad | |||
| 093546f3e4 | |||
| c9f97c2571 | |||
| 6613e54658 | |||
| 3fcb34c04d | |||
| 5cc65bc3f4 | |||
| 443741f5f1 | |||
| 554a319428 | |||
| 3bb4fcd129 | |||
| 4705d1ee2f | |||
| b00b466045 | |||
| 0507f1ee3d | |||
| 651ec2cc68 | |||
| d51cc461f7 | |||
| 54bfc3c021 | |||
| 55f0d2a77e | |||
| 1531ffbf8d | |||
| 74096f2c71 | |||
| 97dcadf07a | |||
| 1c5d776a3a | |||
| c814d7606d | |||
| e1a61fb1e2 | |||
| edbd4a182b | |||
| a9446ec770 | |||
| fe1dd2bcc1 | |||
| 6e833a0735 | |||
| c52bfc8017 | |||
| 58c2be1db8 | |||
| c7c6e184d6 | |||
| 6a2355cf97 | |||
| 1d7f656ec4 | |||
| 1c5b585b3d | |||
| f1b980cdb6 | |||
| 8b77ca9bc5 | |||
| f51275ccc0 | |||
| 8d616735f4 | |||
| 50b2c04cb4 | |||
| 66848af8bf | |||
| e864c62a3a | |||
| f27f69c15f | |||
| 21b361f9d7 | |||
| 83dc6bfd56 | |||
| 0de3bcae22 | |||
| 3326bde40c | |||
| 83c9dfc945 | |||
| cd235248c6 | |||
| 8bb1c22549 | |||
| 8624e2aea8 | |||
| fef1bcc3b6 | |||
| f4d8ed87e2 | |||
| 1f227f70a7 | |||
| 9bf25640f6 | |||
| 0e315396c9 | |||
| f27dd199b8 | |||
| fd0e631b1f | |||
| 6ba26c360d | |||
| 63fd81e54d | |||
| b54f784d51 | |||
| 6194988bf0 | |||
| 759933c1cd | |||
| af8807610d | |||
| 02b6584e44 | |||
| e202534c6b | |||
| 1f13f3ea51 | |||
| 8b7afbcab6 | |||
| afce4ad0b9 | |||
| 32ac007256 | |||
| d45deeb7ed | |||
| 9e21b07271 | |||
| f94c2d0a50 | |||
| 84b8938d1f | |||
| 8d421b58ec | |||
| ebe99c5f22 | |||
| b32b483d09 | |||
| c6e9bc9336 | |||
| 5b0fed7816 | |||
| 3bdd5aa873 | |||
| 3fe79f2fd8 | |||
| 8c1157c26e | |||
| c96e6da78e | |||
| 0fc1760b70 | |||
| 90b8dda16a | |||
| ecff43d5c3 | |||
| 40b8c022fe | |||
| e1b1a2f447 | |||
| 2d4e853ec8 | |||
| 8f097de476 | |||
| 109573fb1b | |||
| 0e55394699 | |||
| b1335d4b00 | |||
| 078e26e868 | |||
| ffebe10593 | |||
| 4c8406ba97 | |||
| 080ebaae47 | |||
| 5cf250b295 | |||
| ef56f79d5c | |||
| 5bcbee8865 | |||
| a74abe684b | |||
| a593be8273 | |||
| 1e004b62b8 | |||
| 5fc7b3d563 | |||
| d4892e1aa4 | |||
| e0ea5ef5be | |||
| 02ff401273 | |||
| c3e78b5db2 | |||
| 09b2baab0a | |||
| d6a2b586e4 | |||
| 936983e14f | |||
| e73f13d7d4 | |||
| 0713e334b3 | |||
| c56f654751 | |||
| 1f88f9b4b5 | |||
| 06fd80f762 | |||
| 2cb605261e | |||
| 975dd45c94 | |||
| d1a8ff0cbf | |||
| cba6c09ca0 | |||
| da7cd2f6f2 | |||
| 9f50e1d293 | |||
| 652cac4232 | |||
| e44e06bace | |||
| 89313fd484 | |||
| 6f340b54a4 | |||
| 8f3be823a4 | |||
| eeb56fd7ad | |||
| 871e1856f1 | |||
| 378bf45c49 | |||
| f6e0c4e615 | |||
| 6ae877f2ab | |||
| 3913143892 | |||
| 4734ae953c | |||
| 5992c390ee | |||
| 283caf282b | |||
| 7c03964d33 | |||
| 36c948f116 | |||
| d0ba4076b3 | |||
| 7fd26ffed3 | |||
| ce2d7eb773 | |||
| 41365fb5d4 | |||
| 96c7d35aa7 | |||
| c2f5359d0c | |||
| 9bebe1a44d | |||
| 06a270e916 | |||
| b621df5435 | |||
| 03dbc08293 | |||
| 8676bbb2f6 | |||
| 6e998810b6 | |||
| c96be7467e | |||
| 6aa7530502 | |||
| d7ac1c6c22 | |||
| 0170a53b5a | |||
| abc33fd06c | |||
| 2fbeb93018 | |||
| 9253a78a06 | |||
| 0008b16d48 | |||
| 8e0dced918 | |||
| 2e21fb7e98 | |||
| b69191e7f7 | |||
| 8f75b5b644 | |||
| 6275bea52c | |||
| 279cac22ee | |||
| e88ba2e1ef | |||
| f01143e887 | |||
| 18711776bb | |||
| 0f246825fc | |||
| 19e30dd0b8 | |||
| 730c4999d7 | |||
| cfe604d3b7 | |||
| ad16b86171 | |||
| 5ee295256b | |||
| ab23d41496 | |||
| 12842a37ff | |||
| ae0944fe00 | |||
| 486cfb5546 | |||
| b92eb909ad | |||
| d2c7302ab2 | |||
| 5affc48dc5 | |||
| cdb6b83124 | |||
| a4f9511892 | |||
| cc7e8c5c4b | |||
| 7a3e96d679 | |||
| e38c391e1c | |||
| c0e002f783 | |||
| e8850ca7c5 | |||
| eaedf8c396 | |||
| ffa20bbdf8 | |||
| b77b6e3a52 | |||
| 69cb63ad89 | |||
| 11790434f5 | |||
| c7d56301fc | |||
| 8bd2b09232 | |||
| 480d149ede | |||
| c6fc400994 | |||
| 16251db248 | |||
| 4752002caf | |||
| 0e028a2cb9 | |||
| 01a281c2aa | |||
| 2a46d363e9 | |||
| 92c3f5e054 | |||
| 6727339e9a | |||
| e3d67722de | |||
| b6fd6292e3 | |||
| e8c24e18be | |||
| 199d0873c8 | |||
| e84ef716f6 | |||
| 135ba0d198 | |||
| 2940475062 | |||
| 202f8809aa | |||
| 28bb81c0b0 | |||
| b9c5de6dc3 | |||
| 8c18688a69 | |||
| 2429958741 | |||
| 6b5a8ee126 | |||
| 8d40529349 | |||
| 9e97cc3b80 | |||
| ebc3ee9cf9 | |||
| a1bbe44105 | |||
| 3ce10c8ba9 | |||
| 7e6163ed68 | |||
| 55d6222698 | |||
| d7ad2ba072 | |||
| 9c50a63dc3 | |||
| 746ac58329 | |||
| a9cb20c8bc | |||
| 32249fde86 | |||
| 27e137bc02 | |||
| 01daddee3b | |||
| ba512508f8 | |||
| ec653aa847 | |||
| 9acea54f28 | |||
| 2f5296e05a | |||
| e8a58655ad | |||
| aa3789566c | |||
| 9c7ae20c18 | |||
| ffc7ef2d61 | |||
| 9f04c8819f | |||
| 6cd7be196b | |||
| ab2cf3f497 | |||
| bf096aa716 | |||
| 09ef508f49 | |||
| 6de8368414 | |||
| d20085a67c | |||
| a54003c658 | |||
| 648ae8395b | |||
| fbad33213c | |||
| b68eb0953e | |||
| 35c7e9a25e | |||
| 57896e37df | |||
| 242879159a | |||
| 7ef9fe53b2 | |||
| 600d9cfca1 | |||
| 48796de1ba | |||
| 4a0b2cd550 | |||
| ba429d7b84 | |||
| e33776daeb | |||
| 9cfd3bd6f4 | |||
| 7eb18caaaf | |||
| e65e82f421 | |||
| c36d0b5866 | |||
| bcdba7b812 | |||
| 7467ec56d9 | |||
| 990494e8b0 | |||
| c6e0cfe4d9 | |||
| 691e613a5c | |||
| 4e010220bf | |||
| ccc4eb759e | |||
| 250f0d07ca | |||
| f481f5bea7 | |||
| 58c65fc928 | |||
| 937e54e47a | |||
| 83c8a15e5a | |||
| 1fcddb7349 | |||
| 26cae9b0f8 | |||
| 0ebd0c46a0 | |||
| 2226b859df | |||
| 07bcab4454 | |||
| 4c6d5350b2 | |||
| f2c492de54 | |||
| e269b3ab44 | |||
| 89ae347a91 | |||
| ddb5547b42 | |||
| afa06d3ee2 | |||
| dd656d5f5f | |||
| cdb62f3c2f | |||
| b0f4592fdd | |||
| 406c11b581 | |||
| 9812006234 | |||
| b8e456f7ef | |||
| 97e5485ef1 | |||
| c77563d38e | |||
| c322f7667a | |||
| 68be479a09 | |||
| dc52d80a92 | |||
| a84ca9c32c | |||
| 6b1dd04dd5 | |||
| 523624756e | |||
| 72593ff7c7 | |||
| d7dfd03241 | |||
| eac135508c | |||
| 90cadbea07 | |||
| 72c3454728 | |||
| 83e25f44cd | |||
| 31f19ad139 | |||
| c6ec99e87a | |||
| b8cb60aec9 | |||
| 55a3e56791 | |||
| 1f17cca62d | |||
| 1629862759 | |||
| a53b4c4010 | |||
| 3e135afecb | |||
| 25d8f217ff | |||
| a477f9ef4f | |||
| b917fb5fbd | |||
| 20ed134628 | |||
| 47a1326292 | |||
| 708aa8cca4 | |||
| 83f4144b43 | |||
| 2c677a8cda | |||
| ec5d42068e | |||
| 8fdb395ae8 | |||
| 60928c520c | |||
| f3ed5e0ad0 | |||
| ee897db52b | |||
| 41201d7406 | |||
| 267172d026 | |||
| 917e6a5f7e | |||
| 23ef4e9a63 | |||
| fa7cc66748 | |||
| 7b006d19ef | |||
| ad070514d7 | |||
| 80c48ea3df | |||
| d76218f88f | |||
| 7276bdda38 | |||
| 73dc3063c0 | |||
| ae4f7d9c58 | |||
| 9a5caef52f | |||
| 15c2a84ecd | |||
| 1094fb2142 | |||
| 2ab1c38de9 | |||
| 3eb35d4e7d | |||
| 4261789f13 | |||
| ce18c89c09 | |||
| 7643cccebf | |||
| 7e4d733d7c | |||
| 1dcd900737 | |||
| 74c3540c96 | |||
| 6d80253d9c | |||
| 1b7c35879c | |||
| ff426b0494 | |||
| 90616537bc | |||
| 0f8eb244e9 | |||
| 38fdad7690 | |||
| 0de6bc0c04 | |||
| fc2d6592a2 | |||
| 222c10fe0a | |||
| 8af825bc18 | |||
| f7684b6c2a | |||
| f9a3a8e389 | |||
| 6ad8fb00bd | |||
| c4dbc104d4 | |||
| 77c0babdca | |||
| aa5a3483bb | |||
| 68b6031da1 | |||
| 4afb02440c | |||
| de5657a9cd | |||
| ff85b67087 | |||
| 6f3d843818 | |||
| 20f8eaaebd | |||
| 69848e77c9 | |||
| 366923699c | |||
| 2a7f39e3fe | |||
| 296fed6e08 | |||
| 1af04631c5 | |||
| 9ec1bcab97 | |||
| d11fc9f1f2 | |||
| 16a65f7155 | |||
| 0fcc425ace | |||
| 21685d60d5 | |||
| 752cdb1502 | |||
| 8299998a0f | |||
| 5ea6431216 | |||
| 7cccd40290 | |||
| 762a694be3 | |||
| 339428c312 | |||
| 7e19268847 | |||
| 20b37de108 | |||
| 7d1187d218 | |||
| 54b07cccad | |||
| fb0c14c3f1 | |||
| fb362e10ad | |||
| c73d3ff370 | |||
| e99b574d1f | |||
| 1062193785 | |||
| 9190b2a16a | |||
| c200098f0c | |||
| 0e775309fb | |||
| 5db71adf03 | |||
| ce5d2b65d3 | |||
| 6016f1cf38 | |||
| b5dcad5614 | |||
| 64f039ba19 | |||
| 8c1822557c | |||
| dacf48b9fd | |||
| daa53e4aef | |||
| 77591425cb | |||
| 667ef6fb9b | |||
| c90687939c | |||
| 8622f2d38d | |||
| bd4f7edc20 | |||
| 45a58f701b | |||
| f08f3a7978 | |||
| 26502f2d37 | |||
| a9b1c179cd | |||
| d144f5c0c4 | |||
| 45d09cd618 | |||
| f2902ad7c1 | |||
| 7f8b444270 | |||
| d4b7c0bf45 | |||
| 959ab68b4d | |||
| 4f312209b3 | |||
| e0b5a26ee6 | |||
| 78471268db | |||
| 84fcec8227 | |||
| dc322d8943 | |||
| 4ce0367f15 | |||
| 421a90ec17 | |||
| ec10f0e54b | |||
| cbcbcbbcee | |||
| dde4f18305 | |||
| 267a0b9556 | |||
| 5ea5e5547c | |||
| d231bb0773 | |||
| ea20332b4f | |||
| 6090e7b17b |
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,9 @@
|
||||
<component name="libraryTable">
|
||||
<library name="GdSdk" type="GdScript">
|
||||
<CLASSES />
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="file://$APPLICATION_PLUGINS_DIR$/GdScript/extracted/Master" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -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>
|
||||
@@ -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,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>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,14 @@
|
||||
<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>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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"><Policy><Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy></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>
|
||||
@@ -0,0 +1,39 @@
|
||||
<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_003AGD_005Fconstants_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fdf61b6148ca71eef8cf4e56545a9319fd2647f05d2f959dad42d9fcbe95192_003FGD_005Fconstants_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"><SessionState ContinuousTestingMode="0" IsActive="True" Name="Tests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<TestAncestor>
|
||||
<TestId>NUnit3x::A6EF2269-9E64-40D4-BA0A-33CB234E2503::net9.0::BabushkaTest.Tests</TestId>
|
||||
</TestAncestor>
|
||||
</SessionState></s:String></wpf:ResourceDictionary>
|
||||
@@ -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 |
@@ -0,0 +1,43 @@
|
||||
[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/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
@@ -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
|
||||
@@ -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,43 @@
|
||||
[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/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
@@ -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 |
@@ -0,0 +1,43 @@
|
||||
[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/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
@@ -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
|
||||
@@ -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 |
@@ -0,0 +1,43 @@
|
||||
[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/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
@@ -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"]
|
||||
@@ -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,43 @@
|
||||
[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/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=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,43 @@
|
||||
[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/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=2.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
@@ -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,43 @@
|
||||
[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/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=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"
|
||||
@@ -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
|
||||
@@ -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 = ""
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="BabushkaHelpers"
|
||||
description=""
|
||||
author="Cozy Raven"
|
||||
version=""
|
||||
script="babushkahelpers.gd"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cp531dtjqotvl
|
||||
@@ -0,0 +1,456 @@
|
||||
[gd_scene load_steps=11 format=3 uid="uid://dlskc36c5hrwv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cp531dtjqotvl" path="res://addons/dialogic/Editor/CharacterEditor/character_editor.gd" id="2"]
|
||||
[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_uhhqs"]
|
||||
[ext_resource type="Script" uid="uid://dr2wwtcytw5ew" path="res://addons/dialogic/Editor/CharacterEditor/character_editor_portrait_tree.gd" id="2_vad0i"]
|
||||
[ext_resource type="Texture2D" uid="uid://babwe22dqjta" path="res://addons/dialogic/Editor/Images/Pieces/add-folder.svg" id="3_v1qnr"]
|
||||
|
||||
[sub_resource type="Image" id="Image_r5ayh"]
|
||||
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_oab13"]
|
||||
image = SubResource("Image_r5ayh")
|
||||
|
||||
[sub_resource type="Image" id="Image_2j4b6"]
|
||||
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_u1a6g"]
|
||||
image = SubResource("Image_2j4b6")
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_es2rd"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_4xgdx"]
|
||||
|
||||
[node name="CharacterEditor" type="Control"]
|
||||
self_modulate = Color(0, 0, 0, 1)
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("2")
|
||||
|
||||
[node name="Scroll" type="ScrollContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="Scroll"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
size_flags_stretch_ratio = 0.3
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="TopSection" type="HBoxContainer" parent="Scroll/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NameContainer" type="HBoxContainer" parent="Scroll/VBox/TopSection"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CharacterName" type="Label" parent="Scroll/VBox/TopSection/NameContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"DialogicTitle"
|
||||
text = "My Character"
|
||||
|
||||
[node name="NameTooltip" parent="Scroll/VBox/TopSection/NameContainer" instance=ExtResource("2_uhhqs")]
|
||||
layout_mode = 2
|
||||
tooltip_text = "This unique identifier is based on the file name. You can change it in the Reference Manager.
|
||||
Use this name in timelines to reference this character."
|
||||
texture = SubResource("ImageTexture_oab13")
|
||||
hint_text = "This unique identifier is based on the file name. You can change it in the Reference Manager.
|
||||
Use this name in timelines to reference this character."
|
||||
|
||||
[node name="MainSettingsCollapse" type="Button" parent="Scroll/VBox/TopSection"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 10
|
||||
size_flags_vertical = 4
|
||||
toggle_mode = true
|
||||
text = "Main Settings"
|
||||
icon = SubResource("ImageTexture_u1a6g")
|
||||
|
||||
[node name="MainHSplit" type="HSplitContainer" parent="Scroll/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="MainSettings" type="VBoxContainer" parent="Scroll/VBox/MainHSplit"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 0.2
|
||||
|
||||
[node name="MainSettingsTitle" type="Label" parent="Scroll/VBox/MainHSplit/MainSettings"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_type_variation = &"DialogicSubTitle"
|
||||
text = "Main Settings"
|
||||
|
||||
[node name="MainSettingsScroll" type="ScrollContainer" parent="Scroll/VBox/MainHSplit/MainSettings"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxEmpty_es2rd")
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="MainSettingsSections" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/MainSettings/MainSettingsScroll"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Split" type="HSplitContainer" parent="Scroll/VBox/MainHSplit"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 0.2
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 0.2
|
||||
theme_override_constants/margin_bottom = 10
|
||||
|
||||
[node name="PortraitListSection" type="PanelContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_type_variation = &"DialogicPanelA"
|
||||
|
||||
[node name="Portraits" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PortraitsTitle" type="Label" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits"]
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"DialogicSubTitle"
|
||||
text = "Portraits"
|
||||
|
||||
[node name="PortraitListTools" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="AddPortraitButton" type="Button" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitListTools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Add portrait"
|
||||
icon = SubResource("ImageTexture_u1a6g")
|
||||
|
||||
[node name="AddPortraitGroupButton" type="Button" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitListTools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Add Group"
|
||||
icon = ExtResource("3_v1qnr")
|
||||
|
||||
[node name="ImportPortraitsButton" type="Button" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitListTools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Import images from folder"
|
||||
icon = SubResource("ImageTexture_u1a6g")
|
||||
|
||||
[node name="PortraitSearch" type="LineEdit" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitListTools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 4
|
||||
placeholder_text = "Search"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
right_icon = SubResource("ImageTexture_u1a6g")
|
||||
caret_blink = true
|
||||
caret_blink_interval = 0.5
|
||||
|
||||
[node name="PortraitTreePanel" type="PanelContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxEmpty_4xgdx")
|
||||
|
||||
[node name="PortraitTree" type="Tree" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
allow_rmb_select = true
|
||||
hide_root = true
|
||||
drop_mode_flags = 3
|
||||
script = ExtResource("2_vad0i")
|
||||
|
||||
[node name="PortraitRightClickMenu" type="PopupMenu" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel/PortraitTree"]
|
||||
size = Vector2i(118, 100)
|
||||
item_count = 5
|
||||
item_0/text = "Rename"
|
||||
item_0/icon = SubResource("ImageTexture_oab13")
|
||||
item_0/id = 2
|
||||
item_1/text = "Duplicate"
|
||||
item_1/icon = SubResource("ImageTexture_oab13")
|
||||
item_1/id = 0
|
||||
item_2/text = "Delete"
|
||||
item_2/icon = SubResource("ImageTexture_oab13")
|
||||
item_2/id = 1
|
||||
item_3/text = ""
|
||||
item_3/id = 3
|
||||
item_3/separator = true
|
||||
item_4/text = "Make Default"
|
||||
item_4/icon = SubResource("ImageTexture_oab13")
|
||||
item_4/id = 4
|
||||
|
||||
[node name="PortraitChangeInfo" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PortraitChangeWarning" type="Label" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitChangeInfo"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
text = "Some portraits were renamed. Make sure no references broke!"
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="ReferenceMangerButton" type="Button" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitChangeInfo"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
text = "Reference
|
||||
Manager"
|
||||
|
||||
[node name="RightSection2" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/Split"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
size_flags_stretch_ratio = 0.5
|
||||
|
||||
[node name="Spacer" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2"]
|
||||
custom_minimum_size = Vector2(0, 10)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RightSection" type="SplitContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
size_flags_stretch_ratio = 0.5
|
||||
vertical = true
|
||||
|
||||
[node name="PortraitPreviewSection" type="Panel" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection"]
|
||||
unique_name_in_owner = true
|
||||
show_behind_parent = true
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_type_variation = &"DialogicPanelB"
|
||||
|
||||
[node name="ClipRect" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
|
||||
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="Node2D" type="Node2D" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/ClipRect"]
|
||||
position = Vector2(13, 17)
|
||||
|
||||
[node name="RealPreviewPivot" type="Sprite2D" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/ClipRect/Node2D"]
|
||||
unique_name_in_owner = true
|
||||
position = Vector2(326.5, 267)
|
||||
texture = SubResource("ImageTexture_u1a6g")
|
||||
|
||||
[node name="ScenePreviewWarning" type="Label" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -143.0
|
||||
offset_top = -44.5
|
||||
offset_right = 143.0
|
||||
offset_bottom = 85.5
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "Custom scenes can only be viewed in \"Full mode\" if they are in @tool mode and override _get_covered_rect"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 3
|
||||
metadata/_edit_layout_mode = 1
|
||||
|
||||
[node name="PreviewReal" type="CenterContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -302.0
|
||||
offset_top = -80.0
|
||||
offset_right = 302.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
mouse_filter = 2
|
||||
metadata/_edit_layout_mode = 1
|
||||
|
||||
[node name="Control" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/PreviewReal"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RealSizeRemotePivotTransform" type="RemoteTransform2D" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/PreviewReal/Control"]
|
||||
unique_name_in_owner = true
|
||||
remote_path = NodePath("../../../ClipRect/Node2D/RealPreviewPivot")
|
||||
update_rotation = false
|
||||
update_scale = false
|
||||
|
||||
[node name="FullPreviewAvailableRect" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 10.0
|
||||
offset_top = 28.0
|
||||
offset_right = -10.0
|
||||
offset_bottom = -16.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
metadata/_edit_layout_mode = 1
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 10
|
||||
anchor_right = 1.0
|
||||
offset_left = 6.0
|
||||
offset_top = 7.0
|
||||
offset_right = -6.0
|
||||
offset_bottom = 43.0
|
||||
grow_horizontal = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="PreviewLabel" type="Label" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
show_behind_parent = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 0
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
text = "No portrait to preview."
|
||||
text_overrun_behavior = 1
|
||||
|
||||
[node name="FitPreview_Toggle" type="Button" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 0
|
||||
tooltip_text = "Real scale"
|
||||
focus_mode = 0
|
||||
toggle_mode = true
|
||||
button_pressed = true
|
||||
icon = SubResource("ImageTexture_u1a6g")
|
||||
flat = true
|
||||
metadata/_edit_layout_mode = 1
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
size_flags_stretch_ratio = 0.75
|
||||
|
||||
[node name="Hbox" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PortraitSettingsTitle" type="Label" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox/Hbox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"DialogicSubTitle"
|
||||
text = "Portrait Settings"
|
||||
|
||||
[node name="SwitchPortraitSettingsPosition" type="Button" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox/Hbox"]
|
||||
unique_name_in_owner = true
|
||||
modulate = Color(1, 1, 1, 0.647059)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Switch position"
|
||||
focus_mode = 0
|
||||
icon = SubResource("ImageTexture_u1a6g")
|
||||
flat = true
|
||||
|
||||
[node name="Scroll" type="ScrollContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
size_flags_stretch_ratio = 0.4
|
||||
|
||||
[node name="PortraitSettingsSection" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox/Scroll"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
size_flags_stretch_ratio = 0.3
|
||||
|
||||
[node name="Spacer2" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2"]
|
||||
custom_minimum_size = Vector2(0, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NoCharacterScreen" type="ColorRect" parent="."]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
color = Color(0, 0, 0, 1)
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="NoCharacterScreen"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="NoCharacterScreen/CenterContainer"]
|
||||
custom_minimum_size = Vector2(250, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="NoCharacterScreen/CenterContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "No character opened.
|
||||
Create a character or double-click one in the file system dock."
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="CreateCharacterButton" type="Button" parent="NoCharacterScreen/CenterContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Create New Character"
|
||||
|
||||
[connection signal="toggled" from="Scroll/VBox/TopSection/MainSettingsCollapse" to="." method="_on_main_settings_collapse_toggled"]
|
||||
[connection signal="item_mouse_selected" from="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel/PortraitTree" to="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel/PortraitTree" method="_on_item_mouse_selected"]
|
||||
[connection signal="index_pressed" from="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel/PortraitTree/PortraitRightClickMenu" to="." method="_on_portrait_right_click_menu_index_pressed"]
|
||||
[connection signal="pressed" from="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitChangeInfo/ReferenceMangerButton" to="." method="_on_reference_manger_button_pressed"]
|
||||
[connection signal="resized" from="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/FullPreviewAvailableRect" to="." method="_on_full_preview_available_rect_resized"]
|
||||
[connection signal="toggled" from="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/HBoxContainer/FitPreview_Toggle" to="." method="_on_fit_preview_toggle_toggled"]
|
||||
[connection signal="pressed" from="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox/Hbox/SwitchPortraitSettingsPosition" to="." method="_on_switch_portrait_settings_position_pressed"]
|
||||
[connection signal="pressed" from="NoCharacterScreen/CenterContainer/VBoxContainer/CreateCharacterButton" to="." method="_on_create_character_button_pressed"]
|
||||