Reflection-driven target dropdowns (SelectNode3D, HasCondition), Brain activeNode generalization with auto-restart, TweenMaterial slot-ownership fix, Behavior/ file reorg into Core/Movements/Receptors, LockOn/CameraLocker updates

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Rokojori 2026-10-09 10:39:14 +02:00
parent e2a1171e5e
commit ba487e326c
69 changed files with 2246 additions and 470 deletions

61
Icons/BehaviorBranch.svg Normal file
View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg4"
sodipodi:docname="BehaviorBranch.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="10.363534"
inkscape:cx="4.728117"
inkscape:cy="5.789531"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
id="rect401"
style="color:#000000;fill:#1de3ab;fill-opacity:1;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none;paint-order:markers stroke fill"
d="m 1.319336,0.0888674 c -0.674094,0 -1.248047,0.571999 -1.248047,1.246093 v 2.511719 h 2 V 2.0888674 H 3.81543 v -2 z m 10.84375,0 v 2 h 1.765625 v 1.757812 h 2 v -2.511719 c 0,-0.674094 -0.573952,-1.246093 -1.248047,-1.246093 z M 0.071289,12.194336 v 2.46875 c 0,0.674094 0.573953,1.248047 1.248047,1.248047 H 3.81543 v -2 H 2.071289 v -1.716797 z m 13.857422,0 v 1.716797 h -1.765625 v 2 h 2.517578 c 0.674095,0 1.248047,-0.573953 1.248047,-1.248047 v -2.46875 z" />
<path
id="path1539"
style="font-weight:bold;font-size:15.624px;line-height:1;font-family:'MS Outlook';-inkscape-font-specification:'MS Outlook Bold';fill:#1de3ab;fill-opacity:1;stroke-width:0.29295;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
d="m 51.913678,-11.337595 c -0.708287,0 -1.359413,0.08855 -1.953125,0.265625 -0.593711,0.177072 -1.145866,0.406268 -1.65625,0.6875 l 0.828125,1.671875 c 0.447888,-0.229152 0.885445,-0.411467 1.3125,-0.546875 0.427056,-0.135408 0.859404,-0.203125 1.296875,-0.203125 0.437472,0 0.776058,0.088553 1.015625,0.265625 0.249984,0.177072 0.373047,0.427104 0.373047,0.75 0,0.333312 -0.10418,0.625016 -0.3125,0.875 -0.197904,0.239568 -0.529327,0.526063 -0.998047,0.859375 -0.531215,0.385392 -0.906264,0.76044 -1.125,1.125 -0.218735,0.354144 -0.328125,0.786492 -0.328125,1.296875 v 0.5625002 h 2.015625 V -4.181345 c 0,-0.20832 0.02605,-0.385426 0.07813,-0.53125 0.0625,-0.15624 0.171885,-0.307301 0.328125,-0.453125 0.166656,-0.15624 0.404318,-0.348973 0.716797,-0.578125 0.406224,-0.291648 0.744809,-0.572934 1.015625,-0.84375 0.270816,-0.270816 0.473967,-0.56252 0.609375,-0.875 0.135408,-0.31248 0.203125,-0.682319 0.203125,-1.109375 0,-0.802031 -0.302122,-1.463576 -0.90625,-1.984375 -0.593711,-0.520799 -1.430409,-0.78125 -2.513672,-0.78125 z m -0.453125,8.9218752 c -0.374975,0 -0.692725,0.09897 -0.953125,0.296875 -0.260399,0.187488 -0.390625,0.520864 -0.390625,1 0,0.45830298 0.130226,0.79167998 0.390625,0.99999998 0.2604,0.197904 0.57815,0.296875 0.953125,0.296875 0.36456,0 0.677101,-0.09897 0.9375,-0.296875 0.2604,-0.20832 0.390625,-0.541697 0.390625,-0.99999998 0,-0.479136 -0.130225,-0.812512 -0.390625,-1 -0.260399,-0.197904 -0.57294,-0.296875 -0.9375,-0.296875 z" />
<g
id="g96554"
transform="matrix(0.75262015,0,0,0.75262015,1.9507002,2.0121453)">
<ellipse
style="fill:#ff82db;fill-opacity:1;stroke:none;stroke-width:7.27012;stroke-linecap:square;stroke-linejoin:round;paint-order:stroke fill markers"
id="path92548"
cx="5.1813359"
cy="8.1338291"
rx="4.3999071"
ry="5.1538987" />
<ellipse
style="fill:#ff82db;fill-opacity:1;stroke:none;stroke-width:7.27012;stroke-linecap:square;stroke-linejoin:round;paint-order:stroke fill markers"
id="circle92550"
cx="10.589555"
cy="8.1338291"
rx="4.3999071"
ry="5.1538987" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

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

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg4"
sodipodi:docname="BehaviorSequence.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="20.727068"
inkscape:cx="-0.38596872"
inkscape:cy="6.0307613"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
id="path1539"
style="font-weight:bold;font-size:15.624px;line-height:1;font-family:'MS Outlook';-inkscape-font-specification:'MS Outlook Bold';fill:#1de3ab;fill-opacity:1;stroke-width:0.29295;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
d="m 51.913678,-11.337595 c -0.708287,0 -1.359413,0.08855 -1.953125,0.265625 -0.593711,0.177072 -1.145866,0.406268 -1.65625,0.6875 l 0.828125,1.671875 c 0.447888,-0.229152 0.885445,-0.411467 1.3125,-0.546875 0.427056,-0.135408 0.859404,-0.203125 1.296875,-0.203125 0.437472,0 0.776058,0.088553 1.015625,0.265625 0.249984,0.177072 0.373047,0.427104 0.373047,0.75 0,0.333312 -0.10418,0.625016 -0.3125,0.875 -0.197904,0.239568 -0.529327,0.526063 -0.998047,0.859375 -0.531215,0.385392 -0.906264,0.76044 -1.125,1.125 -0.218735,0.354144 -0.328125,0.786492 -0.328125,1.296875 v 0.5625002 h 2.015625 V -4.181345 c 0,-0.20832 0.02605,-0.385426 0.07813,-0.53125 0.0625,-0.15624 0.171885,-0.307301 0.328125,-0.453125 0.166656,-0.15624 0.404318,-0.348973 0.716797,-0.578125 0.406224,-0.291648 0.744809,-0.572934 1.015625,-0.84375 0.270816,-0.270816 0.473967,-0.56252 0.609375,-0.875 0.135408,-0.31248 0.203125,-0.682319 0.203125,-1.109375 0,-0.802031 -0.302122,-1.463576 -0.90625,-1.984375 -0.593711,-0.520799 -1.430409,-0.78125 -2.513672,-0.78125 z m -0.453125,8.9218752 c -0.374975,0 -0.692725,0.09897 -0.953125,0.296875 -0.260399,0.187488 -0.390625,0.520864 -0.390625,1 0,0.45830298 0.130226,0.79167998 0.390625,0.99999998 0.2604,0.197904 0.57815,0.296875 0.953125,0.296875 0.36456,0 0.677101,-0.09897 0.9375,-0.296875 0.2604,-0.20832 0.390625,-0.541697 0.390625,-0.99999998 0,-0.479136 -0.130225,-0.812512 -0.390625,-1 -0.260399,-0.197904 -0.57294,-0.296875 -0.9375,-0.296875 z" />
<g
id="g102401"
transform="translate(0.15792213,0.18000243)">
<g
id="g96554"
transform="matrix(0.59356973,0,0,0.59356973,6.7183722,-1.2770906)">
<ellipse
style="fill:#ff82db;fill-opacity:1;stroke:none;stroke-width:7.27012;stroke-linecap:square;stroke-linejoin:round;paint-order:stroke fill markers"
id="path92548"
cx="5.1813359"
cy="8.1338291"
rx="4.3999071"
ry="5.1538987" />
<ellipse
style="fill:#ff82db;fill-opacity:1;stroke:none;stroke-width:7.27012;stroke-linecap:square;stroke-linejoin:round;paint-order:stroke fill markers"
id="circle92550"
cx="10.589555"
cy="8.1338291"
rx="4.3999071"
ry="5.1538987" />
</g>
<g
id="g102393"
transform="matrix(1.0857792,0,0,1.0857792,-5.1777633,-1.0374856)">
<path
sodipodi:type="star"
style="fill:none;fill-opacity:1;stroke:#f7b200;stroke-width:1.93888;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill"
id="path120048"
inkscape:flatsided="false"
sodipodi:sides="5"
sodipodi:cx="-8.2018356"
sodipodi:cy="-2.1710742"
sodipodi:r1="7.9176526"
sodipodi:r2="4.7268386"
sodipodi:arg1="-1.5707963"
sodipodi:arg2="-0.9424778"
inkscape:rounded="0"
inkscape:randomized="0"
d="m -8.2018354,-10.088727 2.7783658,4.0935601 4.75176914,1.3774037 -3.03464454,3.90736248 0.1583886,4.94484222 -4.6538792,-1.6786771 -4.6538794,1.6786768 0.158389,-4.94484188 -3.034645,-3.90736292 4.751769,-1.3774033 z"
inkscape:transform-center-y="-0.91462472"
transform="matrix(0.61233589,0,0,0.61233589,15.058621,11.720562)" />
<path
sodipodi:type="star"
style="fill:#f7b200;fill-opacity:1;stroke:none;stroke-width:2.56963;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="path855"
inkscape:flatsided="false"
sodipodi:sides="5"
sodipodi:cx="-8.2018356"
sodipodi:cy="-2.1710742"
sodipodi:r1="7.9176526"
sodipodi:r2="4.5684857"
sodipodi:arg1="-1.5707963"
sodipodi:arg2="-0.94247777"
inkscape:rounded="0"
inkscape:randomized="0"
d="m -8.2018354,-10.088727 2.6852884,4.2216703 4.84484654,1.2492935 -3.18524704,3.85842889 0.3089911,4.99377581 -4.6538794,-1.8370299 -4.6538792,1.8370296 0.308991,-4.99377573 -3.185247,-3.85842907 4.844847,-1.2492932 z"
inkscape:transform-center-y="-0.30053938"
transform="matrix(0.2012094,0,0,0.2012094,11.471952,11.056661)" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c0bg8q1qhta8d"
path="res://.godot/imported/BehaviorSequence.svg-e83c81b196d7953a5989711a8e3762a9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/rokojori_action_library/Icons/BehaviorSequence.svg"
dest_files=["res://.godot/imported/BehaviorSequence.svg-e83c81b196d7953a5989711a8e3762a9.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

77
Icons/Brain.svg Normal file
View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg4"
sodipodi:docname="Brain.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8">
<linearGradient
inkscape:collect="never"
id="linearGradient114405">
<stop
style="stop-color:#c7169f;stop-opacity:1;"
offset="0"
id="stop114401" />
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="1"
id="stop114403" />
</linearGradient>
<radialGradient
inkscape:collect="never"
xlink:href="#linearGradient114405"
id="radialGradient114407"
cx="8"
cy="8"
fx="8"
fy="8"
r="8"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="3.6640625"
inkscape:cx="-1.9104478"
inkscape:cy="2.45629"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
id="path115146"
style="fill:#ff82db;fill-opacity:1;stroke:#000000;stroke-width:3.4861;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
d="M 5.8309076,3.8548992 C 5.538504,3.8549729 5.1211737,3.9611482 5.1194631,4.2639761 4.9425069,3.9804441 4.4185358,4.1843523 4.1624382,4.3457257 3.055579,5.043185 2.3031667,6.4126815 2.3031341,7.9880008 c 2.7e-5,1.3064477 0.7226271,3.2917552 1.5534037,3.0616162 -0.1864958,0.459405 1.1412645,1.071276 1.9743698,1.071486 0.7865919,-0.0031 1.5497544,0.176332 2.1682659,-0.392928 0.6189579,0.569668 1.3827609,0.390289 2.1699195,0.392928 1.088731,-2.75e-4 2.642337,-0.900404 2.472966,-1.776222 0.564685,0.163911 1.054786,-1.3498657 1.054807,-2.3568802 C 13.696842,6.8660251 13.315168,5.8484548 12.695608,5.1037242 12.488198,4.8544121 11.969439,4.4423021 11.708732,4.6418417 11.782246,4.2189562 10.839029,3.8550683 10.169093,3.8548992 9.3845365,3.8583179 8.6233284,3.8184841 8.005789,4.385337 7.3851242,3.815628 6.6194341,3.8552965 5.8309076,3.8548992 Z"
sodipodi:nodetypes="ccscccccccscccc" />
<path
id="rect401"
style="color:#000000;fill:#1de3ab;fill-opacity:1;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none;paint-order:markers stroke fill"
d="m 44.929225,-13.918847 c -0.674094,0 -1.248047,0.571999 -1.248047,1.246093 v 2.511719 h 2 v -1.757812 h 1.744141 v -2 z m 10.84375,0 v 2 H 57.5386 v 1.757812 h 2 v -2.511719 c 0,-0.674094 -0.573952,-1.246093 -1.248047,-1.246093 z M 43.681178,-1.8133788 v 2.46874998 c 0,0.67409402 0.573953,1.24804702 1.248047,1.24804702 h 2.496094 V -0.09658182 H 45.681178 V -1.8133788 Z m 13.857422,0 v 1.71679698 H 55.772975 V 1.9034182 h 2.517578 c 0.674095,0 1.248047,-0.573953 1.248047,-1.24804702 V -1.8133788 Z" />
<path
id="path1539"
style="font-weight:bold;font-size:15.624px;line-height:1;font-family:'MS Outlook';-inkscape-font-specification:'MS Outlook Bold';fill:#1de3ab;fill-opacity:1;stroke-width:0.29295;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
d="m 51.913678,-11.337595 c -0.708287,0 -1.359413,0.08855 -1.953125,0.265625 -0.593711,0.177072 -1.145866,0.406268 -1.65625,0.6875 l 0.828125,1.671875 c 0.447888,-0.229152 0.885445,-0.411467 1.3125,-0.546875 0.427056,-0.135408 0.859404,-0.203125 1.296875,-0.203125 0.437472,0 0.776058,0.088553 1.015625,0.265625 0.249984,0.177072 0.373047,0.427104 0.373047,0.75 0,0.333312 -0.10418,0.625016 -0.3125,0.875 -0.197904,0.239568 -0.529327,0.526063 -0.998047,0.859375 -0.531215,0.385392 -0.906264,0.76044 -1.125,1.125 -0.218735,0.354144 -0.328125,0.786492 -0.328125,1.296875 v 0.5625002 h 2.015625 V -4.181345 c 0,-0.20832 0.02605,-0.385426 0.07813,-0.53125 0.0625,-0.15624 0.171885,-0.307301 0.328125,-0.453125 0.166656,-0.15624 0.404318,-0.348973 0.716797,-0.578125 0.406224,-0.291648 0.744809,-0.572934 1.015625,-0.84375 0.270816,-0.270816 0.473967,-0.56252 0.609375,-0.875 0.135408,-0.31248 0.203125,-0.682319 0.203125,-1.109375 0,-0.802031 -0.302122,-1.463576 -0.90625,-1.984375 -0.593711,-0.520799 -1.430409,-0.78125 -2.513672,-0.78125 z m -0.453125,8.9218752 c -0.374975,0 -0.692725,0.09897 -0.953125,0.296875 -0.260399,0.187488 -0.390625,0.520864 -0.390625,1 0,0.45830298 0.130226,0.79167998 0.390625,0.99999998 0.2604,0.197904 0.57815,0.296875 0.953125,0.296875 0.36456,0 0.677101,-0.09897 0.9375,-0.296875 0.2604,-0.20832 0.390625,-0.541697 0.390625,-0.99999998 0,-0.479136 -0.130225,-0.812512 -0.390625,-1 -0.260399,-0.197904 -0.57294,-0.296875 -0.9375,-0.296875 z" />
<path
id="path92548"
style="fill:#ff82db;fill-opacity:1;stroke:#fbfbfb;stroke-width:1.74305;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
d="M 5.8309076,3.8548992 C 5.538504,3.8549729 5.0729276,4.0817634 5.4330627,5.1324058 4.9666299,4.1010593 4.4185358,4.1843523 4.1624382,4.3457257 3.055579,5.043185 2.3031667,6.4126815 2.3031341,7.9880008 c 2.7e-5,1.3064477 0.8432423,3.1952632 2.1564798,2.3379252 -0.8619411,1.158973 0.5381884,1.794967 1.3712937,1.795177 0.7865919,-0.0031 1.863354,0.176332 2.1923889,-1.116619 0.2329892,1.269236 1.3586379,1.11398 2.1457965,1.116619 1.088731,-2.75e-4 2.618214,-1.141634 1.894013,-2.2345599 0.974777,0.6704949 1.633739,-0.8915278 1.63376,-1.8985423 C 13.696842,6.8660251 13.315168,5.8484548 12.695608,5.1037242 12.488198,4.8544121 12.1383,4.2493177 11.346886,4.8830722 11.951107,3.8571105 10.736684,3.7015502 10.066748,3.7013811 8.7534064,3.7047998 8.2763238,4.3033695 8.1022812,5.1090284 7.6692497,3.8740742 7.1482187,3.6164905 5.8309076,3.8548992 Z"
sodipodi:nodetypes="ccscccccccscccc" />
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

43
Icons/Brain.svg.import Normal file
View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ddywe4nqkppim"
path="res://.godot/imported/Brain.svg-22061c8f9edbd3ff520fcf62d8a1f665.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/rokojori_action_library/Icons/Brain.svg"
dest_files=["res://.godot/imported/Brain.svg-22061c8f9edbd3ff520fcf62d8a1f665.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

51
Icons/BrainControl.svg Normal file
View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg4"
sodipodi:docname="BrainControl.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="20.727068"
inkscape:cx="1.8333515"
inkscape:cy="4.7763631"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
id="rect401"
style="color:#000000;fill:#1de3ab;fill-opacity:1;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none;paint-order:markers stroke fill"
d="m 44.929225,-13.918847 c -0.674094,0 -1.248047,0.571999 -1.248047,1.246093 v 2.511719 h 2 v -1.757812 h 1.744141 v -2 z m 10.84375,0 v 2 H 57.5386 v 1.757812 h 2 v -2.511719 c 0,-0.674094 -0.573952,-1.246093 -1.248047,-1.246093 z M 43.681178,-1.8133788 v 2.46874998 c 0,0.67409402 0.573953,1.24804702 1.248047,1.24804702 h 2.496094 V -0.09658182 H 45.681178 V -1.8133788 Z m 13.857422,0 v 1.71679698 H 55.772975 V 1.9034182 h 2.517578 c 0.674095,0 1.248047,-0.573953 1.248047,-1.24804702 V -1.8133788 Z" />
<path
id="path1539"
style="font-weight:bold;font-size:15.624px;line-height:1;font-family:'MS Outlook';-inkscape-font-specification:'MS Outlook Bold';fill:#1de3ab;fill-opacity:1;stroke-width:0.29295;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
d="m 51.913678,-11.337595 c -0.708287,0 -1.359413,0.08855 -1.953125,0.265625 -0.593711,0.177072 -1.145866,0.406268 -1.65625,0.6875 l 0.828125,1.671875 c 0.447888,-0.229152 0.885445,-0.411467 1.3125,-0.546875 0.427056,-0.135408 0.859404,-0.203125 1.296875,-0.203125 0.437472,0 0.776058,0.088553 1.015625,0.265625 0.249984,0.177072 0.373047,0.427104 0.373047,0.75 0,0.333312 -0.10418,0.625016 -0.3125,0.875 -0.197904,0.239568 -0.529327,0.526063 -0.998047,0.859375 -0.531215,0.385392 -0.906264,0.76044 -1.125,1.125 -0.218735,0.354144 -0.328125,0.786492 -0.328125,1.296875 v 0.5625002 h 2.015625 V -4.181345 c 0,-0.20832 0.02605,-0.385426 0.07813,-0.53125 0.0625,-0.15624 0.171885,-0.307301 0.328125,-0.453125 0.166656,-0.15624 0.404318,-0.348973 0.716797,-0.578125 0.406224,-0.291648 0.744809,-0.572934 1.015625,-0.84375 0.270816,-0.270816 0.473967,-0.56252 0.609375,-0.875 0.135408,-0.31248 0.203125,-0.682319 0.203125,-1.109375 0,-0.802031 -0.302122,-1.463576 -0.90625,-1.984375 -0.593711,-0.520799 -1.430409,-0.78125 -2.513672,-0.78125 z m -0.453125,8.9218752 c -0.374975,0 -0.692725,0.09897 -0.953125,0.296875 -0.260399,0.187488 -0.390625,0.520864 -0.390625,1 0,0.45830298 0.130226,0.79167998 0.390625,0.99999998 0.2604,0.197904 0.57815,0.296875 0.953125,0.296875 0.36456,0 0.677101,-0.09897 0.9375,-0.296875 0.2604,-0.20832 0.390625,-0.541697 0.390625,-0.99999998 0,-0.479136 -0.130225,-0.812512 -0.390625,-1 -0.260399,-0.197904 -0.57294,-0.296875 -0.9375,-0.296875 z" />
<path
id="path92548"
style="fill:#ff82db;fill-opacity:1;stroke:none;stroke-width:1.55798;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
d="M 6.0887647,4.3576783 A 3.1098158,3.6427305 0 0 0 2.9786072,8.0006902 3.1098158,3.6427305 0 0 0 6.0887647,11.642322 3.1098158,3.6427305 0 0 0 8.0006904,10.862367 3.1098158,3.6427305 0 0 0 9.9112359,11.642322 3.1098158,3.6427305 0 0 0 13.021393,8.0006902 3.1098158,3.6427305 0 0 0 9.9112359,4.3576783 3.1098158,3.6427305 0 0 0 8.0062121,5.1321117 3.1098158,3.6427305 0 0 0 6.0887647,4.3576783 Z" />
<path
id="path103023"
style="color:#000000;fill:#ffb801;fill-opacity:1;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none;paint-order:markers stroke fill"
d="m 1.319336,0.088867 c -0.674094,0 -1.248047,0.571999 -1.248047,1.246093 v 2.511719 h 2 V 2.088867 H 3.81543 v -2 z m 10.84375,0 v 2 h 1.765625 v 1.757812 h 2 V 1.33496 c 0,-0.674094 -0.573952,-1.246093 -1.248047,-1.246093 z M 0.071289,12.194336 v 2.468749 c 0,0.674095 0.573953,1.248048 1.248047,1.248048 H 3.81543 V 13.911132 H 2.071289 v -1.716796 z m 13.857422,0 v 1.716796 h -1.765625 v 2.000001 h 2.517578 c 0.674095,0 1.248047,-0.573953 1.248047,-1.248048 v -2.468749 z" />
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c37yfx3ryvoxu"
path="res://.godot/imported/BrainControl.svg-0da1cfbf5e1e82c6dfa220b6df44195a.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/rokojori_action_library/Icons/BrainControl.svg"
dest_files=["res://.godot/imported/BrainControl.svg-0da1cfbf5e1e82c6dfa220b6df44195a.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

64
Icons/Receptor.svg Normal file
View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg4"
sodipodi:docname="Receptor.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="29.3125"
inkscape:cx="9.108742"
inkscape:cy="11.633262"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
id="path1539"
style="font-weight:bold;font-size:15.624px;line-height:1;font-family:'MS Outlook';-inkscape-font-specification:'MS Outlook Bold';fill:#1de3ab;fill-opacity:1;stroke-width:0.29295;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
d="m 51.913678,-11.337595 c -0.708287,0 -1.359413,0.08855 -1.953125,0.265625 -0.593711,0.177072 -1.145866,0.406268 -1.65625,0.6875 l 0.828125,1.671875 c 0.447888,-0.229152 0.885445,-0.411467 1.3125,-0.546875 0.427056,-0.135408 0.859404,-0.203125 1.296875,-0.203125 0.437472,0 0.776058,0.088553 1.015625,0.265625 0.249984,0.177072 0.373047,0.427104 0.373047,0.75 0,0.333312 -0.10418,0.625016 -0.3125,0.875 -0.197904,0.239568 -0.529327,0.526063 -0.998047,0.859375 -0.531215,0.385392 -0.906264,0.76044 -1.125,1.125 -0.218735,0.354144 -0.328125,0.786492 -0.328125,1.296875 v 0.5625002 h 2.015625 V -4.181345 c 0,-0.20832 0.02605,-0.385426 0.07813,-0.53125 0.0625,-0.15624 0.171885,-0.307301 0.328125,-0.453125 0.166656,-0.15624 0.404318,-0.348973 0.716797,-0.578125 0.406224,-0.291648 0.744809,-0.572934 1.015625,-0.84375 0.270816,-0.270816 0.473967,-0.56252 0.609375,-0.875 0.135408,-0.31248 0.203125,-0.682319 0.203125,-1.109375 0,-0.802031 -0.302122,-1.463576 -0.90625,-1.984375 -0.593711,-0.520799 -1.430409,-0.78125 -2.513672,-0.78125 z m -0.453125,8.9218752 c -0.374975,0 -0.692725,0.09897 -0.953125,0.296875 -0.260399,0.187488 -0.390625,0.520864 -0.390625,1 0,0.45830298 0.130226,0.79167998 0.390625,0.99999998 0.2604,0.197904 0.57815,0.296875 0.953125,0.296875 0.36456,0 0.677101,-0.09897 0.9375,-0.296875 0.2604,-0.20832 0.390625,-0.541697 0.390625,-0.99999998 0,-0.479136 -0.130225,-0.812512 -0.390625,-1 -0.260399,-0.197904 -0.57294,-0.296875 -0.9375,-0.296875 z" />
<ellipse
style="fill:#ff82db;fill-opacity:1;stroke:none;stroke-width:9.94378;stroke-linecap:square;stroke-linejoin:round;paint-order:stroke fill markers"
id="path92548"
cx="7.8807526"
cy="8.2403135"
rx="6.6163163"
ry="6.4118476" />
<g
id="g100943"
transform="matrix(0.84582226,0,0,0.84582226,1.0549805,0.17352877)">
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:7.54947;stroke-linecap:square;stroke-linejoin:round;paint-order:stroke fill markers"
id="ellipse100204"
cx="7.9489827"
cy="8.2061987"
rx="5.0232086"
ry="4.8679729" />
<ellipse
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:4.70623;stroke-linecap:square;stroke-linejoin:round;paint-order:stroke fill markers"
id="ellipse100206"
cx="7.7077522"
cy="7.3860149"
rx="3.131393"
ry="3.0346215" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

43
Icons/Receptor.svg.import Normal file
View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://jusswlaofkgc"
path="res://.godot/imported/Receptor.svg-3c1cccf5229247e268d12db0af1e1abb.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/rokojori_action_library/Icons/Receptor.svg"
dest_files=["res://.godot/imported/Receptor.svg-3c1cccf5229247e268d12db0af1e1abb.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

72
Icons/ReceptorBranch.svg Normal file
View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg4"
sodipodi:docname="ReceptorBranch.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="20.727068"
inkscape:cx="12.785214"
inkscape:cy="15.583488"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
id="path1539"
style="font-weight:bold;font-size:15.624px;line-height:1;font-family:'MS Outlook';-inkscape-font-specification:'MS Outlook Bold';fill:#1de3ab;fill-opacity:1;stroke-width:0.29295;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"
d="m 51.913678,-11.337595 c -0.708287,0 -1.359413,0.08855 -1.953125,0.265625 -0.593711,0.177072 -1.145866,0.406268 -1.65625,0.6875 l 0.828125,1.671875 c 0.447888,-0.229152 0.885445,-0.411467 1.3125,-0.546875 0.427056,-0.135408 0.859404,-0.203125 1.296875,-0.203125 0.437472,0 0.776058,0.088553 1.015625,0.265625 0.249984,0.177072 0.373047,0.427104 0.373047,0.75 0,0.333312 -0.10418,0.625016 -0.3125,0.875 -0.197904,0.239568 -0.529327,0.526063 -0.998047,0.859375 -0.531215,0.385392 -0.906264,0.76044 -1.125,1.125 -0.218735,0.354144 -0.328125,0.786492 -0.328125,1.296875 v 0.5625002 h 2.015625 V -4.181345 c 0,-0.20832 0.02605,-0.385426 0.07813,-0.53125 0.0625,-0.15624 0.171885,-0.307301 0.328125,-0.453125 0.166656,-0.15624 0.404318,-0.348973 0.716797,-0.578125 0.406224,-0.291648 0.744809,-0.572934 1.015625,-0.84375 0.270816,-0.270816 0.473967,-0.56252 0.609375,-0.875 0.135408,-0.31248 0.203125,-0.682319 0.203125,-1.109375 0,-0.802031 -0.302122,-1.463576 -0.90625,-1.984375 -0.593711,-0.520799 -1.430409,-0.78125 -2.513672,-0.78125 z m -0.453125,8.9218752 c -0.374975,0 -0.692725,0.09897 -0.953125,0.296875 -0.260399,0.187488 -0.390625,0.520864 -0.390625,1 0,0.45830298 0.130226,0.79167998 0.390625,0.99999998 0.2604,0.197904 0.57815,0.296875 0.953125,0.296875 0.36456,0 0.677101,-0.09897 0.9375,-0.296875 0.2604,-0.20832 0.390625,-0.541697 0.390625,-0.99999998 0,-0.479136 -0.130225,-0.812512 -0.390625,-1 -0.260399,-0.197904 -0.57294,-0.296875 -0.9375,-0.296875 z" />
<g
id="g108125"
transform="matrix(0.79163872,0,0,0.79163872,1.7612911,1.4766487)">
<ellipse
style="fill:#ff82db;fill-opacity:1;stroke:none;stroke-width:9.94378;stroke-linecap:square;stroke-linejoin:round;paint-order:stroke fill markers"
id="path92548"
cx="7.8807526"
cy="8.2403135"
rx="6.6163163"
ry="6.4118476" />
<g
id="g100943"
transform="matrix(0.84582226,0,0,0.84582226,1.0549805,0.17352877)">
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:7.54947;stroke-linecap:square;stroke-linejoin:round;paint-order:stroke fill markers"
id="ellipse100204"
cx="7.9489827"
cy="8.2061987"
rx="5.0232086"
ry="4.8679729" />
<ellipse
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:4.70623;stroke-linecap:square;stroke-linejoin:round;paint-order:stroke fill markers"
id="ellipse100206"
cx="7.7077522"
cy="7.3860149"
rx="3.131393"
ry="3.0346215" />
</g>
</g>
<path
id="path103023"
style="color:#000000;fill:#1de3ab;fill-opacity:1;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none;paint-order:markers stroke fill"
d="m 1.319336,0.088867 c -0.674094,0 -1.248047,0.571999 -1.248047,1.246093 v 2.511719 h 2 V 2.088867 H 3.81543 v -2 z m 10.84375,0 v 2 h 1.765625 v 1.757812 h 2 V 1.33496 c 0,-0.674094 -0.573952,-1.246093 -1.248047,-1.246093 z M 0.071289,12.194336 v 2.468749 c 0,0.674095 0.573953,1.248048 1.248047,1.248048 H 3.81543 V 13.911132 H 2.071289 v -1.716796 z m 13.857422,0 v 1.716796 h -1.765625 v 2.000001 h 2.517578 c 0.674095,0 1.248047,-0.573953 1.248047,-1.248048 v -2.468749 z" />
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

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

View File

@ -18,7 +18,9 @@ namespace Rokojori
protected override void _OnTrigger() protected override void _OnTrigger()
{ {
if ( lookFrom == null || lookTarget == null ) if ( lookFrom == null || lookTarget == null ||
! IsInstanceValid( lookFrom ) || ! IsInstanceValid( lookTarget )
)
{ {
return; return;
} }

View File

@ -0,0 +1,141 @@
using Godot;
using System.Collections.Generic;
using System.Reflection;
using Rokojori.Extensions;
namespace Rokojori
{
[GlobalClass, Tool ]
public partial class SelectNode3D : Action
{
[Export]
public Selector selector;
bool _enteredTree = false;
public override void _EnterTree()
{
base._EnterTree();
_enteredTree = true;
}
Node _target;
[Export]
public Node target
{
get => _target;
set
{
if ( _target == value )
{
return;
}
_target = value;
// Deserialization sets exported properties before _EnterTree fires, so this
// only triggers for genuine interactive edits in the editor, never for a scene
// being loaded (regardless of property order) or for runtime reassignment.
if ( _enteredTree && Engine.IsEditorHint() )
{
targetMember = "";
#if TOOLS
NotifyPropertyListChanged();
#endif
}
}
}
[Export]
public string targetMember;
protected override void _OnTrigger()
{
if ( target == null )
{
return;
}
Node3D node3D = null;
if ( selector == null )
{
ReflectionHelper.SetValue<Node3D>( target, targetMember, node3D );
this.LogInfo( "Set Null:", HierarchyName.Of( target ), targetMember );
return;
}
var selectedNode = Unique<SelectManager>.Get().Select( selector );
if ( selectedNode is Selectable )
{
var go = selectedNode.GetParent<GameObject3D>();
if ( go != null )
{
node3D = go.body;
}
}
ReflectionHelper.SetValue<Node3D>( target, targetMember, node3D );
}
#if TOOLS
public override void _ValidateProperty( Godot.Collections.Dictionary property )
{
if ( property[ "name" ].AsString() != nameof( targetMember ) )
{
return;
}
var members = GetTargetNode3DMemberNames();
property[ "hint" ] = (long) PropertyHint.Enum;
property[ "hint_string" ] = members.Count == 0
? "(Invalid Target)"
: "None," + string.Join( ",", members );
}
List<string> GetTargetNode3DMemberNames()
{
var names = new List<string>();
if ( target == null )
{
return names;
}
var infos = ReflectionHelper.GetDataMemberInfos( target, BindingFlags.Public | BindingFlags.Instance );
foreach ( var info in infos )
{
if ( ! info.IsDefined( typeof( ExportAttribute ) ) )
{
continue;
}
var memberType = info.GetMemberType();
if ( memberType == null || ! memberType.IsAssignableFrom( typeof( Node3D ) ) )
{
continue;
}
names.Add( info.Name );
}
return names;
}
#endif
}
}

View File

@ -0,0 +1 @@
uid://76pepn06ofq5

View File

@ -30,10 +30,13 @@ namespace Rokojori
return; return;
} }
var value = ReflectionHelper.GetValue<Node3D>( source, sourceMember ); var value = sourceMember == null || sourceMember == "" ? (Node3D) source :
ReflectionHelper.GetValue<Node3D>( source, sourceMember );
ReflectionHelper.SetValue<Node3D>( target, targetMember, value ); ReflectionHelper.SetValue<Node3D>( target, targetMember, value );
} }
} }
} }

