@adiholick/nvstudio-mcp 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -3
- package/agent-plugin/rules/AGENTS.md +31 -0
- package/agent-plugin/skills/nvstudio-animation-vfx/SKILL.md +120 -0
- package/agent-plugin/skills/nvstudio-mcp-guide/SKILL.md +14 -0
- package/agent-plugin/skills/nvstudio-rig-engine/SKILL.md +170 -0
- package/dist/index.js +625 -0
- package/dist/roblox-bridge.js +108 -0
- package/dist/vfx-database.js +507 -0
- package/package.json +2 -2
- package/public/index.html +913 -14
- package/studio-plugin/Main.server.lua +1 -1
- package/studio-plugin/animate_character.lua +36 -0
- package/studio-plugin/apply_vfx.lua +59 -0
- package/studio-plugin/edit_rig.lua +35 -0
- package/studio-plugin/nv_anim_dispatcher.lua +239 -0
- package/studio-plugin/nv_auto_rigger.lua +392 -0
- package/studio-plugin/nv_curves.lua +260 -0
- package/studio-plugin/nv_joint_math.lua +138 -0
- package/studio-plugin/nv_kinematics.lua +248 -0
- package/studio-plugin/nv_motion_generator.lua +279 -0
- package/studio-plugin/nv_rig_dispatcher.lua +319 -0
- package/studio-plugin/nv_vfx_serializer.lua +363 -0
- package/studio-plugin/nvstudio_mcp.rbxmx +3008 -115
- package/studio-plugin/scan_vfx.lua +85 -0
- package/studio-plugin/scrub_timeline.lua +122 -0
- package/studio-plugin/set_properties.lua +73 -0
- package/studio-plugin/solve_limb_ik.lua +63 -0
- package/studio-plugin/ui_manager.lua +1 -1
package/public/index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>nvstudio-mcp v2.
|
|
6
|
+
<title>nvstudio-mcp v2.4 | Bridge Dashboard</title>
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
@@ -684,6 +684,410 @@
|
|
|
684
684
|
font-size: 12px;
|
|
685
685
|
}
|
|
686
686
|
|
|
687
|
+
/* ── VFX Database & Library Styles ── */
|
|
688
|
+
.vfx-sidebar-badge {
|
|
689
|
+
font-size: 10px;
|
|
690
|
+
font-weight: 700;
|
|
691
|
+
background: var(--accent);
|
|
692
|
+
color: #fff;
|
|
693
|
+
padding: 1px 7px;
|
|
694
|
+
border-radius: 10px;
|
|
695
|
+
margin-left: auto;
|
|
696
|
+
letter-spacing: 0.02em;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.vfx-container {
|
|
700
|
+
display: flex;
|
|
701
|
+
flex-direction: column;
|
|
702
|
+
gap: 20px;
|
|
703
|
+
padding: 24px 32px 60px 32px;
|
|
704
|
+
overflow-y: auto;
|
|
705
|
+
height: 100%;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
.vfx-header {
|
|
709
|
+
display: flex;
|
|
710
|
+
justify-content: space-between;
|
|
711
|
+
align-items: flex-start;
|
|
712
|
+
gap: 16px;
|
|
713
|
+
flex-wrap: wrap;
|
|
714
|
+
}
|
|
715
|
+
.vfx-header-info h2 {
|
|
716
|
+
font-size: 22px;
|
|
717
|
+
font-weight: 700;
|
|
718
|
+
color: var(--text);
|
|
719
|
+
display: flex;
|
|
720
|
+
align-items: center;
|
|
721
|
+
gap: 10px;
|
|
722
|
+
}
|
|
723
|
+
.vfx-header-info p {
|
|
724
|
+
font-size: 13px;
|
|
725
|
+
color: var(--text-muted);
|
|
726
|
+
margin-top: 4px;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
.vfx-actions-bar {
|
|
730
|
+
display: flex;
|
|
731
|
+
gap: 10px;
|
|
732
|
+
align-items: center;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.vfx-btn-primary {
|
|
736
|
+
background: linear-gradient(135deg, #0088cc 0%, #00aaff 100%);
|
|
737
|
+
color: #fff;
|
|
738
|
+
font-weight: 600;
|
|
739
|
+
font-size: 13px;
|
|
740
|
+
padding: 8px 16px;
|
|
741
|
+
border-radius: 8px;
|
|
742
|
+
border: none;
|
|
743
|
+
cursor: pointer;
|
|
744
|
+
display: inline-flex;
|
|
745
|
+
align-items: center;
|
|
746
|
+
gap: 8px;
|
|
747
|
+
box-shadow: 0 4px 12px var(--accent-glow);
|
|
748
|
+
transition: all 0.2s;
|
|
749
|
+
}
|
|
750
|
+
.vfx-btn-primary:hover {
|
|
751
|
+
transform: translateY(-1px);
|
|
752
|
+
box-shadow: 0 6px 16px var(--accent-glow);
|
|
753
|
+
filter: brightness(1.1);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.vfx-btn-secondary {
|
|
757
|
+
background: rgba(255, 255, 255, 0.05);
|
|
758
|
+
color: var(--text);
|
|
759
|
+
border: 1px solid var(--border);
|
|
760
|
+
font-weight: 600;
|
|
761
|
+
font-size: 13px;
|
|
762
|
+
padding: 8px 14px;
|
|
763
|
+
border-radius: 8px;
|
|
764
|
+
cursor: pointer;
|
|
765
|
+
display: inline-flex;
|
|
766
|
+
align-items: center;
|
|
767
|
+
gap: 6px;
|
|
768
|
+
transition: all 0.2s;
|
|
769
|
+
}
|
|
770
|
+
.vfx-btn-secondary:hover {
|
|
771
|
+
background: rgba(255, 255, 255, 0.1);
|
|
772
|
+
border-color: var(--border-highlight);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.vfx-search-box {
|
|
776
|
+
position: relative;
|
|
777
|
+
flex: 1;
|
|
778
|
+
min-width: 260px;
|
|
779
|
+
max-width: 480px;
|
|
780
|
+
}
|
|
781
|
+
.vfx-search-box input {
|
|
782
|
+
width: 100%;
|
|
783
|
+
background: #0d0d0d;
|
|
784
|
+
border: 1px solid var(--border);
|
|
785
|
+
border-radius: 8px;
|
|
786
|
+
padding: 9px 14px 9px 36px;
|
|
787
|
+
color: var(--text);
|
|
788
|
+
font-size: 13px;
|
|
789
|
+
outline: none;
|
|
790
|
+
transition: all 0.2s;
|
|
791
|
+
}
|
|
792
|
+
.vfx-search-box input:focus {
|
|
793
|
+
border-color: var(--accent);
|
|
794
|
+
box-shadow: 0 0 10px var(--accent-glow);
|
|
795
|
+
}
|
|
796
|
+
.vfx-search-box svg {
|
|
797
|
+
position: absolute;
|
|
798
|
+
left: 12px;
|
|
799
|
+
top: 50%;
|
|
800
|
+
transform: translateY(-50%);
|
|
801
|
+
width: 15px;
|
|
802
|
+
height: 15px;
|
|
803
|
+
color: var(--text-dim);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/* Category Filter Pills */
|
|
807
|
+
.vfx-category-tabs {
|
|
808
|
+
display: flex;
|
|
809
|
+
align-items: center;
|
|
810
|
+
gap: 8px;
|
|
811
|
+
overflow-x: auto;
|
|
812
|
+
padding-bottom: 6px;
|
|
813
|
+
}
|
|
814
|
+
.vfx-cat-pill {
|
|
815
|
+
background: #18181d;
|
|
816
|
+
border: 1px solid var(--border);
|
|
817
|
+
color: var(--text-muted);
|
|
818
|
+
font-size: 12px;
|
|
819
|
+
font-weight: 600;
|
|
820
|
+
padding: 6px 14px;
|
|
821
|
+
border-radius: 20px;
|
|
822
|
+
cursor: pointer;
|
|
823
|
+
white-space: nowrap;
|
|
824
|
+
display: inline-flex;
|
|
825
|
+
align-items: center;
|
|
826
|
+
gap: 6px;
|
|
827
|
+
transition: all 0.2s;
|
|
828
|
+
}
|
|
829
|
+
.vfx-cat-pill:hover {
|
|
830
|
+
color: var(--text);
|
|
831
|
+
border-color: var(--border-highlight);
|
|
832
|
+
background: #222228;
|
|
833
|
+
}
|
|
834
|
+
.vfx-cat-pill.active {
|
|
835
|
+
background: var(--accent);
|
|
836
|
+
color: #fff;
|
|
837
|
+
border-color: var(--accent);
|
|
838
|
+
box-shadow: 0 2px 10px var(--accent-glow);
|
|
839
|
+
}
|
|
840
|
+
.vfx-cat-count {
|
|
841
|
+
font-size: 10px;
|
|
842
|
+
background: rgba(0, 0, 0, 0.35);
|
|
843
|
+
padding: 1px 6px;
|
|
844
|
+
border-radius: 10px;
|
|
845
|
+
opacity: 0.9;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/* Summary Stats Cards */
|
|
849
|
+
.vfx-stats-row {
|
|
850
|
+
display: grid;
|
|
851
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
852
|
+
gap: 14px;
|
|
853
|
+
}
|
|
854
|
+
.vfx-stat-box {
|
|
855
|
+
background: var(--surface-card);
|
|
856
|
+
border: 1px solid var(--border);
|
|
857
|
+
border-radius: 10px;
|
|
858
|
+
padding: 16px 20px;
|
|
859
|
+
display: flex;
|
|
860
|
+
flex-direction: column;
|
|
861
|
+
gap: 4px;
|
|
862
|
+
}
|
|
863
|
+
.vfx-stat-label { font-size: 11px; text-transform: uppercase; color: var(--text-dim); font-weight: 600; letter-spacing: 0.05em; }
|
|
864
|
+
.vfx-stat-val { font-size: 24px; font-weight: 700; color: var(--text); }
|
|
865
|
+
.vfx-stat-sub { font-size: 12px; color: var(--text-muted); }
|
|
866
|
+
|
|
867
|
+
/* VFX Grid & Cards */
|
|
868
|
+
.vfx-grid {
|
|
869
|
+
display: grid;
|
|
870
|
+
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
871
|
+
gap: 16px;
|
|
872
|
+
}
|
|
873
|
+
.vfx-card {
|
|
874
|
+
background: #18181d;
|
|
875
|
+
border: 1px solid var(--border);
|
|
876
|
+
border-radius: 10px;
|
|
877
|
+
padding: 20px;
|
|
878
|
+
display: flex;
|
|
879
|
+
flex-direction: column;
|
|
880
|
+
gap: 14px;
|
|
881
|
+
transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
882
|
+
position: relative;
|
|
883
|
+
}
|
|
884
|
+
.vfx-card:hover {
|
|
885
|
+
border-color: rgba(0, 170, 255, 0.4);
|
|
886
|
+
transform: translateY(-2px);
|
|
887
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.vfx-card-top {
|
|
891
|
+
display: flex;
|
|
892
|
+
justify-content: space-between;
|
|
893
|
+
align-items: flex-start;
|
|
894
|
+
gap: 10px;
|
|
895
|
+
}
|
|
896
|
+
.vfx-card-title-group h4 {
|
|
897
|
+
font-size: 15px;
|
|
898
|
+
font-weight: 600;
|
|
899
|
+
color: var(--text);
|
|
900
|
+
letter-spacing: -0.01em;
|
|
901
|
+
}
|
|
902
|
+
.vfx-card-date {
|
|
903
|
+
font-size: 11px;
|
|
904
|
+
color: var(--text-dim);
|
|
905
|
+
margin-top: 2px;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.vfx-badge-cat {
|
|
909
|
+
font-size: 10px;
|
|
910
|
+
font-weight: 700;
|
|
911
|
+
text-transform: uppercase;
|
|
912
|
+
padding: 3px 8px;
|
|
913
|
+
border-radius: 6px;
|
|
914
|
+
letter-spacing: 0.04em;
|
|
915
|
+
}
|
|
916
|
+
.vfx-badge-Slash { background: rgba(0, 170, 255, 0.15); color: #00aaff; border: 1px solid rgba(0, 170, 255, 0.3); }
|
|
917
|
+
.vfx-badge-Explosion { background: rgba(245, 158, 11, 0.15); color: #f59e0b; border: 1px solid rgba(245, 158, 11, 0.3); }
|
|
918
|
+
.vfx-badge-Aura { background: rgba(16, 185, 129, 0.15); color: #10b981; border: 1px solid rgba(16, 185, 129, 0.3); }
|
|
919
|
+
.vfx-badge-Magic { background: rgba(168, 85, 247, 0.15); color: #c084fc; border: 1px solid rgba(168, 85, 247, 0.3); }
|
|
920
|
+
.vfx-badge-Impact { background: rgba(239, 68, 68, 0.15); color: #f87171; border: 1px solid rgba(239, 68, 68, 0.3); }
|
|
921
|
+
.vfx-badge-Beam { background: rgba(6, 182, 212, 0.15); color: #22d3ee; border: 1px solid rgba(6, 182, 212, 0.3); }
|
|
922
|
+
.vfx-badge-Projectile { background: rgba(236, 72, 153, 0.15); color: #f472b6; border: 1px solid rgba(236, 72, 153, 0.3); }
|
|
923
|
+
.vfx-badge-Ambient { background: rgba(161, 161, 170, 0.15); color: #a1a1aa; border: 1px solid rgba(161, 161, 170, 0.3); }
|
|
924
|
+
|
|
925
|
+
.vfx-card-desc {
|
|
926
|
+
font-size: 13px;
|
|
927
|
+
color: var(--text-muted);
|
|
928
|
+
line-height: 1.5;
|
|
929
|
+
min-height: 38px;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
.vfx-composition-badges {
|
|
933
|
+
display: flex;
|
|
934
|
+
flex-wrap: wrap;
|
|
935
|
+
gap: 6px;
|
|
936
|
+
}
|
|
937
|
+
.vfx-comp-pill {
|
|
938
|
+
font-size: 11px;
|
|
939
|
+
background: rgba(255, 255, 255, 0.05);
|
|
940
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
941
|
+
color: var(--text-muted);
|
|
942
|
+
padding: 2px 7px;
|
|
943
|
+
border-radius: 4px;
|
|
944
|
+
font-family: 'JetBrains Mono', monospace;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.vfx-card-tags {
|
|
948
|
+
display: flex;
|
|
949
|
+
flex-wrap: wrap;
|
|
950
|
+
gap: 4px;
|
|
951
|
+
}
|
|
952
|
+
.vfx-tag-chip {
|
|
953
|
+
font-size: 11px;
|
|
954
|
+
color: var(--text-dim);
|
|
955
|
+
background: #0d0d0d;
|
|
956
|
+
padding: 2px 6px;
|
|
957
|
+
border-radius: 4px;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
.vfx-card-footer {
|
|
961
|
+
margin-top: auto;
|
|
962
|
+
display: flex;
|
|
963
|
+
gap: 8px;
|
|
964
|
+
padding-top: 12px;
|
|
965
|
+
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
|
966
|
+
}
|
|
967
|
+
.vfx-btn-apply {
|
|
968
|
+
flex: 1;
|
|
969
|
+
background: rgba(0, 170, 255, 0.12);
|
|
970
|
+
color: var(--accent);
|
|
971
|
+
border: 1px solid rgba(0, 170, 255, 0.3);
|
|
972
|
+
font-size: 12px;
|
|
973
|
+
font-weight: 600;
|
|
974
|
+
padding: 7px 12px;
|
|
975
|
+
border-radius: 6px;
|
|
976
|
+
cursor: pointer;
|
|
977
|
+
display: inline-flex;
|
|
978
|
+
align-items: center;
|
|
979
|
+
justify-content: center;
|
|
980
|
+
gap: 6px;
|
|
981
|
+
transition: all 0.2s;
|
|
982
|
+
}
|
|
983
|
+
.vfx-btn-apply:hover {
|
|
984
|
+
background: var(--accent);
|
|
985
|
+
color: #fff;
|
|
986
|
+
box-shadow: 0 0 12px var(--accent-glow);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
.vfx-btn-json {
|
|
990
|
+
background: rgba(255, 255, 255, 0.04);
|
|
991
|
+
color: var(--text-muted);
|
|
992
|
+
border: 1px solid var(--border);
|
|
993
|
+
font-size: 12px;
|
|
994
|
+
padding: 7px 10px;
|
|
995
|
+
border-radius: 6px;
|
|
996
|
+
cursor: pointer;
|
|
997
|
+
transition: all 0.2s;
|
|
998
|
+
}
|
|
999
|
+
.vfx-btn-json:hover {
|
|
1000
|
+
background: rgba(255, 255, 255, 0.09);
|
|
1001
|
+
color: var(--text);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
.vfx-btn-del {
|
|
1005
|
+
background: rgba(239, 68, 68, 0.08);
|
|
1006
|
+
color: #f87171;
|
|
1007
|
+
border: 1px solid rgba(239, 68, 68, 0.2);
|
|
1008
|
+
font-size: 12px;
|
|
1009
|
+
padding: 7px 10px;
|
|
1010
|
+
border-radius: 6px;
|
|
1011
|
+
cursor: pointer;
|
|
1012
|
+
transition: all 0.2s;
|
|
1013
|
+
}
|
|
1014
|
+
.vfx-btn-del:hover {
|
|
1015
|
+
background: #ef4444;
|
|
1016
|
+
color: #fff;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/* Modals */
|
|
1020
|
+
.vfx-modal-backdrop {
|
|
1021
|
+
display: none;
|
|
1022
|
+
position: fixed;
|
|
1023
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
1024
|
+
background: rgba(0, 0, 0, 0.7);
|
|
1025
|
+
backdrop-filter: blur(8px);
|
|
1026
|
+
z-index: 1000;
|
|
1027
|
+
align-items: center;
|
|
1028
|
+
justify-content: center;
|
|
1029
|
+
padding: 20px;
|
|
1030
|
+
}
|
|
1031
|
+
.vfx-modal-backdrop.open { display: flex; }
|
|
1032
|
+
.vfx-modal-card {
|
|
1033
|
+
background: #18181d;
|
|
1034
|
+
border: 1px solid var(--border-highlight);
|
|
1035
|
+
border-radius: 12px;
|
|
1036
|
+
max-width: 600px;
|
|
1037
|
+
width: 100%;
|
|
1038
|
+
max-height: 85vh;
|
|
1039
|
+
display: flex;
|
|
1040
|
+
flex-direction: column;
|
|
1041
|
+
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.6);
|
|
1042
|
+
overflow: hidden;
|
|
1043
|
+
}
|
|
1044
|
+
.vfx-modal-header {
|
|
1045
|
+
padding: 18px 24px;
|
|
1046
|
+
border-bottom: 1px solid var(--border);
|
|
1047
|
+
display: flex;
|
|
1048
|
+
align-items: center;
|
|
1049
|
+
justify-content: space-between;
|
|
1050
|
+
}
|
|
1051
|
+
.vfx-modal-header h3 { font-size: 16px; font-weight: 600; color: #fff; }
|
|
1052
|
+
.vfx-modal-close {
|
|
1053
|
+
background: none; border: none; color: var(--text-dim); cursor: pointer; font-size: 20px; line-height: 1;
|
|
1054
|
+
}
|
|
1055
|
+
.vfx-modal-close:hover { color: #fff; }
|
|
1056
|
+
.vfx-modal-body {
|
|
1057
|
+
padding: 20px 24px;
|
|
1058
|
+
overflow-y: auto;
|
|
1059
|
+
display: flex;
|
|
1060
|
+
flex-direction: column;
|
|
1061
|
+
gap: 16px;
|
|
1062
|
+
}
|
|
1063
|
+
.vfx-modal-footer {
|
|
1064
|
+
padding: 16px 24px;
|
|
1065
|
+
border-top: 1px solid var(--border);
|
|
1066
|
+
display: flex;
|
|
1067
|
+
justify-content: flex-end;
|
|
1068
|
+
gap: 10px;
|
|
1069
|
+
background: rgba(0, 0, 0, 0.2);
|
|
1070
|
+
}
|
|
1071
|
+
.vfx-form-group {
|
|
1072
|
+
display: flex;
|
|
1073
|
+
flex-direction: column;
|
|
1074
|
+
gap: 6px;
|
|
1075
|
+
}
|
|
1076
|
+
.vfx-form-label { font-size: 12px; font-weight: 600; color: var(--text-muted); }
|
|
1077
|
+
.vfx-form-input, .vfx-form-select {
|
|
1078
|
+
background: #0d0d0d;
|
|
1079
|
+
border: 1px solid var(--border);
|
|
1080
|
+
border-radius: 6px;
|
|
1081
|
+
padding: 9px 12px;
|
|
1082
|
+
color: var(--text);
|
|
1083
|
+
font-size: 13px;
|
|
1084
|
+
outline: none;
|
|
1085
|
+
}
|
|
1086
|
+
.vfx-form-input:focus, .vfx-form-select:focus {
|
|
1087
|
+
border-color: var(--accent);
|
|
1088
|
+
box-shadow: 0 0 8px var(--accent-glow);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
687
1091
|
/* Scrollbar */
|
|
688
1092
|
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
689
1093
|
::-webkit-scrollbar-track { background: transparent; }
|
|
@@ -734,6 +1138,11 @@
|
|
|
734
1138
|
</div>
|
|
735
1139
|
|
|
736
1140
|
<div class="nav-group-label" style="margin-top:20px;">Engine Tools</div>
|
|
1141
|
+
<div class="nav-item" data-page="vfx-database">
|
|
1142
|
+
<svg viewBox="0 0 24 24"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
|
1143
|
+
VFX Database
|
|
1144
|
+
<span class="vfx-sidebar-badge" id="vfx-sidebar-count">0</span>
|
|
1145
|
+
</div>
|
|
737
1146
|
<div class="nav-item" data-page="docs">
|
|
738
1147
|
<svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
|
739
1148
|
Docs Explorer
|
|
@@ -742,12 +1151,12 @@
|
|
|
742
1151
|
<div class="nav-group-label" style="margin-top:20px;">Info</div>
|
|
743
1152
|
<div class="nav-item" data-page="about">
|
|
744
1153
|
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
|
745
|
-
About v2.
|
|
1154
|
+
About v2.4
|
|
746
1155
|
</div>
|
|
747
1156
|
</nav>
|
|
748
1157
|
|
|
749
1158
|
<div class="sidebar-footer">
|
|
750
|
-
<span class="badge-v2" id="pkg-version">v2.
|
|
1159
|
+
<span class="badge-v2" id="pkg-version">v2.4.0</span>
|
|
751
1160
|
<span style="font-size:10px; opacity:0.6;">Model Context Protocol</span>
|
|
752
1161
|
</div>
|
|
753
1162
|
</aside>
|
|
@@ -917,11 +1326,165 @@
|
|
|
917
1326
|
</div>
|
|
918
1327
|
</div>
|
|
919
1328
|
|
|
1329
|
+
<!-- VFX Database & Library Page -->
|
|
1330
|
+
<div class="page" id="page-vfx-database">
|
|
1331
|
+
<div class="vfx-container">
|
|
1332
|
+
<div class="vfx-header">
|
|
1333
|
+
<div class="vfx-header-info">
|
|
1334
|
+
<h2>✨ NV-VFX Library & Database <span class="v2-chip" id="vfx-hero-badge">Portable</span></h2>
|
|
1335
|
+
<p>Katalog efek visual portabel lintas-proyek Roblox (ParticleEmitter, Beam, Trail, Light). Pindai dari satu place dan pasang ke place lain secara instan.</p>
|
|
1336
|
+
</div>
|
|
1337
|
+
<div class="vfx-actions-bar">
|
|
1338
|
+
<div class="vfx-search-box">
|
|
1339
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
|
1340
|
+
<input type="text" id="vfx-search-input" placeholder="Cari VFX berdasarkan nama, tag, atau kategori...">
|
|
1341
|
+
</div>
|
|
1342
|
+
<button class="vfx-btn-primary" id="btn-open-scan-modal">
|
|
1343
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
1344
|
+
Scan dari Studio
|
|
1345
|
+
</button>
|
|
1346
|
+
<button class="vfx-btn-secondary" id="btn-refresh-vfx" title="Muat ulang data">
|
|
1347
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
|
|
1348
|
+
</button>
|
|
1349
|
+
</div>
|
|
1350
|
+
</div>
|
|
1351
|
+
|
|
1352
|
+
<!-- Category Pills Segment Bar -->
|
|
1353
|
+
<div class="vfx-category-tabs" id="vfx-category-tabs">
|
|
1354
|
+
<button class="vfx-cat-pill active" data-cat="All">Semua <span class="vfx-cat-count" id="vfx-count-all">0</span></button>
|
|
1355
|
+
</div>
|
|
1356
|
+
|
|
1357
|
+
<!-- Stats Overview Row -->
|
|
1358
|
+
<div class="vfx-stats-row">
|
|
1359
|
+
<div class="vfx-stat-box">
|
|
1360
|
+
<div class="vfx-stat-label">Total Efek Visual</div>
|
|
1361
|
+
<div class="vfx-stat-val" id="vfx-stat-total">0</div>
|
|
1362
|
+
<div class="vfx-stat-sub">Tersimpan di database MCP</div>
|
|
1363
|
+
</div>
|
|
1364
|
+
<div class="vfx-stat-box">
|
|
1365
|
+
<div class="vfx-stat-label">Kategori Aktif</div>
|
|
1366
|
+
<div class="vfx-stat-val" id="vfx-stat-categories" style="color:#00aaff;">0</div>
|
|
1367
|
+
<div class="vfx-stat-sub">Slash, Aura, Magic, Explosion dll</div>
|
|
1368
|
+
</div>
|
|
1369
|
+
<div class="vfx-stat-box">
|
|
1370
|
+
<div class="vfx-stat-label">Total Emitters</div>
|
|
1371
|
+
<div class="vfx-stat-val" id="vfx-stat-emitters" style="color:#f59e0b;">0</div>
|
|
1372
|
+
<div class="vfx-stat-sub">ParticleEmitter aktif</div>
|
|
1373
|
+
</div>
|
|
1374
|
+
<div class="vfx-stat-box">
|
|
1375
|
+
<div class="vfx-stat-label">Beams & Trails</div>
|
|
1376
|
+
<div class="vfx-stat-val" id="vfx-stat-beams" style="color:#10b981;">0</div>
|
|
1377
|
+
<div class="vfx-stat-sub">Elemen trajektori & sinar</div>
|
|
1378
|
+
</div>
|
|
1379
|
+
</div>
|
|
1380
|
+
|
|
1381
|
+
<!-- Cards Grid -->
|
|
1382
|
+
<div class="vfx-grid" id="vfx-cards-grid">
|
|
1383
|
+
<!-- Kartu VFX di-render secara dinamis oleh JavaScript -->
|
|
1384
|
+
</div>
|
|
1385
|
+
</div>
|
|
1386
|
+
</div>
|
|
1387
|
+
|
|
1388
|
+
<!-- Modal: Scan VFX dari Studio -->
|
|
1389
|
+
<div class="vfx-modal-backdrop" id="modal-scan-vfx">
|
|
1390
|
+
<div class="vfx-modal-card">
|
|
1391
|
+
<div class="vfx-modal-header">
|
|
1392
|
+
<h3>📥 Pindai VFX dari Studio Aktif</h3>
|
|
1393
|
+
<button class="vfx-modal-close" onclick="closeVfxModals()">×</button>
|
|
1394
|
+
</div>
|
|
1395
|
+
<div class="vfx-modal-body">
|
|
1396
|
+
<div class="vfx-form-group">
|
|
1397
|
+
<label class="vfx-form-label">Path Objek di Studio (Target)</label>
|
|
1398
|
+
<input type="text" class="vfx-form-input" id="scan-input-target" placeholder="contoh: Workspace.FlameBurst atau Selection" value="Workspace.">
|
|
1399
|
+
</div>
|
|
1400
|
+
<div class="vfx-form-group">
|
|
1401
|
+
<label class="vfx-form-label">Nama VFX</label>
|
|
1402
|
+
<input type="text" class="vfx-form-input" id="scan-input-name" placeholder="contoh: Crimson_Flame_Slash">
|
|
1403
|
+
</div>
|
|
1404
|
+
<div class="vfx-form-group">
|
|
1405
|
+
<label class="vfx-form-label">Kategori</label>
|
|
1406
|
+
<select class="vfx-form-select" id="scan-select-category">
|
|
1407
|
+
<option value="Slash">Slash (Tebasan & Bilah)</option>
|
|
1408
|
+
<option value="Explosion">Explosion (Ledakan & Api)</option>
|
|
1409
|
+
<option value="Aura">Aura (Glow & Perisai)</option>
|
|
1410
|
+
<option value="Magic">Magic (Sihir & Lingkaran Rune)</option>
|
|
1411
|
+
<option value="Beam">Beam (Laser & Sinar Energi)</option>
|
|
1412
|
+
<option value="Impact">Impact (Benturan & Hantaman)</option>
|
|
1413
|
+
<option value="Projectile">Projectile (Proyektil & Peluru)</option>
|
|
1414
|
+
<option value="Ambient">Ambient (Lingkungan & Partikel Halus)</option>
|
|
1415
|
+
</select>
|
|
1416
|
+
</div>
|
|
1417
|
+
<div class="vfx-form-group">
|
|
1418
|
+
<label class="vfx-form-label">Tags (Pisahkan dengan koma)</label>
|
|
1419
|
+
<input type="text" class="vfx-form-input" id="scan-input-tags" placeholder="contoh: anime, sword, fire, fast">
|
|
1420
|
+
</div>
|
|
1421
|
+
</div>
|
|
1422
|
+
<div class="vfx-modal-footer">
|
|
1423
|
+
<button class="vfx-btn-secondary" onclick="closeVfxModals()">Batal</button>
|
|
1424
|
+
<button class="vfx-btn-primary" id="btn-submit-scan">Mulai Pindai</button>
|
|
1425
|
+
</div>
|
|
1426
|
+
</div>
|
|
1427
|
+
</div>
|
|
1428
|
+
|
|
1429
|
+
<!-- Modal: Apply VFX ke Studio -->
|
|
1430
|
+
<div class="vfx-modal-backdrop" id="modal-apply-vfx">
|
|
1431
|
+
<div class="vfx-modal-card">
|
|
1432
|
+
<div class="vfx-modal-header">
|
|
1433
|
+
<h3>⚡ Terapkan VFX ke Studio</h3>
|
|
1434
|
+
<button class="vfx-modal-close" onclick="closeVfxModals()">×</button>
|
|
1435
|
+
</div>
|
|
1436
|
+
<div class="vfx-modal-body">
|
|
1437
|
+
<input type="hidden" id="apply-vfx-id">
|
|
1438
|
+
<div class="vfx-form-group">
|
|
1439
|
+
<label class="vfx-form-label">VFX Terpilih</label>
|
|
1440
|
+
<div id="apply-vfx-title" style="font-weight:600; color:var(--accent);"></div>
|
|
1441
|
+
</div>
|
|
1442
|
+
<div class="vfx-form-group">
|
|
1443
|
+
<label class="vfx-form-label">Target Instance di Studio</label>
|
|
1444
|
+
<input type="text" class="vfx-form-input" id="apply-input-target" placeholder="contoh: Workspace.Dummy.RightHand atau Workspace" value="Workspace">
|
|
1445
|
+
</div>
|
|
1446
|
+
<div class="vfx-form-group">
|
|
1447
|
+
<label class="vfx-form-label">Skala Ukuran (Scale Factor)</label>
|
|
1448
|
+
<input type="number" class="vfx-form-input" id="apply-input-scale" value="1.0" step="0.1" min="0.1" max="10">
|
|
1449
|
+
</div>
|
|
1450
|
+
<div class="vfx-form-group">
|
|
1451
|
+
<label class="vfx-form-label">Status Partikel (Enabled)</label>
|
|
1452
|
+
<select class="vfx-form-select" id="apply-select-enabled">
|
|
1453
|
+
<option value="default">Sesuai Preset / Default</option>
|
|
1454
|
+
<option value="true">Paksa Aktif (Enabled = true)</option>
|
|
1455
|
+
<option value="false">Paksa Nonaktif (Enabled = false)</option>
|
|
1456
|
+
</select>
|
|
1457
|
+
</div>
|
|
1458
|
+
</div>
|
|
1459
|
+
<div class="vfx-modal-footer">
|
|
1460
|
+
<button class="vfx-btn-secondary" onclick="closeVfxModals()">Batal</button>
|
|
1461
|
+
<button class="vfx-btn-primary" id="btn-submit-apply">Terapkan Sekarang</button>
|
|
1462
|
+
</div>
|
|
1463
|
+
</div>
|
|
1464
|
+
</div>
|
|
1465
|
+
|
|
1466
|
+
<!-- Modal: View JSON / Inspect Data -->
|
|
1467
|
+
<div class="vfx-modal-backdrop" id="modal-json-vfx">
|
|
1468
|
+
<div class="vfx-modal-card" style="max-width:700px;">
|
|
1469
|
+
<div class="vfx-modal-header">
|
|
1470
|
+
<h3 id="json-modal-title">Inspeksi Data Murni VFX</h3>
|
|
1471
|
+
<button class="vfx-modal-close" onclick="closeVfxModals()">×</button>
|
|
1472
|
+
</div>
|
|
1473
|
+
<div class="vfx-modal-body" style="padding:0;">
|
|
1474
|
+
<pre id="json-modal-content" style="background:#0d0d0d; padding:16px; margin:0; font-family:'JetBrains Mono', monospace; font-size:12px; color:#e0e0e0; overflow:auto; max-height:55vh;"></pre>
|
|
1475
|
+
</div>
|
|
1476
|
+
<div class="vfx-modal-footer">
|
|
1477
|
+
<button class="vfx-btn-secondary" id="btn-copy-vfx-json">Salin JSON</button>
|
|
1478
|
+
<button class="vfx-btn-primary" onclick="closeVfxModals()">Tutup</button>
|
|
1479
|
+
</div>
|
|
1480
|
+
</div>
|
|
1481
|
+
</div>
|
|
1482
|
+
|
|
920
1483
|
<!-- About Page -->
|
|
921
1484
|
<div class="page" id="page-about">
|
|
922
1485
|
<div class="about-section">
|
|
923
1486
|
<div class="about-hero">
|
|
924
|
-
<h2>nvstudio-mcp <span class="v2-chip" id="about-version-chip">v2.
|
|
1487
|
+
<h2>nvstudio-mcp <span class="v2-chip" id="about-version-chip">v2.4.0</span></h2>
|
|
925
1488
|
<p>Jembatan pintar berbasis <strong>Model Context Protocol (MCP)</strong> yang menghubungkan AI Agent (Antigravity IDE, Cursor, Claude Desktop, Windsurf) secara langsung ke dalam <strong>Roblox Studio</strong>.</p>
|
|
926
1489
|
<div class="about-links">
|
|
927
1490
|
<a class="about-link" href="https://github.com/Adiholick/nvstudio-mcp" target="_blank">
|
|
@@ -936,23 +1499,25 @@
|
|
|
936
1499
|
</div>
|
|
937
1500
|
|
|
938
1501
|
<div class="about-card">
|
|
939
|
-
<h3>🚀 Fitur Baru v2.
|
|
1502
|
+
<h3>🚀 Fitur Baru v2.4.0</h3>
|
|
940
1503
|
<ul>
|
|
941
|
-
<li><strong
|
|
942
|
-
<li><strong
|
|
943
|
-
<li><strong
|
|
944
|
-
<li><strong
|
|
1504
|
+
<li><strong>🦴 NV-Rig Engine:</strong> Rigging headless presisi $O(1)$ untuk R6, R15, dan Custom Rig. Menyediakan inspeksi sendi (<code>inspect_rig</code>), pemindahan poros C0/C1 simultan anti-dislokasi (<code>edit_rig_joint</code>), auto-rigging instan (<code>auto_rig</code>), dan assembly welding senjata/prop (<code>weld_assembly</code>).</li>
|
|
1505
|
+
<li><strong>🎬 NV-Animation Engine:</strong> Pembuatan animasi karakter halus alami (Walk, Slash, Punch, Idle, IK) langsung menjadi <code>KeyframeSequence</code> resmi dengan interpolasi Bezier (Cubic/Quad) dan penegakan batas anatomis aman tanpa glitch.</li>
|
|
1506
|
+
<li><strong>✨ NV-VFX Subsystem & Database:</strong> Serialisasi efek visual (ParticleEmitter, Beam, Trail, PointLight) lintas-proyek dengan tool <code>scan_vfx</code> dan rekonstruksi instan via <code>apply_vfx</code>, dilengkapi web category browser interaktif.</li>
|
|
1507
|
+
<li><strong>⚡ Batch Instance Builder & Security:</strong> Membangun hierarki massal, sanitasi keamanan backdoor aset, dan live Creator Docs lookup.</li>
|
|
945
1508
|
</ul>
|
|
946
1509
|
</div>
|
|
947
1510
|
|
|
948
1511
|
<div class="about-card">
|
|
949
1512
|
<h3>🛠️ Daftar Semantic MCP Tools Lengkap</h3>
|
|
950
1513
|
<ul>
|
|
951
|
-
<li><code>
|
|
952
|
-
<li><code>
|
|
1514
|
+
<li><code>inspect_rig</code> / <code>edit_rig_joint</code> / <code>auto_rig</code> / <code>weld_assembly</code> — NV-Rig Engine headless.</li>
|
|
1515
|
+
<li><code>animate_character</code> / <code>solve_limb_ik</code> / <code>scrub_timeline</code> — NV-Animation Engine.</li>
|
|
1516
|
+
<li><code>scan_vfx</code> / <code>apply_vfx</code> / <code>list_vfx_database</code> / <code>delete_vfx</code> — NV-VFX Subsystem & Database.</li>
|
|
1517
|
+
<li><code>batch_create_instances</code> / <code>batch_set_properties</code> — Pembuatan objek/hierarki massal pohon bersarang.</li>
|
|
953
1518
|
<li><code>get_roblox_docs</code> — Lookup dokumentasi resmi engine Roblox (Class, Datatype, Service, Enum).</li>
|
|
954
1519
|
<li><code>preview_asset</code> / <code>scan_security</code> — Audit keamanan backdoor & sanitasi script model.</li>
|
|
955
|
-
<li><code>patch_script_source</code> — Mengedit
|
|
1520
|
+
<li><code>patch_script_source</code> / <code>update_script_source</code> — Mengedit script Roblox secara terisolasi.</li>
|
|
956
1521
|
<li><code>execute_luau</code> — Eksekusi sembarang kode Luau dinamis di Studio.</li>
|
|
957
1522
|
<li><code>get_full_tree</code> / <code>get_children</code> — Eksplorasi hierarki DataModel.</li>
|
|
958
1523
|
<li><code>insert_asset</code> / <code>generate_terrain</code> — Memasukkan model atau membuat terrain voxel.</li>
|
|
@@ -990,8 +1555,9 @@
|
|
|
990
1555
|
const pageTitles = {
|
|
991
1556
|
activity: 'Activity Log',
|
|
992
1557
|
stats: 'Session Diagnostics & Studios',
|
|
1558
|
+
'vfx-database': 'NV-VFX Database & Library',
|
|
993
1559
|
docs: 'Roblox Docs Explorer',
|
|
994
|
-
about: 'About v2.
|
|
1560
|
+
about: 'About v2.4'
|
|
995
1561
|
};
|
|
996
1562
|
|
|
997
1563
|
navItems.forEach(item => {
|
|
@@ -1083,7 +1649,13 @@
|
|
|
1083
1649
|
}
|
|
1084
1650
|
|
|
1085
1651
|
function escapeHtml(str) {
|
|
1086
|
-
|
|
1652
|
+
if (str === null || str === undefined) return '';
|
|
1653
|
+
return String(str)
|
|
1654
|
+
.replace(/&/g, '&')
|
|
1655
|
+
.replace(/</g, '<')
|
|
1656
|
+
.replace(/>/g, '>')
|
|
1657
|
+
.replace(/"/g, '"')
|
|
1658
|
+
.replace(/'/g, ''');
|
|
1087
1659
|
}
|
|
1088
1660
|
|
|
1089
1661
|
function shouldShowLog(log) {
|
|
@@ -1364,6 +1936,329 @@
|
|
|
1364
1936
|
}
|
|
1365
1937
|
});
|
|
1366
1938
|
|
|
1939
|
+
/* ── VFX Database & Library Controller ── */
|
|
1940
|
+
let currentVfxItems = [];
|
|
1941
|
+
let currentVfxCategories = [];
|
|
1942
|
+
let activeVfxCategory = 'All';
|
|
1943
|
+
let vfxSearchTimeout = null;
|
|
1944
|
+
|
|
1945
|
+
const vfxSidebarCount = document.getElementById('vfx-sidebar-count');
|
|
1946
|
+
const vfxCategoryTabs = document.getElementById('vfx-category-tabs');
|
|
1947
|
+
const vfxCardsGrid = document.getElementById('vfx-cards-grid');
|
|
1948
|
+
const vfxSearchInput = document.getElementById('vfx-search-input');
|
|
1949
|
+
const vfxStatTotal = document.getElementById('vfx-stat-total');
|
|
1950
|
+
const vfxStatCategories = document.getElementById('vfx-stat-categories');
|
|
1951
|
+
const vfxStatEmitters = document.getElementById('vfx-stat-emitters');
|
|
1952
|
+
const vfxStatBeams = document.getElementById('vfx-stat-beams');
|
|
1953
|
+
|
|
1954
|
+
const modalScanVfx = document.getElementById('modal-scan-vfx');
|
|
1955
|
+
const modalApplyVfx = document.getElementById('modal-apply-vfx');
|
|
1956
|
+
const modalJsonVfx = document.getElementById('modal-json-vfx');
|
|
1957
|
+
|
|
1958
|
+
window.closeVfxModals = function() {
|
|
1959
|
+
modalScanVfx.classList.remove('open');
|
|
1960
|
+
modalApplyVfx.classList.remove('open');
|
|
1961
|
+
modalJsonVfx.classList.remove('open');
|
|
1962
|
+
};
|
|
1963
|
+
|
|
1964
|
+
window.openApplyModal = function(id) {
|
|
1965
|
+
const decodedId = decodeURIComponent(id);
|
|
1966
|
+
const item = currentVfxItems.find(i => i.id === decodedId);
|
|
1967
|
+
if (!item) return;
|
|
1968
|
+
document.getElementById('apply-vfx-id').value = item.id;
|
|
1969
|
+
document.getElementById('apply-vfx-title').textContent = item.name;
|
|
1970
|
+
modalApplyVfx.classList.add('open');
|
|
1971
|
+
};
|
|
1972
|
+
|
|
1973
|
+
window.openJsonModal = function(id) {
|
|
1974
|
+
const decodedId = decodeURIComponent(id);
|
|
1975
|
+
const item = currentVfxItems.find(i => i.id === decodedId);
|
|
1976
|
+
if (!item) return;
|
|
1977
|
+
document.getElementById('json-modal-title').textContent = 'Pure Data: ' + item.name;
|
|
1978
|
+
document.getElementById('json-modal-content').textContent = JSON.stringify(item.data, null, 2);
|
|
1979
|
+
modalJsonVfx.classList.add('open');
|
|
1980
|
+
};
|
|
1981
|
+
|
|
1982
|
+
window.deleteVfxItem = async function(id) {
|
|
1983
|
+
const decodedId = decodeURIComponent(id);
|
|
1984
|
+
const item = currentVfxItems.find(i => i.id === decodedId);
|
|
1985
|
+
const name = item ? item.name : decodedId;
|
|
1986
|
+
if (!confirm(`Apakah Anda yakin ingin menghapus VFX "${name}" dari database?`)) return;
|
|
1987
|
+
try {
|
|
1988
|
+
const res = await fetch('/api/vfx/' + encodeURIComponent(decodedId), { method: 'DELETE' });
|
|
1989
|
+
if (res.ok) {
|
|
1990
|
+
loadVfxDatabase();
|
|
1991
|
+
} else {
|
|
1992
|
+
alert('Gagal menghapus VFX');
|
|
1993
|
+
}
|
|
1994
|
+
} catch (err) {
|
|
1995
|
+
alert('Error: ' + err.message);
|
|
1996
|
+
}
|
|
1997
|
+
};
|
|
1998
|
+
|
|
1999
|
+
async function loadVfxDatabase() {
|
|
2000
|
+
try {
|
|
2001
|
+
const [vfxRes, catRes] = await Promise.all([
|
|
2002
|
+
fetch('/api/vfx' + (activeVfxCategory !== 'All' ? `?category=${encodeURIComponent(activeVfxCategory)}` : '')),
|
|
2003
|
+
fetch('/api/vfx/categories')
|
|
2004
|
+
]);
|
|
2005
|
+
if (!vfxRes.ok || !catRes.ok) return;
|
|
2006
|
+
|
|
2007
|
+
const vfxData = await vfxRes.json();
|
|
2008
|
+
const catData = await catRes.json();
|
|
2009
|
+
|
|
2010
|
+
currentVfxItems = vfxData.items || [];
|
|
2011
|
+
currentVfxCategories = catData.categories || [];
|
|
2012
|
+
|
|
2013
|
+
renderVfxCategories();
|
|
2014
|
+
renderVfxStats();
|
|
2015
|
+
renderVfxCards();
|
|
2016
|
+
|
|
2017
|
+
// Hitung total di sidebar
|
|
2018
|
+
let totalCount = 0;
|
|
2019
|
+
currentVfxCategories.forEach(c => totalCount += c.count);
|
|
2020
|
+
if (vfxSidebarCount) vfxSidebarCount.textContent = totalCount;
|
|
2021
|
+
} catch (err) {
|
|
2022
|
+
console.error('Gagal memuat VFX Database:', err);
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
function renderVfxCategories() {
|
|
2027
|
+
if (!vfxCategoryTabs) return;
|
|
2028
|
+
let totalCount = 0;
|
|
2029
|
+
currentVfxCategories.forEach(c => totalCount += c.count);
|
|
2030
|
+
|
|
2031
|
+
let html = `
|
|
2032
|
+
<button class="vfx-cat-pill ${activeVfxCategory === 'All' ? 'active' : ''}" data-cat="All">
|
|
2033
|
+
Semua <span class="vfx-cat-count">${totalCount}</span>
|
|
2034
|
+
</button>
|
|
2035
|
+
`;
|
|
2036
|
+
|
|
2037
|
+
currentVfxCategories.forEach(cat => {
|
|
2038
|
+
html += `
|
|
2039
|
+
<button class="vfx-cat-pill ${activeVfxCategory === cat.name ? 'active' : ''}" data-cat="${escapeHtml(cat.name)}">
|
|
2040
|
+
${escapeHtml(cat.name)} <span class="vfx-cat-count">${cat.count}</span>
|
|
2041
|
+
</button>
|
|
2042
|
+
`;
|
|
2043
|
+
});
|
|
2044
|
+
|
|
2045
|
+
vfxCategoryTabs.innerHTML = html;
|
|
2046
|
+
|
|
2047
|
+
vfxCategoryTabs.querySelectorAll('.vfx-cat-pill').forEach(btn => {
|
|
2048
|
+
btn.addEventListener('click', () => {
|
|
2049
|
+
activeVfxCategory = btn.dataset.cat;
|
|
2050
|
+
vfxCategoryTabs.querySelectorAll('.vfx-cat-pill').forEach(b => b.classList.remove('active'));
|
|
2051
|
+
btn.classList.add('active');
|
|
2052
|
+
loadVfxDatabase();
|
|
2053
|
+
});
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
function renderVfxStats() {
|
|
2058
|
+
let totalCount = 0;
|
|
2059
|
+
let totalEmitters = 0;
|
|
2060
|
+
let totalBeams = 0;
|
|
2061
|
+
|
|
2062
|
+
currentVfxItems.forEach(item => {
|
|
2063
|
+
totalCount++;
|
|
2064
|
+
if (item.stats) {
|
|
2065
|
+
totalEmitters += (item.stats.emitters || 0);
|
|
2066
|
+
totalBeams += ((item.stats.beams || 0) + (item.stats.trails || 0));
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
|
|
2070
|
+
if (vfxStatTotal) vfxStatTotal.textContent = totalCount;
|
|
2071
|
+
if (vfxStatCategories) vfxStatCategories.textContent = currentVfxCategories.length;
|
|
2072
|
+
if (vfxStatEmitters) vfxStatEmitters.textContent = totalEmitters;
|
|
2073
|
+
if (vfxStatBeams) vfxStatBeams.textContent = totalBeams;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
function renderVfxCards() {
|
|
2077
|
+
if (!vfxCardsGrid) return;
|
|
2078
|
+
if (currentVfxItems.length === 0) {
|
|
2079
|
+
vfxCardsGrid.innerHTML = `
|
|
2080
|
+
<div style="grid-column: 1 / -1; padding: 48px; text-align: center; color: var(--text-dim); background: var(--surface-card); border: 1px dashed var(--border); border-radius: 12px;">
|
|
2081
|
+
<div style="font-size: 32px; margin-bottom: 8px;">✨</div>
|
|
2082
|
+
<div style="font-size: 15px; font-weight: 600; color: var(--text);">Belum ada efek visual di kategori ini</div>
|
|
2083
|
+
<div style="font-size: 13px; margin-top: 4px;">Klik tombol "Scan dari Studio" di atas untuk memindai partikel dari proyek aktif Anda.</div>
|
|
2084
|
+
</div>
|
|
2085
|
+
`;
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
const filterQuery = (vfxSearchInput ? vfxSearchInput.value.toLowerCase().trim() : '');
|
|
2090
|
+
const items = filterQuery
|
|
2091
|
+
? currentVfxItems.filter(i =>
|
|
2092
|
+
i.name.toLowerCase().includes(filterQuery) ||
|
|
2093
|
+
i.category.toLowerCase().includes(filterQuery) ||
|
|
2094
|
+
(i.description && i.description.toLowerCase().includes(filterQuery)) ||
|
|
2095
|
+
(i.tags && i.tags.some(t => t.toLowerCase().includes(filterQuery))))
|
|
2096
|
+
: currentVfxItems;
|
|
2097
|
+
|
|
2098
|
+
vfxCardsGrid.innerHTML = items.map(item => {
|
|
2099
|
+
const dateStr = item.createdAt ? new Date(item.createdAt).toLocaleDateString('id-ID', { day:'numeric', month:'short', year:'numeric' }) : 'NVStudio';
|
|
2100
|
+
const stats = item.stats || { emitters: 0, beams: 0, trails: 0, lights: 0 };
|
|
2101
|
+
const compPills = [];
|
|
2102
|
+
if (stats.emitters > 0) compPills.push(`${stats.emitters} Emitters`);
|
|
2103
|
+
if (stats.beams > 0) compPills.push(`${stats.beams} Beams`);
|
|
2104
|
+
if (stats.trails > 0) compPills.push(`${stats.trails} Trails`);
|
|
2105
|
+
if (stats.lights > 0) compPills.push(`${stats.lights} Lights`);
|
|
2106
|
+
if (compPills.length === 0) compPills.push(`${item.nodeCount || 1} Nodes`);
|
|
2107
|
+
|
|
2108
|
+
const tagChips = (item.tags || []).slice(0, 4).map(t => `<span class="vfx-tag-chip">#${escapeHtml(t)}</span>`).join('');
|
|
2109
|
+
|
|
2110
|
+
return `
|
|
2111
|
+
<div class="vfx-card">
|
|
2112
|
+
<div class="vfx-card-top">
|
|
2113
|
+
<div class="vfx-card-title-group">
|
|
2114
|
+
<h4>${escapeHtml(item.name)}</h4>
|
|
2115
|
+
<div class="vfx-card-date">${escapeHtml(dateStr)} • ${escapeHtml(item.author || 'NVStudio')}</div>
|
|
2116
|
+
</div>
|
|
2117
|
+
<span class="vfx-badge-cat vfx-badge-${escapeHtml(item.category || 'Ambient')}">${escapeHtml(item.category || 'Ambient')}</span>
|
|
2118
|
+
</div>
|
|
2119
|
+
|
|
2120
|
+
<div class="vfx-card-desc">${escapeHtml(item.description || 'Efek visual siap pakai untuk proyek Roblox.')}</div>
|
|
2121
|
+
|
|
2122
|
+
<div class="vfx-composition-badges">
|
|
2123
|
+
${compPills.map(p => `<span class="vfx-comp-pill">${escapeHtml(p)}</span>`).join('')}
|
|
2124
|
+
</div>
|
|
2125
|
+
|
|
2126
|
+
${tagChips ? `<div class="vfx-card-tags">${tagChips}</div>` : ''}
|
|
2127
|
+
|
|
2128
|
+
<div class="vfx-card-footer">
|
|
2129
|
+
<button class="vfx-btn-apply" onclick="openApplyModal('${encodeURIComponent(item.id)}')">
|
|
2130
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
|
2131
|
+
Apply ke Studio
|
|
2132
|
+
</button>
|
|
2133
|
+
<button class="vfx-btn-json" title="Inspeksi Data JSON" onclick="openJsonModal('${encodeURIComponent(item.id)}')">
|
|
2134
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
|
2135
|
+
</button>
|
|
2136
|
+
<button class="vfx-btn-del" title="Hapus dari Database" onclick="deleteVfxItem('${encodeURIComponent(item.id)}')">
|
|
2137
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
|
2138
|
+
</button>
|
|
2139
|
+
</div>
|
|
2140
|
+
</div>
|
|
2141
|
+
`;
|
|
2142
|
+
}).join('');
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
// Search debounce
|
|
2146
|
+
if (vfxSearchInput) {
|
|
2147
|
+
vfxSearchInput.addEventListener('input', () => {
|
|
2148
|
+
clearTimeout(vfxSearchTimeout);
|
|
2149
|
+
vfxSearchTimeout = setTimeout(() => {
|
|
2150
|
+
renderVfxCards();
|
|
2151
|
+
}, 180);
|
|
2152
|
+
});
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// Modal triggers & handlers
|
|
2156
|
+
const btnOpenScanModal = document.getElementById('btn-open-scan-modal');
|
|
2157
|
+
if (btnOpenScanModal) {
|
|
2158
|
+
btnOpenScanModal.addEventListener('click', () => {
|
|
2159
|
+
modalScanVfx.classList.add('open');
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
const btnRefreshVfx = document.getElementById('btn-refresh-vfx');
|
|
2164
|
+
if (btnRefreshVfx) {
|
|
2165
|
+
btnRefreshVfx.addEventListener('click', () => {
|
|
2166
|
+
loadVfxDatabase();
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
// Submit Scan form
|
|
2171
|
+
const btnSubmitScan = document.getElementById('btn-submit-scan');
|
|
2172
|
+
if (btnSubmitScan) {
|
|
2173
|
+
btnSubmitScan.addEventListener('click', async () => {
|
|
2174
|
+
const target = document.getElementById('scan-input-target').value.trim();
|
|
2175
|
+
const name = document.getElementById('scan-input-name').value.trim();
|
|
2176
|
+
const category = document.getElementById('scan-select-category').value;
|
|
2177
|
+
const tags = document.getElementById('scan-input-tags').value.split(',').map(t => t.trim()).filter(Boolean);
|
|
2178
|
+
|
|
2179
|
+
if (!target) {
|
|
2180
|
+
alert('Harap isi path target di Studio');
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
btnSubmitScan.disabled = true;
|
|
2185
|
+
btnSubmitScan.textContent = 'Memindai...';
|
|
2186
|
+
try {
|
|
2187
|
+
const res = await fetch('/api/vfx/scan', {
|
|
2188
|
+
method: 'POST',
|
|
2189
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2190
|
+
body: JSON.stringify({ target, name, category, tags })
|
|
2191
|
+
});
|
|
2192
|
+
const data = await res.json();
|
|
2193
|
+
if (res.ok && data.success) {
|
|
2194
|
+
closeVfxModals();
|
|
2195
|
+
loadVfxDatabase();
|
|
2196
|
+
alert(`✅ Sukses memindai VFX "${data.item.name}" (${data.item.nodeCount} node)!`);
|
|
2197
|
+
} else {
|
|
2198
|
+
alert('Gagal scan: ' + (data.error || 'Unknown error'));
|
|
2199
|
+
}
|
|
2200
|
+
} catch (err) {
|
|
2201
|
+
alert('Error: ' + err.message);
|
|
2202
|
+
} finally {
|
|
2203
|
+
btnSubmitScan.disabled = false;
|
|
2204
|
+
btnSubmitScan.textContent = 'Mulai Pindai';
|
|
2205
|
+
}
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
// Submit Apply form
|
|
2210
|
+
const btnSubmitApply = document.getElementById('btn-submit-apply');
|
|
2211
|
+
if (btnSubmitApply) {
|
|
2212
|
+
btnSubmitApply.addEventListener('click', async () => {
|
|
2213
|
+
const id = document.getElementById('apply-vfx-id').value;
|
|
2214
|
+
const target = document.getElementById('apply-input-target').value.trim();
|
|
2215
|
+
const scale = parseFloat(document.getElementById('apply-input-scale').value) || 1.0;
|
|
2216
|
+
const enabledVal = document.getElementById('apply-select-enabled').value;
|
|
2217
|
+
const enabled = enabledVal === 'default' ? undefined : (enabledVal === 'true');
|
|
2218
|
+
|
|
2219
|
+
if (!target) {
|
|
2220
|
+
alert('Harap isi target di Studio');
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
btnSubmitApply.disabled = true;
|
|
2225
|
+
btnSubmitApply.textContent = 'Menerapkan...';
|
|
2226
|
+
try {
|
|
2227
|
+
const res = await fetch('/api/vfx/apply', {
|
|
2228
|
+
method: 'POST',
|
|
2229
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2230
|
+
body: JSON.stringify({ id, target, scale, enabled })
|
|
2231
|
+
});
|
|
2232
|
+
const data = await res.json();
|
|
2233
|
+
if (res.ok && data.success) {
|
|
2234
|
+
closeVfxModals();
|
|
2235
|
+
alert(`✅ VFX berhasil diterapkan ke "${target}" di Studio!`);
|
|
2236
|
+
} else {
|
|
2237
|
+
alert('Gagal apply: ' + (data.error || 'Unknown error'));
|
|
2238
|
+
}
|
|
2239
|
+
} catch (err) {
|
|
2240
|
+
alert('Error: ' + err.message);
|
|
2241
|
+
} finally {
|
|
2242
|
+
btnSubmitApply.disabled = false;
|
|
2243
|
+
btnSubmitApply.textContent = 'Terapkan Sekarang';
|
|
2244
|
+
}
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
// Copy JSON button
|
|
2249
|
+
const btnCopyVfxJson = document.getElementById('btn-copy-vfx-json');
|
|
2250
|
+
if (btnCopyVfxJson) {
|
|
2251
|
+
btnCopyVfxJson.addEventListener('click', () => {
|
|
2252
|
+
const content = document.getElementById('json-modal-content').textContent;
|
|
2253
|
+
navigator.clipboard.writeText(content);
|
|
2254
|
+
btnCopyVfxJson.textContent = 'Tersalin ✓';
|
|
2255
|
+
setTimeout(() => { btnCopyVfxJson.textContent = 'Salin JSON'; }, 1500);
|
|
2256
|
+
});
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
// Muat VFX Database saat pertama kali dibuka
|
|
2260
|
+
loadVfxDatabase();
|
|
2261
|
+
|
|
1367
2262
|
/* ── Socket.IO Event Listeners ── */
|
|
1368
2263
|
socket.on('connect', () => {});
|
|
1369
2264
|
socket.on('disconnect', () => setStudioStatus(false));
|
|
@@ -1385,6 +2280,10 @@
|
|
|
1385
2280
|
updateStats(s);
|
|
1386
2281
|
});
|
|
1387
2282
|
|
|
2283
|
+
socket.on('vfx_updated', () => {
|
|
2284
|
+
loadVfxDatabase();
|
|
2285
|
+
});
|
|
2286
|
+
|
|
1388
2287
|
socket.on('studio-status', ({ connected, studioCount, connectedStudios, version }) => {
|
|
1389
2288
|
setStudioStatus(connected, studioCount);
|
|
1390
2289
|
if (connectedStudios) updateStudiosTable(connectedStudios);
|