View File

@ -23,7 +23,7 @@ public partial class NumberValue : Node
return GetType().Name + " { value = " + _value._DDD() + ", frozen = " + _frozen + " }"; return GetType().Name + " { value = " + _value._DDD() + ", frozen = " + _frozen + " }";
} }
void _OnValueChange( double v ) protected virtual void _OnValueChange( double v )
{ {
if ( _frozen ) if ( _frozen )
{ {

View File

@ -13,6 +13,9 @@ namespace Rokojori
[Export] [Export]
public Material material; public Material material;
[Export]
public GeometryInstance3D instanceTarget;
[Export] [Export]
public FloatPropertyName propertyName; public FloatPropertyName propertyName;
@ -59,11 +62,46 @@ namespace Rokojori
protected override void _OnTrigger() protected override void _OnTrigger()
{ {
if ( ! interruptCurrent && _actionID != -1 ) if ( material != null && ! IsInstanceValid( material ) )
{
material = null;
}
if ( instanceTarget != null && ! IsInstanceValid( instanceTarget ) )
{
instanceTarget = null;
}
if ( propertyName == null )
{ {
return; return;
} }
// this.LogInfo( "TRIGGERED" );
if ( instanceTarget != null )
{
// this.LogInfo( "INSTANCE" );
TweenInstanceUniform();
return;
}
if ( material != null )
{
// this.LogInfo( "MATERIAL" );
TweenMaterial();
return;
}
}
void TweenMaterial()
{
if ( ! interruptCurrent && _actionID != -1 )
{
return;
}
if ( _actionID != -1 ) if ( _actionID != -1 )
{ {
@ -156,5 +194,105 @@ namespace Rokojori
).id; ).id;
} }
void TweenInstanceUniform()
{
if ( ! interruptCurrent && _actionID != -1 )
{
return;
}
if ( _actionID != -1 )
{
CancelAction( _actionID );
}
if ( stopAnimation )
{
AnimationManager.StartAnimation( this, instanceTarget, propertyName.propertyName );
if ( setStopValue )
{
propertyName.SetInstance( instanceTarget, endValue );
}
AnimationManager.EndAnimation( this, instanceTarget, propertyName.propertyName );
return;
}
_actionID = DispatchStart();
var startValue = propertyName.GetInstance( instanceTarget );
if ( forceStartValue && Engine.IsEditorHint() )
{
startValue = forcedStartValue;
}
if ( animateStartValue )
{
startValue = animationStartValue;
}
AnimationManager.StartAnimation( this, instanceTarget, propertyName.propertyName );
// this.LogInfo( "Get Float Tween", startValue );
_timeID = TimelineManager.ScheduleSpanWith( duration,
( span, type )=>
{
// this.LogInfo( "Update Float Tween", startValue );
if ( span.id != _timeID )
{
return;
}
if ( ! AnimationManager.IsAnimating( this, instanceTarget, propertyName.propertyName ) )
{
return;
}
var phase = span.phase;
if ( curve != null )
{
phase = curve.Sample( phase );
}
var value = Mathf.Lerp( startValue, endValue, phase );
// this.LogInfo( "Updating Float Tween", "phase:", phase, "value:", value, instanceTarget );
propertyName.SetInstance( instanceTarget, value );
// ReflectionHelper.SetValue( material, propertyName , value );
if ( type == TimelineSpanUpdateType.End )
{
// this.LogInfo( "End Float Tween", endValue );
// target._Set( targetMember, endValue );
propertyName.SetInstance( instanceTarget, endValue );
AnimationManager.EndAnimation( this, instanceTarget, propertyName.propertyName );
DispatchEnd( _actionID );
_actionID = -1;
_timeID = -1;
}
},
this
).id;
}
} }
} }

View File

@ -89,6 +89,11 @@ namespace Rokojori
var fromMaterial = MaterialSurfaceContainer.From( target, this.index ).GetMaterialInSlot<Material>( slot ); var fromMaterial = MaterialSurfaceContainer.From( target, this.index ).GetMaterialInSlot<Material>( slot );
// Registers ownership of the *slot assignment itself* (as opposed to the color/float
// properties on the material), so a second TweenMaterial targeting the same slot takes
// over cleanly instead of both tweens fighting to set the end material.
AnimationManager.StartAnimation( this, target, GetSlotKey() );
SetAssignedMaterial( fromMaterial ); SetAssignedMaterial( fromMaterial );
var tl = TimelineManager.Ensure( timeLine ); var tl = TimelineManager.Ensure( timeLine );
@ -180,23 +185,30 @@ namespace Rokojori
AnimationManager.EndAnimation( this, _assignedMaterial, f.propertyName ); AnimationManager.EndAnimation( this, _assignedMaterial, f.propertyName );
} }
if ( MaterialAssignmentMode.Temporary_Duplicate_For_Each_Tween == assignmentMode ) // Only write the end material (and release ownership) if no later TweenMaterial
// has since taken over this slot - otherwise this would stomp on it after the fact.
if ( AnimationManager.IsAnimating( this, target, GetSlotKey() ) )
{ {
_assignedMaterial = null; if ( MaterialAssignmentMode.Temporary_Duplicate_For_Each_Tween == assignmentMode )
var msc = MaterialSurfaceContainer.From( target, index );
msc.SetMaterialInSlot( slot, toMaterial );
var prop = new ColorPropertyName();
prop.propertyName = "albedo_color";
var info = new Color();
if ( toMaterial is BaseMaterial3D bm )
{ {
info = bm.AlbedoColor; _assignedMaterial = null;
var msc = MaterialSurfaceContainer.From( target, index );
msc.SetMaterialInSlot( slot, toMaterial );
var prop = new ColorPropertyName();
prop.propertyName = "albedo_color";
var info = new Color();
if ( toMaterial is BaseMaterial3D bm )
{
info = bm.AlbedoColor;
}
// this.LogInfo( "Setting:", slot, toMaterial, info, prop.Get( toMaterial ) );
} }
// this.LogInfo( "Setting:", slot, toMaterial, info, prop.Get( toMaterial ) ); AnimationManager.EndAnimation( this, target, GetSlotKey() );
} }
DispatchEnd( sequenceID ); DispatchEnd( sequenceID );
@ -208,6 +220,11 @@ namespace Rokojori
} }
string GetSlotKey()
{
return $"MaterialSlot:{index}:{slot}";
}
void SetAssignedMaterial( Material material ) void SetAssignedMaterial( Material material )
{ {
if ( MaterialAssignmentMode.Temporary_Duplicate_For_Each_Tween == assignmentMode ) if ( MaterialAssignmentMode.Temporary_Duplicate_For_Each_Tween == assignmentMode )

View File

@ -118,6 +118,11 @@ namespace Rokojori
for ( int i = 0; i < targets.Length; i++ ) for ( int i = 0; i < targets.Length; i++ )
{ {
if ( ! IsInstanceValid( targets[ i ] ) )
{
continue;
}
var o = _targetValues[ i ]; var o = _targetValues[ i ];
o.Set( targets[ i ], shakeEffect.globalPosition, shakeEffect.globalRotation ); o.Set( targets[ i ], shakeEffect.globalPosition, shakeEffect.globalRotation );
@ -146,6 +151,11 @@ namespace Rokojori
for ( int i = 0; i < targets.Length; i++ ) for ( int i = 0; i < targets.Length; i++ )
{ {
if ( ! IsInstanceValid( targets[ i ] ) )
{
continue;
}
var combined = offsetData.Clone(); var combined = offsetData.Clone();
var o = _targetValues[ i ]; var o = _targetValues[ i ];

View File

@ -1,109 +0,0 @@
using Godot;
using Rokojori.Extensions;
namespace Rokojori;
[Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")]
public partial class Brain:Action
{
[Export]
public Receptor[] receptors;
[Export]
public Node behaviorRoot;
[Export]
public BehaviorSequence activeSequence;
bool _paused = false;
public bool paused => _paused;
public void SetPaused( bool value )
{
_paused = value;
}
protected override void _OnTrigger()
{
if ( _paused )
{
return;
}
if ( activeSequence != null && activeSequence.isExclusive && activeSequence.isRunning )
{
return;
}
var resolved = Resolve();
if ( resolved == null )
{
return;
}
if ( resolved == activeSequence )
{
if ( activeSequence.reinitialize )
{
activeSequence.CancelAllRunning();
activeSequence.Trigger();
}
return;
}
var previous = activeSequence;
activeSequence = resolved;
if ( previous != null )
{
previous.CancelAllRunning();
}
activeSequence.Trigger();
}
BehaviorSequence Resolve()
{
if ( behaviorRoot == null )
{
return null;
}
return ResolveIn( behaviorRoot );
}
BehaviorSequence ResolveIn( Node parent )
{
var walker = NodesWalker.Get();
var numChildren = walker.NumChildren( parent );
for ( int i = 0; i < numChildren; i++ )
{
var child = walker.ChildAt( parent, i );
if ( child is BehaviorSequence sequence )
{
return sequence;
}
if ( child is BehaviorBranch branch )
{
if ( branch.IsBranchActive( this ) )
{
var found = ResolveIn( branch );
if ( found != null )
{
return found;
}
}
}
}
return null;
}
}

View File

@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori; namespace Rokojori;
[Tool] [Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")] [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/BehaviorBranch.svg")]
public abstract partial class BehaviorBranch:Node public abstract partial class BehaviorBranch:Node
{ {
[Export] [Export]

View File

@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori; namespace Rokojori;
[Tool] [Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")] [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/BehaviorSequence.svg")]
public partial class BehaviorSequence:Sequence public partial class BehaviorSequence:Sequence
{ {
[Export] [Export]
@ -29,7 +29,9 @@ public partial class BehaviorSequence:Sequence
void OnDone( SequenceActionFinishedEvent ev ) void OnDone( SequenceActionFinishedEvent ev )
{ {
if ( _brain == null || _brain.activeSequence != this ) this.LogInfo( "On Done BS" );
if ( _brain == null || _brain.activeNode != this )
{ {
return; return;
} }

View File

@ -0,0 +1,321 @@
using Godot;
using System.Collections.Generic;
using Rokojori.Extensions;
using System.Linq;
namespace Rokojori;
[Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Brain.svg")]
public partial class Brain:Action
{
[Export]
public Receptor[] receptors;
[Export]
public Node[] receporContainers;
[Export]
public Node behaviorRoot;
[ExportGroup( "Debugging")]
[Export]
public Node activeNode;
[Export]
public bool printActiveNodeSelection = false;
[Export]
public bool printBranchResult = false;
// Non-BehaviorSequence nodes (plain Actions) don't carry this state themselves,
// so these stand in for it. Kept as protected statics in case that needs tuning later.
protected static bool defaultIsExclusive = false;
protected static bool defaultIsRunning = false;
protected static bool defaultReinitialize = true;
bool _paused = false;
public bool paused => _paused;
public void SetPaused( bool value )
{
_paused = value;
}
static bool IsExclusive( Node node )
{
return node is BehaviorSequence sequence ? sequence.isExclusive : defaultIsExclusive;
}
static bool IsRunning( Node node )
{
return node is BehaviorSequence sequence ? sequence.isRunning : defaultIsRunning;
}
static bool ShouldReinitialize( Node node )
{
return node is BehaviorSequence sequence ? sequence.reinitialize : defaultReinitialize;
}
static void CancelIfRunning( Node node )
{
if ( node is BehaviorSequence sequence )
{
sequence.CancelAllRunning();
}
}
public void StartBrain()
{
SetPaused( false );
receptors?.ForEachNonNull( r => r.Start() );
receporContainers?.ForEachNonNull( c => c.ForEach<Receptor>( r => r.Start() ) );
Trigger();
}
// The node most recently armed with a one-shot onSequenceDone listener, so a late
// event from a node the Brain has since moved away from doesn't retrigger it.
SequenceAction _listenedSequenceAction;
// The node a next-frame restart is currently scheduled for, so a plain Action
// doesn't accumulate more than one pending restart chain at a time.
Node _pendingRestartNode;
void ActivateNode( Node node )
{
if ( node is Action action )
{
action.Trigger();
}
if ( node is BehaviorSequence )
{
// Manages its own re-trigger/loop via onSequenceDone already (see BehaviorSequence._Ready).
return;
}
if ( node is SequenceAction sequenceAction )
{
_listenedSequenceAction = sequenceAction;
if ( ! sequenceAction.onSequenceDone.HasAction( OnListenedSequenceActionDone ) )
{
sequenceAction.onSequenceDone.Once( OnListenedSequenceActionDone );
}
}
else if ( node is Action )
{
ScheduleRestartNextFrame( node );
}
}
void OnListenedSequenceActionDone( SequenceActionFinishedEvent ev )
{
if ( activeNode != _listenedSequenceAction )
{
return;
}
Trigger();
}
async void ScheduleRestartNextFrame( Node node )
{
if ( _pendingRestartNode == node )
{
return;
}
_pendingRestartNode = node;
await this.RequestNextFrame();
if ( _pendingRestartNode == node )
{
_pendingRestartNode = null;
}
if ( activeNode != node )
{
return;
}
Trigger();
}
// Dedup flag for the no-match-resolved restart below, mirroring _pendingRestartNode.
bool _pendingNullResolveRestart = false;
async void ScheduleRestartNextFrameOnNullResolve()
{
if ( _pendingNullResolveRestart )
{
return;
}
_pendingNullResolveRestart = true;
await this.RequestNextFrame();
_pendingNullResolveRestart = false;
Trigger();
}
List<string> _branchInfo = new List<string>();
protected override void _OnTrigger()
{
if ( _paused )
{
if ( printActiveNodeSelection )
{
this.LogInfo( "Skipped. Paused" );
}
return;
}
if ( activeNode != null && IsExclusive( activeNode ) && IsRunning( activeNode ) )
{
if ( printActiveNodeSelection )
{
this.LogInfo( "Skipped. Running already exclusively" );
}
return;
}
if ( printBranchResult && _branchInfo.Count > 0 )
{
_branchInfo.Clear();
}
var resolved = Resolve();
if ( printBranchResult && _branchInfo.Count > 0 )
{
this.LogInfo( _branchInfo.Join( "\n" ) );
}
if ( printActiveNodeSelection )
{
this.LogInfo( "Active Node:", resolved == null ? "Null" : ( HierarchyName.Of( resolved ) ) );
}
if ( resolved == null )
{
ScheduleRestartNextFrameOnNullResolve();
return;
}
if ( resolved == activeNode )
{
if ( ShouldReinitialize( activeNode ) )
{
CancelIfRunning( activeNode );
ActivateNode( activeNode );
}
return;
}
var previous = activeNode;
activeNode = resolved;
if ( previous != null )
{
CancelIfRunning( previous );
}
ActivateNode( activeNode );
}
Node Resolve()
{
if ( behaviorRoot == null )
{
return null;
}
return ResolveIn( behaviorRoot );
}
Node ResolveIn( Node parent )
{
var walker = NodesWalker.Get();
var numChildren = walker.NumChildren( parent );
for ( int i = 0; i < numChildren; i++ )
{
var child = walker.ChildAt( parent, i );
if ( child is BehaviorBranch branch && branch.IsBranchActive( this ) )
{
if ( printBranchResult )
{
_branchInfo.Add( HierarchyName.Of( child ) );
}
var found = ResolveIn( branch );
if ( found != null )
{
return found;
}
}
if ( child is SceneCondition condition && condition.Evaluate() )
{
if ( printBranchResult )
{
_branchInfo.Add( HierarchyName.Of( child ) );
}
var found = ResolveIn( condition );
if ( found != null )
{
return found;
}
}
if ( child is Receptor receptor && receptor.active )
{
if ( printBranchResult )
{
_branchInfo.Add( HierarchyName.Of( child ) );
}
var found = ResolveIn( receptor );
if ( found != null )
{
return found;
}
}
if ( child is BehaviorSequence sequence )
{
return sequence;
}
if ( child is Action action )
{
return action;
}
}
return null;
}
}

View File

@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori; namespace Rokojori;
[Tool] [Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")] [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/BrainControl.svg")]
public partial class BrainControl:Action public partial class BrainControl:Action
{ {
public enum Mode public enum Mode
@ -31,7 +31,7 @@ public partial class BrainControl:Action
switch ( mode ) switch ( mode )
{ {
case Mode.Start: case Mode.Start:
StartBrain(); brain.StartBrain();
break; break;
case Mode.Pause: case Mode.Pause:
@ -48,21 +48,5 @@ public partial class BrainControl:Action
} }
} }
void StartBrain()
{
brain.SetPaused( false );
if ( brain.receptors != null )
{
foreach ( var receptor in brain.receptors )
{
if ( receptor != null )
{
receptor.Start();
}
}
}
brain.Trigger();
}
} }

View File

@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori; namespace Rokojori;
[Tool] [Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")] [GlobalClass]
public partial class ConditionBranch:BehaviorBranch public partial class ConditionBranch:BehaviorBranch
{ {
[Export] [Export]

View File

@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori; namespace Rokojori;
[Tool] [Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")] [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/ReceptorBranch.svg")]
public partial class ReceptorBranch:BehaviorBranch public partial class ReceptorBranch:BehaviorBranch
{ {
[Export] [Export]

View File

@ -1,25 +0,0 @@
using Godot;
using Rokojori.Extensions;
namespace Rokojori;
[Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")]
public partial class FollowBehavior:BehaviorSequence
{
[Export]
public BehaviorMovement movement;
[Export]
public Node3D target;
protected override void _OnTrigger()
{
if ( movement != null )
{
movement.Follow( target );
}
base._OnTrigger();
}
}

View File

@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori; namespace Rokojori;
[Tool] [Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")] [GlobalClass]
public abstract partial class BehaviorMovement:Node public abstract partial class BehaviorMovement:Node
{ {
public abstract void GoToPosition( Vector3 worldPosition ); public abstract void GoToPosition( Vector3 worldPosition );

View File

@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori; namespace Rokojori;
[Tool] [Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")] [GlobalClass]
public partial class CharacterBodyBehaviorMovement:BehaviorMovement public partial class CharacterBodyBehaviorMovement:BehaviorMovement
{ {
[Export] [Export]

View File

@ -0,0 +1,33 @@
using Godot;
using Rokojori.Extensions;
namespace Rokojori;
[Tool]
[GlobalClass]
public partial class FollowBehavior:Action
{
[Export]
public BehaviorMovement movement;
[Export]
public Node3D target;
protected override void _OnTrigger()
{
if ( movement == null || target == null )
{
return;
}
// var id = DispatchStart();
movement.Follow( target );
// await this.RequestNextFrame();
// this.LogInfo( "Done" );
// DispatchEnd( id );
}
}

View File

@ -4,17 +4,17 @@ using Rokojori.Extensions;
namespace Rokojori; namespace Rokojori;
[Tool] [Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")] [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Receptor.svg")]
public partial class Receptor:Action public partial class Receptor:Action
{ {
[Export] [Export]
public SceneCondition sceneCondition; public SceneCondition sceneCondition;
[Export( PropertyHint.Range, "0.1,100")] [Export( PropertyHint.Range, "0.1,100")]
public float receiptionTriggerCoverage = 75f; public float receptionTriggerCoverage = 75f;
[Export( PropertyHint.Range, "10,2000")] [Export( PropertyHint.Range, "10,2000")]
public float receiptionWindowDurationMS = 500f; public float receptionWindowDurationMS = 500f;
[Export( PropertyHint.Range, "10,1000")] [Export( PropertyHint.Range, "10,1000")]
public float tickRateMS = 100f; public float tickRateMS = 100f;
@ -22,7 +22,11 @@ public partial class Receptor:Action
[Export] [Export]
public Timeline timeline; public Timeline timeline;
int resultSize => Mathf.CeilToInt( receiptionWindowDurationMS / tickRateMS ); [ExportGroup("Debugging")]
[Export]
public float fillState = 0;
int resultSize => Mathf.CeilToInt( receptionWindowDurationMS / tickRateMS );
RingBuffer<bool> _resultsBuffer; RingBuffer<bool> _resultsBuffer;
@ -70,8 +74,11 @@ public partial class Receptor:Action
void Sample() void Sample()
{ {
// this.LogInfo( "Receptor Update" );
if ( sceneCondition == null ) if ( sceneCondition == null )
{ {
// this.LogInfo( "No condition" );
return; return;
} }
@ -84,12 +91,15 @@ public partial class Receptor:Action
if ( _resultsBuffer.count != resultSize ) if ( _resultsBuffer.count != resultSize )
{ {
// this.LogInfo( "No activation" );
_activated = false; _activated = false;
return; return;
} }
var numActive = _resultsBuffer.CountItems( b => b ); var numActive = _resultsBuffer.CountItems( b => b );
_activated = numActive / (float) resultSize >= receiptionTriggerCoverage / 100f; fillState = numActive / (float) resultSize;
_activated = fillState >= receptionTriggerCoverage / 100f;
} }
} }

View File

@ -1,41 +0,0 @@
using Godot;
using Rokojori.Extensions;
namespace Rokojori
{
public enum ThirdPersonCameraLockMode
{
Lock,
Release
}
[Tool]
[GlobalClass]
public partial class SetThirdPersonCameraLockTarget : Action
{
[Export]
public ThirdPersonCamera camera;
[Export]
public Pointer source;
[Export]
public ThirdPersonCameraLockMode mode = ThirdPersonCameraLockMode.Lock;
protected override void _OnTrigger()
{
if ( camera == null )
{
return;
}
if ( mode == ThirdPersonCameraLockMode.Release )
{
camera.lockTarget = null;
return;
}
camera.lockTarget = source != null && source.pointable != null ? source.pointable.pointerPosition : null;
}
}
}

View File

@ -17,7 +17,7 @@ namespace Rokojori
public Node3D target; public Node3D target;
[Export] [Export]
public Node3D lockTarget; public CameraLocker cameraLocker;
[Export] [Export]
public float yaw; public float yaw;
@ -68,12 +68,6 @@ namespace Rokojori
return; return;
} }
if ( lockTarget != null && ! IsInstanceValid( lockTarget ) )
{
lockTarget = null;
}
// var tl = TimeLineManager.Ensure( timeLine ); // var tl = TimeLineManager.Ensure( timeLine );
// var delta = tl.delta; // var delta = tl.delta;
var delta = (float) gameDelta; var delta = (float) gameDelta;
@ -81,19 +75,14 @@ namespace Rokojori
var targetPosition = Smoothing.Apply( settings.targetFollowSmoothing, target.GlobalPosition, delta ); var targetPosition = Smoothing.Apply( settings.targetFollowSmoothing, target.GlobalPosition, delta );
var hasLockTarget = lockTarget != null; var lockTargetNode = cameraLocker != null && cameraLocker.isLocked && IsInstanceValid( cameraLocker.lockTarget ) ? cameraLocker.lockTarget.pointerPosition : null;
var hasLockTarget = lockTargetNode != null;
var lockTargetPosition = _lastLockTargetPosition; var lockTargetPosition = _lastLockTargetPosition;
if ( hasLockTarget ) if ( hasLockTarget )
{ {
lockTargetPosition = Smoothing.Apply( settings.lockTargetFollowSmoothing, lockTarget.GlobalPosition, delta ); lockTargetPosition = Smoothing.Apply( settings.lockTargetFollowSmoothing, lockTargetNode.GlobalPosition, delta );
_lastLockTargetPosition = lockTargetPosition; _lastLockTargetPosition = lockTargetPosition;
if ( settings.lockReleaseDistance > 0f && ( lockTargetPosition - targetPosition ).Length() > settings.lockReleaseDistance )
{
lockTarget = null;
hasLockTarget = false;
}
} }
// lockBlend fades 0->1 on engage and 1->0 on release instead of snapping, so the focus // lockBlend fades 0->1 on engage and 1->0 on release instead of snapping, so the focus

View File

@ -81,9 +81,6 @@ namespace Rokojori
[Export] [Export]
public Smoothing lockBlendSmoothing = new FrameSmoothing(); public Smoothing lockBlendSmoothing = new FrameSmoothing();
[Export]
public float lockReleaseDistance = 0f;
[Export] [Export]
public float lockMinPitch = -10; public float lockMinPitch = -10;

View File

@ -0,0 +1,205 @@
using System.Collections.Generic;
using System.Reflection;
using Godot;
using Rokojori.Extensions;
namespace Rokojori
{
[Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Condition.svg")]
public partial class HasCondition: SceneCondition
{
bool _enteredTree = false;
public override void _EnterTree()
{
base._EnterTree();
_enteredTree = true;
}
void OnTargetAssignmentChanged()
{
// Deserialization sets exported properties before _EnterTree fires, so this
// only triggers for genuine interactive edits in the editor, never for a scene
// being loaded (regardless of property order) or for runtime reassignment.
if ( ! _enteredTree || ! Engine.IsEditorHint() )
{
return;
}
targetName = "";
#if TOOLS
NotifyPropertyListChanged();
#endif
}
GodotObject _target;
[Export]
public GodotObject target
{
get => _target;
set
{
if ( _target == value )
{
return;
}
_target = value;
OnTargetAssignmentChanged();
}
}
Node _targetNode;
[Export]
public Node targetNode
{
get => _targetNode;
set
{
if ( _targetNode == value )
{
return;
}
_targetNode = value;
OnTargetAssignmentChanged();
}
}
[Export]
public string targetName;
GodotObject GetEffectiveTarget()
{
return target != null ? target : (GodotObject) targetNode;
}
public override bool Evaluate()
{
var effectiveTarget = GetEffectiveTarget();
if ( effectiveTarget == null || string.IsNullOrEmpty( targetName ) )
{
return false;
}
var value = ReflectionHelper.GetDataMemberValue<object>( effectiveTarget, targetName );
return EvaluateValue( value );
}
static bool EvaluateValue( object value )
{
switch ( value )
{
case null:
return false;
case GodotObject go:
return GodotObject.IsInstanceValid( go );
case bool b:
return b;
case string s:
return ! string.IsNullOrEmpty( s );
case int i:
return i > 0;
case long l:
return l > 0;
case float f:
return f > 0;
case double d:
return d > 0;
default:
return false;
}
}
#if TOOLS
static bool IsSupportedMemberType( System.Type type )
{
if ( type == null )
{
return false;
}
if ( type == typeof( bool ) || type == typeof( string ) )
{
return true;
}
if ( type == typeof( int ) || type == typeof( long ) || type == typeof( float ) || type == typeof( double ) )
{
return true;
}
if ( typeof( Node ).IsAssignableFrom( type ) || typeof( Resource ).IsAssignableFrom( type ) )
{
return true;
}
return false;
}
List<string> GetTargetMemberNames()
{
var names = new List<string>();
var effectiveTarget = GetEffectiveTarget();
if ( effectiveTarget == null )
{
return names;
}
var infos = ReflectionHelper.GetDataMemberInfos( effectiveTarget, BindingFlags.Public | BindingFlags.Instance );
foreach ( var info in infos )
{
if ( ! info.IsDefined( typeof( ExportAttribute ) ) )
{
continue;
}
if ( ! IsSupportedMemberType( info.GetMemberType() ) )
{
continue;
}
names.Add( info.Name );
}
return names;
}
public override void _ValidateProperty( Godot.Collections.Dictionary property )
{
if ( property[ "name" ].AsString() != nameof( targetName ) )
{
return;
}
var members = GetTargetMemberNames();
property[ "hint" ] = (long) PropertyHint.Enum;
property[ "hint_string" ] = members.Count == 0
? "(Invalid Target)"
: "None," + string.Join( ",", members );
}
#endif
}
}

View File

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

View File

@ -14,6 +14,7 @@ namespace Rokojori
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/GameObject3D.svg") ] [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/GameObject3D.svg") ]
public partial class GameObject3D:Node3D, INetworkNode public partial class GameObject3D:Node3D, INetworkNode
{ {
[ExportToolButton("Center On Children")] [ExportToolButton("Center On Children")]
public Callable centerOnChildrenButton => Callable.From( ()=> { this.CenterOnChildren(); } ); public Callable centerOnChildrenButton => Callable.From( ()=> { this.CenterOnChildren(); } );

View File

@ -0,0 +1,15 @@
using Godot;
using Rokojori;
using System.Collections.Generic;
using Rokojori.Extensions;
namespace Rokojori;
[Tool]
[GlobalClass]
public partial class GameObject3DFlag:SelectorFlag
{
}

View File

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

View File

@ -16,6 +16,40 @@ namespace Rokojori
} }
} }
public static void ForEach<[MustBeVariant] T>( this T[] array, System.Action<T> action )
{
for (int i = 0; i < array.Length; i++)
{
action( array[i] );
}
}
public static void ForEachNonNull<[MustBeVariant] T>( this Godot.Collections.Array<T> array, System.Action<T> action ) where T:class
{
for (int i = 0; i < array.Count; i++)
{
if ( array[ i ] == null )
{
continue;
}
action( array[i] );
}
}
public static void ForEachNonNull<T>( this T[] array, System.Action<T> action ) where T:class
{
for (int i = 0; i < array.Length; i++)
{
if ( array[ i ] == null )
{
continue;
}
action( array[i] );
}
}
public static T Find<[MustBeVariant] T>( this Godot.Collections.Array<T> array, System.Func<T,bool> evaluater, int offset = 0 ) public static T Find<[MustBeVariant] T>( this Godot.Collections.Array<T> array, System.Func<T,bool> evaluater, int offset = 0 )
{ {
for ( int i = offset; i < array.Count; i++ ) for ( int i = offset; i < array.Count; i++ )

View File

@ -435,6 +435,50 @@ namespace Rokojori
return list; return list;
} }
public static Node Select( this Node root, Selector selector )
{
var walker = nodesWalker;
var end = walker.IterationEndOf( root );
var it = root;
while ( it != end )
{
if ( selector.Selects( it ) )
{
return it;
}
it = walker.NextNode( it );
}
return null;
}
public static List<Node> SelectAll( this Node root, Selector selector )
{
var walker = nodesWalker;
var end = walker.IterationEndOf( root );
var it = root;
var list = new List<Node>();
while ( it != end )
{
if ( selector.Selects( it ) )
{
list.Add( it );
}
it = walker.NextNode( it );
}
return null;
}
public static void ForEach<T>( this Node root, Action<T> callback ) where T:class public static void ForEach<T>( this Node root, Action<T> callback ) where T:class
{ {
var walker = nodesWalker; var walker = nodesWalker;

View File

@ -0,0 +1,293 @@
using Godot;
using Rokojori.Extensions;
namespace Rokojori
{
// Single owner of lock state. LockOnPointer only tracks candidates and the current selection;
// ThirdPersonCamera (or anything else) only reads isLocked/lockTarget from here.
[Tool]
[GlobalClass]
public partial class CameraLocker : Action
{
[Export]
public LockOnPointer source;
[Export]
public Sensor engageSensor;
[ExportGroup( "Release" )]
[Export]
public float releaseDistance = 0f;
[Export]
public Node3D releaseDistanceOrigin;
[ExportGroup( "Cycling" )]
[Export]
public LockOnCyclingSetup cyclingSetup;
[Export]
public LockOnCyclingSetup lockedCyclingSetup;
[ExportGroup( "Visualizer" )]
[Export]
public Node3D visualizerMarker;
[ExportGroup( "Candidate Events" )]
[Export]
public Action onCandidateSpotted;
[Export]
public Action onCandidateUnlocked;
[Export]
public Action onCandidateVanished;
[Export]
public Action onCandidateChanged;
[Export]
public bool triggerCanidatesWhenLocked = false;
[ExportGroup( "Lock Events" )]
[Export]
public Action onLock;
[Export]
public Action onReleasedLock;
[Export]
public Action onChangeLocked;
[ExportGroup( "Read Only" )]
[Export]
public Pointable lockTarget;
public bool isLocked => lockTarget != null;
Pointable _previousCandidate;
bool _wasEngaged = false;
protected override void _OnTrigger()
{
if ( source == null )
{
return;
}
source.Trigger();
UpdateCandidateEvents();
var previousTarget = lockTarget;
var wasLocked = previousTarget != null;
// Order matters: cycling may move an existing lock, engagement may start/end one,
// auto-release may end one - each only touches lockTarget for its own reason, nothing
// here resyncs it every frame just because source.pointable drifted on its own.
UpdateCycling();
UpdateEngagement();
UpdateAutoRelease();
FireLockEvents( wasLocked, previousTarget );
UpdateVisualizer();
}
public void Release()
{
if ( lockTarget == null )
{
return;
}
var previousTarget = lockTarget;
lockTarget = null;
FireLockEvents( true, previousTarget );
}
void UpdateCandidateEvents()
{
if ( lockTarget != null && ! triggerCanidatesWhenLocked )
{
return;
}
var candidate = source.pointable;
if ( candidate == _previousCandidate )
{
return;
}
_previousCandidate = candidate;
onCandidateChanged?.Trigger();
Action.TriggerSafe( candidate == null ? onCandidateVanished : onCandidateSpotted );
}
void UpdateCycling()
{
var setup = isLocked ? ( lockedCyclingSetup ?? cyclingSetup ) : ( cyclingSetup ?? lockedCyclingSetup );
if ( setup == null )
{
return;
}
var cycled = false;
if ( setup.cycleMode == LockOnCycleMode.ClosestCycle )
{
if ( setup.cycleSensor != null && setup.cycleSensor.isDown )
{
source.CycleNext();
cycled = true;
}
}
else if ( setup.cycleMode == LockOnCycleMode.Directional && setup.directionalCycleInputs != null )
{
for ( int i = 0; i < setup.directionalCycleInputs.Length; i++ )
{
var input = setup.directionalCycleInputs[ i ];
if ( input == null || input.sensor == null || ! input.sensor.isDown )
{
continue;
}
source.CycleInScreenSpaceDirection( input.direction );
cycled = true;
break;
}
}
// Cycling is the only thing that is allowed to move an already-established lock onto a
// different candidate - a deliberate, one-time assignment here, not a per-frame resync.
if ( cycled && isLocked )
{
lockTarget = source.pointable;
}
}
void UpdateEngagement()
{
var engaged = engageSensor != null && engageSensor.isActive;
var risingEdge = engaged && ! _wasEngaged;
_wasEngaged = engaged;
if ( ! engaged )
{
lockTarget = null;
return;
}
// Only a fresh press/toggle-on acquires a target. Once locked, the lock is held
// regardless of what source.pointable does on its own (e.g. losing/regaining the raycast
// hit) - it only changes via cycling, auto-release, or an explicit Release() call.
if ( lockTarget == null && risingEdge )
{
lockTarget = source.pointable;
}
}
void UpdateAutoRelease()
{
if ( lockTarget == null )
{
return;
}
if ( IsGone( lockTarget ) )
{
lockTarget = null;
return;
}
if ( IsTooFar( lockTarget ) )
{
lockTarget = null;
}
}
// Covers both an actually freed node and one that was removed from the tree without being
// freed (e.g. pooled/recycled enemies) - either way it is no longer something the lock
// should keep pointing at.
static bool IsGone( Pointable target )
{
return target == null || ! IsInstanceValid( target ) || ! target.IsInsideTree();
}
bool IsTooFar( Pointable target )
{
if ( releaseDistance <= 0f )
{
return false;
}
var origin = releaseDistanceOrigin ?? source.caster;
if ( origin == null )
{
return false;
}
return origin.GlobalPosition.DistanceTo( target.GlobalPosition ) > releaseDistance;
}
void ReleaseEngageSensorIfToggled()
{
if ( engageSensor is ActivationModeSensor ams && ams.isActive )
{
ams.Release();
}
}
void FireLockEvents( bool wasLocked, Pointable previousTarget )
{
var isLockedNow = lockTarget != null;
if ( isLockedNow && ! wasLocked )
{
Action.TriggerSafe( onLock );
return;
}
if ( ! isLockedNow && wasLocked )
{
ReleaseEngageSensorIfToggled();
Action.TriggerSafe( onReleasedLock );
if ( source.IsCandidate( previousTarget ) )
{
Action.TriggerSafe( onCandidateUnlocked );
}
return;
}
if ( isLockedNow && wasLocked && lockTarget != previousTarget )
{
Action.TriggerSafe( onChangeLocked );
}
}
void UpdateVisualizer()
{
if ( visualizerMarker == null )
{
return;
}
var current = lockTarget ?? source.pointable;
if ( ! IsGone( current ) )
{
visualizerMarker.CopyGlobalPoseFrom( current.pointerPosition );
}
}
}
}

View File

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

View File

@ -0,0 +1,19 @@
using Godot;
using Rokojori.Extensions;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class LockOnCyclingSetup : Resource
{
[Export]
public LockOnCycleMode cycleMode = LockOnCycleMode.None;
[Export]
public Sensor cycleSensor;
[Export]
public DirectionalCycleInput[] directionalCycleInputs = [];
}
}

View File

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

View File

@ -11,52 +11,28 @@ namespace Rokojori
Directional Directional
} }
// Only tracks the current candidate list and the single selected pointable (sticky - stays on
// the selection until it is cycled away from or stops being a valid candidate). Owns no concept
// of "locked" - that is CameraLocker's job, reading/driving this pointer's pointable.
[Tool] [Tool]
[GlobalClass] [GlobalClass]
public partial class LockOnPointer : Pointer public partial class LockOnPointer : Pointer
{ {
[Export] public int test = 0;
public Sensor engageSensor;
[Export] List<Pointable> _candidates = new List<Pointable>();
public bool releaseWhenTargetInvalid = true;
[Export] public int numCandidates => _candidates.Count;
public Action onLockEngaged;
[Export] public Pointable GetCandidate( int index )
public Action onLockReleased; {
return _candidates[ index ];
}
[ExportGroup( "Cycling" )] public bool IsCandidate( Pointable p )
[Export] {
public LockOnCycleMode cycleMode = LockOnCycleMode.None; return p != null && _candidates.Contains( p );
}
[Export]
public Sensor cycleSensor;
[Export]
public DirectionalCycleInput[] directionalCycleInputs = [];
[Export]
public Node3D closestVisualizer;
[Export]
public Action onClosestFound;
[Export]
public Action onClosestLost;
[Export]
public Action onClosestChanged;
[ExportGroup("Read Only")]
[Export]
public Pointable closestCandidate;
bool _engaged = false;
public bool isEngaged => _engaged;
protected override void _OnTrigger() protected override void _OnTrigger()
{ {
@ -68,185 +44,68 @@ namespace Rokojori
if ( pointable != null && ! IsInstanceValid( pointable ) ) if ( pointable != null && ! IsInstanceValid( pointable ) )
{ {
pointable = null; pointable = null;
if ( engageSensor.isActive && engageSensor is ActivationModeSensor ams )
{
ams.Release();
}
onLockReleased?.Trigger();
} }
UpdateClosestCandidate(); RefreshCandidates();
var wasLocked = pointable != null; if ( pointable == null || ! _candidates.Contains( pointable ) )
if ( visualizer != null && pointable != null )
{ {
visualizer.CopyGlobalPoseFrom( pointable.pointerPosition ); SetPointable( ClosestCandidate() );
}
if ( engageSensor != null )
{
_engaged = engageSensor.isActive;
if ( ! _engaged )
{
if ( pointable != null )
{
SetPointable( null );
}
}
else if ( pointable == null )
{
SetPointable( closestCandidate );
}
else if ( releaseWhenTargetInvalid && ! IsStillValidTarget( pointable ) )
{
SetPointable( null );
}
else
{
HandleCycling();
}
}
// Fires off the pointable transition (not the raw engage-sensor edge) so it also covers the
// releaseWhenTargetInvalid auto-release path above, and so pointable already holds its new
// value (or null) by the time a listener - e.g. SetThirdPersonCameraLockTarget - reads it.
var isNowLocked = pointable != null;
if ( isNowLocked != wasLocked )
{
Action.TriggerSafe( isNowLocked ? onLockEngaged : onLockReleased );
} }
} }
void UpdateClosestCandidate() void RefreshCandidates()
{ {
var next = FindClosestCandidate(); _candidates.Clear();
if ( next == closestCandidate )
{
if ( closestVisualizer != null && closestCandidate != null )
{
closestVisualizer.CopyGlobalPoseFrom( closestCandidate.pointerPosition );
}
return;
}
closestCandidate = next;
onClosestChanged?.Trigger();
if ( closestCandidate == null )
{
onClosestLost?.Trigger();
}
else
{
onClosestFound?.Trigger();
}
if ( closestVisualizer != null && closestCandidate != null )
{
closestVisualizer.CopyGlobalPoseFrom( closestCandidate.pointerPosition );
}
}
void HandleCycling()
{
if ( cycleMode == LockOnCycleMode.ClosestCycle )
{
if ( cycleSensor != null && cycleSensor.isDown )
{
SetPointable( FindNextClosestCandidate() );
}
return;
}
if ( cycleMode != LockOnCycleMode.Directional || directionalCycleInputs == null )
{
return;
}
for ( int i = 0; i < directionalCycleInputs.Length; i++ )
{
var input = directionalCycleInputs[ i ];
if ( input == null || input.sensor == null || ! input.sensor.isDown )
{
continue;
}
var next = FindDirectionalCandidate( input.direction );
if ( next != null )
{
SetPointable( next );
}
return;
}
}
Pointable FindClosestCandidate()
{
for ( int i = 0; i < caster.NumColliders(); i++ ) for ( int i = 0; i < caster.NumColliders(); i++ )
{ {
var p = Nodes.Find<Pointable>( caster.GetCollider( i ) ); var p = Nodes.Find<Pointable>( caster.GetCollider( i ) );
if ( p != null && p.enabled ) if ( p != null && p.enabled )
{ {
return p; _candidates.Add( p );
} }
} }
return null;
} }
Pointable FindNextClosestCandidate() Pointable ClosestCandidate()
{ {
var currentIndex = -1; return _candidates.Count == 0 ? null : _candidates[ 0 ];
var candidates = new List<Pointable>();
for ( int i = 0; i < caster.NumColliders(); i++ )
{
var p = Nodes.Find<Pointable>( caster.GetCollider( i ) );
if ( p == null || ! p.enabled )
{
continue;
}
if ( p == pointable )
{
currentIndex = candidates.Count;
}
candidates.Add( p );
}
if ( candidates.Count == 0 )
{
return null;
}
if ( currentIndex == -1 )
{
return candidates[ 0 ];
}
return candidates[ ( currentIndex + 1 ) % candidates.Count ];
} }
Pointable FindDirectionalCandidate( Vector2 direction ) public void CycleNext()
{
if ( _candidates.Count == 0 )
{
return;
}
var currentIndex = _candidates.IndexOf( pointable );
var nextIndex = currentIndex == -1 ? 0 : ( currentIndex + 1 ) % _candidates.Count;
SetPointable( _candidates[ nextIndex ] );
}
public void CyclePrevious()
{
if ( _candidates.Count == 0 )
{
return;
}
var currentIndex = _candidates.IndexOf( pointable );
var previousIndex = currentIndex == -1 ? 0 : ( currentIndex - 1 + _candidates.Count ) % _candidates.Count;
SetPointable( _candidates[ previousIndex ] );
}
public void CycleInScreenSpaceDirection( Vector2 screenSpaceDirection )
{ {
if ( pointable == null ) if ( pointable == null )
{ {
return FindClosestCandidate(); SetPointable( ClosestCandidate() );
return;
} }
var right = caster.GlobalTransform.Basis.X; var right = caster.GlobalTransform.Basis.X;
@ -256,11 +115,11 @@ namespace Rokojori
Pointable best = null; Pointable best = null;
var bestScore = float.NegativeInfinity; var bestScore = float.NegativeInfinity;
for ( int i = 0; i < caster.NumColliders(); i++ ) for ( int i = 0; i < _candidates.Count; i++ )
{ {
var candidate = Nodes.Find<Pointable>( caster.GetCollider( i ) ); var candidate = _candidates[ i ];
if ( candidate == null || candidate == pointable || ! candidate.enabled ) if ( candidate == pointable )
{ {
continue; continue;
} }
@ -273,7 +132,7 @@ namespace Rokojori
continue; continue;
} }
var score = projected.Normalized().Dot( direction ); var score = projected.Normalized().Dot( screenSpaceDirection );
if ( score > bestScore ) if ( score > bestScore )
{ {
@ -282,20 +141,10 @@ namespace Rokojori
} }
} }
return best; if ( best != null )
}
bool IsStillValidTarget( Pointable p )
{
for ( int i = 0; i < caster.NumColliders(); i++ )
{ {
if ( Nodes.Find<Pointable>( caster.GetCollider( i ) ) == p ) SetPointable( best );
{
return true;
}
} }
return false;
} }
} }
} }

View File

@ -0,0 +1,18 @@
using Godot;
using Rokojori.Extensions;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class ReleaseCameraLock : Action
{
[Export]
public CameraLocker cameraLocker;
protected override void _OnTrigger()
{
cameraLocker?.Release();
}
}
}

View File

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

View File

@ -24,7 +24,18 @@ namespace Rokojori
} }
[Export] [Export]
public FlagMode mode = FlagMode.Needs_One_Flag; public FlagMode flagMode = FlagMode.Needs_One_Flag;
public enum IterationMode
{
Only_Node,
Only_Iterator,
Node_And_Iterator
}
[Export]
public IterationMode iterationMode= IterationMode.Node_And_Iterator;
[Export] [Export]
public TreeIteratorType iteratorType = TreeIteratorType.DirectChildren; public TreeIteratorType iteratorType = TreeIteratorType.DirectChildren;
@ -41,7 +52,12 @@ namespace Rokojori
} }
} }
if ( numPresent == flag.Length || mode == FlagMode.Needs_One_Flag && numPresent > 0 ) if ( numPresent == flag.Length )
{
return true;
}
if ( flagMode == FlagMode.Needs_One_Flag && numPresent > 0 )
{ {
return true; return true;
} }
@ -51,6 +67,21 @@ namespace Rokojori
public override bool Selects( Node node ) public override bool Selects( Node node )
{ {
if ( IterationMode.Only_Node == iterationMode )
{
return node is Selectable s && SelectsSelectable( s );
}
if ( IterationMode.Only_Iterator != iterationMode )
{
if ( node is Selectable s && SelectsSelectable( s ) )
{
return true;
}
}
var iterator = TreeIterator<Node>.GetIterator( iteratorType, node, NodesWalker.Get() ); var iterator = TreeIterator<Node>.GetIterator( iteratorType, node, NodesWalker.Get() );
while ( iterator.HasNext() ) while ( iterator.HasNext() )

View File

@ -0,0 +1,57 @@
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System;
using Godot;
using Rokojori.Extensions;
namespace Rokojori
{
[Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Selector.svg")]
public partial class SelectManager:Node
{
[Export]
public Node root;
Dictionary<Selector,Node> _singleSelections = new Dictionary<Selector, Node>();
Dictionary<Selector,List<Node>> _listSelections = new Dictionary<Selector, List<Node>>();
public Node GetSelectionRoot()
{
return root == null ? GetTree().Root : root;
}
public Node Select( Selector selector )
{
if ( selector == null )
{
return null;
}
if ( ! _singleSelections.ContainsKey( selector ) )
{
_singleSelections[ selector ] = GetSelectionRoot().Select( selector );
}
return _singleSelections[ selector ];
}
public List<Node> SelectAll( Selector selector )
{
if ( selector == null )
{
return new List<Node>();
}
if ( ! _singleSelections.ContainsKey( selector ) )
{
_listSelections[ selector ] = GetSelectionRoot().SelectAll( selector );
}
return _listSelections[ selector ];
}
}
}

View File

@ -0,0 +1 @@
uid://7ogqhvo3x5og

View File

@ -36,6 +36,8 @@ namespace Rokojori
{ {
var variant = Variant.From<T>( value ); var variant = Variant.From<T>( value );
mi.SetInstanceShaderParameter( propertyName, variant ); mi.SetInstanceShaderParameter( propertyName, variant );
// RJLog.Log( "Set", propertyName, variant );
return; return;
} }

View File

@ -155,6 +155,20 @@ public static class ListExtensions
); );
} }
public static void ForEachNonNull<T>( this List<T> elements, Action<T> callback ) where T:class
{
foreach ( var e in elements )
{
if ( e == null )
{
continue;
}
callback( e );
}
}
public static List<T> Sub<T>( this List<T> elements, int start, int length = 0 ) public static List<T> Sub<T>( this List<T> elements, int start, int length = 0 )
{ {
var end = length == 0 ? elements.Count : ( start + length ); var end = length == 0 ? elements.Count : ( start + length );

View File

@ -0,0 +1,24 @@
using Godot;
using Rokojori;
using System.Collections.Generic;
using Rokojori.Extensions;
namespace Rokojori
{
[Tool]
[GlobalClass,Icon("res://addons/rokojori_action_library/Icons/UIImage.svg")]
public partial class CustomUIImageType:UIImageType
{
[Export]
public Material material;
protected override void _Assign( UIImage image )
{
image.Material = material;
}
}
}

View File

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

View File

@ -613,7 +613,10 @@ namespace Rokojori
} }
else else
{ {
return control.GetWindow().Size.X; // return control.GetWindow().Size.X;
return control.GetViewport().GetVisibleRect().Size.X;
} }
} }
@ -625,7 +628,8 @@ namespace Rokojori
} }
else else
{ {
return control.GetWindow().Size.Y; // return control.GetWindow().Size.Y;
return control.GetViewport().GetVisibleRect().Size.Y;
} }
} }

View File

@ -15,6 +15,11 @@ public abstract partial class ParticleVFXData:Resource
return; return;
} }
if ( ! IsInstanceValid( particles ) )
{
return;
}
if ( particles.Amount != preset.maxParticles ) if ( particles.Amount != preset.maxParticles )
{ {
particles.Amount = preset.maxParticles; particles.Amount = preset.maxParticles;