@adiholick/nvstudio-mcp 2.2.2 → 2.3.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.
@@ -0,0 +1,419 @@
1
+ ---
2
+ name: nvstudio-gui-standards
3
+ description: Panduan standar arsitektur dan taste desain StarterGui Roblox (Struktur Instance, Smart Device Detection NVSelfTitle/NVDance, Layout Hierarchy, dan NV Dark Modern Aesthetics).
4
+ ---
5
+
6
+ # NVStudio Roblox GUI Design Standards & Taste Guide
7
+
8
+ Gunakan skill ini sebagai acuan mutlak setiap kali membuat atau memodifikasi GUI di Roblox Studio (`StarterGui`). Semua GUI dirancang mengikuti filosofi arsitektur **NV-Series** (`NVMembership`, `NVMusic`, `NVSelfTitle`, `NVServer`, `NVDance`).
9
+
10
+ ---
11
+
12
+ ## 1. Aturan Fundamental Arsitektur GUI
13
+
14
+ 1. **Roblox Instance Penuh (Bukan Pure Script):**
15
+ - GUI dibuat sebagai hierarki **Instance nyata di Roblox Studio** (menggunakan MCP `create_instance` / `set_properties` atau builder helper Luau).
16
+ - DILARANG membuat GUI murni on-the-fly dari script kosong dengan ratusan baris `Instance.new`.
17
+ - Script Luau (`LocalScript` / `ModuleScript`) hanya bertindak sebagai **UI Controller** yang mengontrol state, event click/input, tweening, dan integrasi server.
18
+
19
+ 2. **Hierarki ScreenGui & MainFrame:**
20
+ - Setiap GUI berada di bawah `ScreenGui` dengan properti:
21
+ - `ResetOnSpawn = false`
22
+ - `ZIndexBehavior = Enum.ZIndexBehavior.Sibling`
23
+ - `IgnoreGuiInset = false`
24
+ - Child utama dari `ScreenGui` WAJIB berupa Frame bernama **`MainFrame`** (atau `ContainerFrame` yang membungkus `MainFrame`).
25
+ - `MainFrame.AnchorPoint` default: `Vector2.new(0.5, 0.5)` dengan posisi tengah `UDim2.new(0.5, 0, 0.5, 0)`.
26
+
27
+ 3. **Aturan Wajib Layout Frame:**
28
+ - **Setiap Frame** WAJIB memiliki:
29
+ - `UIListLayout` (atau `UIGridLayout` jika grid konten) dengan `SortOrder = Enum.SortOrder.LayoutOrder`.
30
+ - `UIPadding` (memberikan padding konsisten agar elemen tidak menempel ke tepi).
31
+ - **Setiap Item di dalam Frame** WAJIB memiliki properti `LayoutOrder` eksplisit (0, 1, 2, 3...) agar susunan elemen teratur secara deterministik.
32
+
33
+ 4. **Aturan Wajib ScrollingFrame:**
34
+ - Setiap `ScrollingFrame` WAJIB memiliki Frame transparan pembatas bernama **`Spacer`** atau **`SPACER`**:
35
+ - `Name = "Spacer"`
36
+ - `BackgroundTransparency = 1`
37
+ - `BorderSizePixel = 0`
38
+ - `LayoutOrder = 99999` (atau `999999999`)
39
+ - Size: `UDim2.new(1, 0, 0.15, 0)` atau `UDim2.new(0, 0, 0, 20)` (menyesuaikan orientasi scroll vertical/horizontal).
40
+ - Ini memastikan elemen paling bawah/ujung tidak terpotong saat pemain melakukan scroll.
41
+
42
+ 5. **Pembangun Deklaratif Cepat (`batch_create_instances`):**
43
+ - Sangat disarankan membangun seluruh pohon GUI menggunakan tool `batch_create_instances` dengan mode `tree` bersarang.
44
+ - Contoh payload tree:
45
+ ```json
46
+ {
47
+ "target": "StarterGui.MyScreen",
48
+ "tree": {
49
+ "className": "Frame",
50
+ "name": "MainFrame",
51
+ "properties": {
52
+ "Size": [0.5, 0, 0.6, 0],
53
+ "Position": [0.5, 0, 0.5, 0],
54
+ "AnchorPoint": [0.5, 0.5],
55
+ "BackgroundColor3": [28, 28, 30]
56
+ },
57
+ "children": [
58
+ { "className": "UICorner", "properties": { "CornerRadius": [0, 10] } },
59
+ { "className": "UIStroke", "properties": { "Color": [63, 63, 63], "Thickness": 1 } },
60
+ { "className": "UIListLayout", "properties": { "SortOrder": "LayoutOrder" } },
61
+ { "className": "UIPadding", "properties": { "PaddingTop": [0, 10], "PaddingBottom": [0, 10] } }
62
+ ]
63
+ }
64
+ }
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 2. Smart Device Detection & Responsive Scaling System
70
+
71
+ Diadopsi langsung dari **`NVSelfTitle`** dan **`NVDance`**. Sistem ini membedakan ukuran perangkat secara akurat dan memisahkan skala PC, Mobile, Tablet, dan Console secara independen.
72
+
73
+ ### Modul Logika Deteksi Perangkat:
74
+ ```lua
75
+ local GuiService = game:GetService("GuiService")
76
+ local UserInputService = game:GetService("UserInputService")
77
+ local Workspace = game:GetService("Workspace")
78
+
79
+ -- [KONFIGURASI SKALA INDEPENDEN PER PERANGKAT]
80
+ local PC_Scale = 1.0 -- Skala normal PC / Laptop
81
+ local PC_Portrait_Scale = 1.2 -- Jendela PC orientasi portrait
82
+ local Mobile_Landscape_Scale = 1.45 -- Smartphone landscape
83
+ local Mobile_Portrait_Scale = 1.7 -- Smartphone portrait
84
+ local Tablet_Landscape_Scale = 1.25 -- Tablet / iPad landscape
85
+ local Tablet_Portrait_Scale = 1.45 -- Tablet / iPad portrait
86
+ local Console_Scale = 1.15 -- PlayStation / Xbox / TV
87
+ local GlobalScaleMultiplier = 1.0
88
+
89
+ -- Fungsi Cerdas Deteksi Perangkat (NVSelfTitle / NVDance Standard)
90
+ local function GetDeviceType(vw: number, vh: number, mainFrame: GuiObject): string
91
+ -- 1. Opsi override untuk testing di Studio lewat Attribute "ForceDevice" pada MainFrame:
92
+ local forceDevice = mainFrame:GetAttribute("ForceDevice")
93
+ if forceDevice and (forceDevice == "Phone" or forceDevice == "Tablet" or forceDevice == "PC" or forceDevice == "Console") then
94
+ return forceDevice :: string
95
+ end
96
+
97
+ -- 2. Console (PlayStation / Xbox / Smart TV)
98
+ if GuiService:IsTenFootInterface() then
99
+ return "Console"
100
+ end
101
+
102
+ -- 3. Touch Device (Smartphone vs Tablet)
103
+ -- Touchscreen aktif dan keyboard fisik tidak aktif
104
+ if UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled then
105
+ local smallerDimension = math.min(vw, vh)
106
+ -- Dimensi logika layar smartphone di Roblox umumnya < 550 pixel, Tablet >= 550 pixel
107
+ if smallerDimension > 0 and smallerDimension < 550 then
108
+ return "Phone"
109
+ else
110
+ return "Tablet"
111
+ end
112
+ end
113
+
114
+ -- 4. Default: Desktop / PC / Laptop
115
+ return "PC"
116
+ end
117
+ ```
118
+
119
+ ### Handler Update Skala & Event Listener:
120
+ ```lua
121
+ local function UpdateMainFrameScale(mainFrame: Frame, defaultSize: UDim2, isOpen: boolean)
122
+ local camera = Workspace.CurrentCamera
123
+ if not camera then return end
124
+
125
+ local vw, vh = camera.ViewportSize.X, camera.ViewportSize.Y
126
+ local isPortrait = vh > vw
127
+ local deviceType = GetDeviceType(vw, vh, mainFrame)
128
+
129
+ local baseScale = 1.0
130
+ if deviceType == "Phone" then
131
+ baseScale = isPortrait and Mobile_Portrait_Scale or Mobile_Landscape_Scale
132
+ elseif deviceType == "Tablet" then
133
+ baseScale = isPortrait and Tablet_Portrait_Scale or Tablet_Landscape_Scale
134
+ elseif deviceType == "Console" then
135
+ baseScale = Console_Scale
136
+ else
137
+ baseScale = isPortrait and PC_Portrait_Scale or PC_Scale
138
+ end
139
+
140
+ local totalScale = GlobalScaleMultiplier * baseScale
141
+
142
+ mainFrame.Size = UDim2.new(
143
+ defaultSize.X.Scale * totalScale,
144
+ defaultSize.X.Offset * totalScale,
145
+ defaultSize.Y.Scale * totalScale,
146
+ defaultSize.Y.Offset * totalScale
147
+ )
148
+
149
+ if isOpen then
150
+ mainFrame.Position = UDim2.new(0.5, 0, 0.5, 0)
151
+ end
152
+ end
153
+ ```
154
+
155
+ ### Studio Attributes Testing Support:
156
+ Pasang Attribute pada `MainFrame`:
157
+ - `ForceDevice` (`String`: `"Phone"`, `"Tablet"`, `"PC"`, `"Console"`)
158
+ - `PCScale` (`Number`)
159
+ - `MobileScale` (`Number`)
160
+
161
+ ---
162
+
163
+ ## 3. NV Dark Modern Taste & Design Tokens
164
+
165
+ Semua GUI NV-Series menggunakan tema gelap elegan (*business-grade dark mode*) dengan kontras tinggi dan sentuhan rounded halus.
166
+
167
+ | Komponen | Token Warna / Properti | Nilai RGB / Hex | Kegunaan |
168
+ | :--- | :--- | :--- | :--- |
169
+ | **MainFrame Background** | `BackgroundColor3` | `Color3.fromRGB(28, 28, 30)` (`#1C1C1E`) | Latar belakang frame utama jendela |
170
+ | **Card / Item Background** | `BackgroundColor3` | `Color3.fromRGB(25, 25, 25)` (`#191919`) | Latar belakang list item, segment container |
171
+ | **Sub-Frame / Input** | `BackgroundColor3` | `Color3.fromRGB(13, 13, 13)` (`#0D0D0D`) | Textbox search, area preview, inner box |
172
+ | **Border / UIStroke** | `Color`, `Thickness = 1` | `Color3.fromRGB(63, 63, 63)` (`#3F3F3F`) | Garis tepi halus (`ApplyStrokeMode.Border`) |
173
+ | **Active Accent (Cyan)** | `Color3` | `Color3.fromRGB(0, 170, 255)` / `fromRGB(0, 255, 255)` | Tab aktif, border seleksi (`SelectStroke`) |
174
+ | **Text Primary** | `TextColor3` | `Color3.fromRGB(255, 255, 255)` (`#FFFFFF`) | Judul, label penting, nama item |
175
+ | **Text Secondary** | `TextColor3` | `Color3.fromRGB(163, 162, 165)` (`#A3A2A5`) | Deskripsi, subtitle, teks tidak aktif |
176
+
177
+ ### Corner Radius (`UICorner`):
178
+ - `MainFrame`: `UDim.new(0, 10)` atau `UDim.new(0.03, 0)`
179
+ - `Card / Container Frame`: `UDim.new(0, 8)` atau `UDim.new(0, 10)`
180
+ - `Buttons / Pills / Tags`: `UDim.new(0.2, 0)` atau `UDim.new(0.5, 0)`
181
+
182
+ ### Tipografi (`Font`):
183
+ - **Judul & Header:** `Enum.Font.GothamBold`
184
+ - **Teks Tubuh & List:** `Enum.Font.Gotham` atau `Enum.Font.GothamMedium`
185
+ - **Aksen / Badge:** `Enum.Font.FredokaOne` atau `Enum.Font.SourceSansBold`
186
+
187
+ ---
188
+
189
+ ## 4. UI Controller Script Template (LocalScript)
190
+
191
+ Setiap ScreenGui memiliki Controller (`LocalScript`) dengan struktur standar berikut:
192
+
193
+ ```lua
194
+ --!strict
195
+ local Players = game:GetService("Players")
196
+ local ReplicatedStorage = game:GetService("ReplicatedStorage")
197
+ local TweenService = game:GetService("TweenService")
198
+ local UserInputService = game:GetService("UserInputService")
199
+ local GuiService = game:GetService("GuiService")
200
+ local Workspace = game:GetService("Workspace")
201
+
202
+ local Modules = ReplicatedStorage:WaitForChild("Modules")
203
+ local Maid = require(Modules:WaitForChild("Maid"))
204
+ local Icon = require(ReplicatedStorage:WaitForChild("Icon")) -- TopbarPlus
205
+
206
+ local GUI = script.Parent
207
+ local MainFrame = GUI:WaitForChild("MainFrame") :: Frame
208
+ local ScrollingFrame = MainFrame:FindFirstChildWhichIsA("ScrollingFrame", true)
209
+
210
+ local Controller = {
211
+ _maid = Maid.new(),
212
+ IsOpen = false,
213
+ DefaultSize = MainFrame.Size,
214
+ }
215
+
216
+ -- [1] ANIMASI TOGGLE WINDOW (Pop-In & Slide)
217
+ function Controller:Toggle(state: boolean)
218
+ self.IsOpen = state
219
+ local tweenInfo = TweenInfo.new(0.4, Enum.EasingStyle.Quint, Enum.EasingDirection.Out)
220
+ local targetPos = state and UDim2.new(0.5, 0, 0.5, 0) or UDim2.new(0.5, 0, -0.6, 0)
221
+
222
+ if state then
223
+ MainFrame.Visible = true
224
+ UpdateMainFrameScale(MainFrame, self.DefaultSize, true)
225
+ end
226
+
227
+ local tween = TweenService:Create(MainFrame, tweenInfo, { Position = targetPos })
228
+ tween:Play()
229
+
230
+ if not state then
231
+ tween.Completed:Connect(function()
232
+ if not self.IsOpen then
233
+ MainFrame.Visible = false
234
+ end
235
+ end)
236
+ end
237
+ end
238
+
239
+ -- [2] SELEKSI TOMBOL INTERAKTIF (Pola SelectStroke)
240
+ function Controller:HighlightButton(parentContainer: Instance, activeBtn: Instance?)
241
+ for _, child in ipairs(parentContainer:GetChildren()) do
242
+ if child:IsA("TextButton") or child:IsA("ImageButton") then
243
+ local stroke = child:FindFirstChild("SelectStroke") :: UIStroke?
244
+ if not stroke then
245
+ stroke = Instance.new("UIStroke")
246
+ stroke.Name = "SelectStroke"
247
+ stroke.Thickness = 1
248
+ stroke.ApplyStrokeMode = Enum.ApplyStrokeMode.Border
249
+ stroke.Parent = child
250
+ end
251
+ if child == activeBtn then
252
+ stroke.Color = Color3.fromRGB(0, 255, 255)
253
+ else
254
+ stroke.Color = Color3.fromRGB(60, 60, 65)
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+ -- [3] SETUP TOPBARPLUS ICON
261
+ local topbarIcon = Icon.new()
262
+ topbarIcon:setLabel(GUI.Name)
263
+ topbarIcon.toggled:Connect(function(isSelected: boolean)
264
+ Controller:Toggle(isSelected)
265
+ end)
266
+
267
+ -- [4] HOOK CAMERA & ATTRIBUTES
268
+ local function hookCamera()
269
+ local cam = Workspace.CurrentCamera
270
+ if cam then
271
+ cam:GetPropertyChangedSignal("ViewportSize"):Connect(function()
272
+ UpdateMainFrameScale(MainFrame, Controller.DefaultSize, Controller.IsOpen)
273
+ end)
274
+ UpdateMainFrameScale(MainFrame, Controller.DefaultSize, Controller.IsOpen)
275
+ end
276
+ end
277
+ hookCamera()
278
+ Workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(hookCamera)
279
+ ```
280
+
281
+ ---
282
+
283
+ ## 5. Komponen Interaktif Standar NV-Series
284
+
285
+ ### A. Vertical Slider (`VerticalSlider`)
286
+ Diadopsi dari `NVMusic` (Volume Fader Master, Deck A/B Volume, Speed Control):
287
+ - **Struktur Instance:**
288
+ - `SliderFrame` (Frame Track Vertikal: Width `0.12` - `0.2`, Height `0.85`, background `#101010`, `UICorner`, `UIStroke`, `UIPadding`).
289
+ - `Button` (Draggable Knob: AnchorPoint `0.5, 0.5`, `UICorner`).
290
+ - `ValueTextTarget` (TextLabel indikator nilai).
291
+ - **Logika Luau:**
292
+ ```lua
293
+ local function ProcessVerticalInput(inputY, sliderFrame, minVal, maxVal, step)
294
+ local frameY = sliderFrame.AbsolutePosition.Y
295
+ local frameH = sliderFrame.AbsoluteSize.Y
296
+ if frameH <= 0 then return minVal end
297
+
298
+ local relativeY = math.clamp(inputY - frameY, 0, frameH)
299
+ local ratio = 1 - (relativeY / frameH) -- Dari bawah ke atas
300
+ local rawVal = minVal + ratio * (maxVal - minVal)
301
+ local steps = math.floor(rawVal / step + 0.5)
302
+ return math.clamp(steps * step, minVal, maxVal), ratio
303
+ end
304
+
305
+ local function UpdateVerticalVisual(button, ratio, textLabel, val)
306
+ button.Position = UDim2.new(
307
+ button.Position.X.Scale,
308
+ button.Position.X.Offset,
309
+ (1 - ratio),
310
+ -button.Size.Y.Offset / 2
311
+ )
312
+ if textLabel then
313
+ textLabel.Text = tostring(math.floor(val * 100) / 100)
314
+ end
315
+ end
316
+ ```
317
+
318
+ ---
319
+
320
+ ### B. Horizontal Slider (`HorizontalSlider`)
321
+ Diadopsi dari `NVMusic` (Volume Horizontal, Master Crossfade) dan `NVDance` (Emote Speed Slider):
322
+ - **Struktur Instance:**
323
+ - `SliderFrame` (Track Horizontal: Height `0.2` - `0.25`, Width `1.0`, background `#101010`, `UICorner`, `UIStroke`).
324
+ - `Fill` (Frame Bar kemajuan aksen Cyan/Putih, Size `UDim2.new(ratio, 0, 1, 0)`).
325
+ - `Button` / `Knob` (Frame/Button bulat, Position `UDim2.new(ratio, 0, 0.5, 0)`).
326
+ - `Trigger` (TextButton transparan pembungkus input sentuh `Size = UDim2.new(1, 0, 1, 0)`).
327
+ - **Logika Luau:**
328
+ ```lua
329
+ local function ProcessHorizontalInput(inputX, barFrame, minVal, maxVal, step)
330
+ local frameX = barFrame.AbsolutePosition.X
331
+ local frameW = barFrame.AbsoluteSize.X
332
+ if frameW <= 0 then return minVal end
333
+
334
+ local relX = math.clamp((inputX - frameX) / frameW, 0, 1)
335
+ local rawVal = minVal + relX * (maxVal - minVal)
336
+ local steps = math.floor(rawVal / step + 0.5)
337
+ return math.clamp(steps * step, minVal, maxVal), relX
338
+ end
339
+
340
+ local function UpdateHorizontalVisual(fillFrame, knobFrame, ratio, valueLabel, val)
341
+ if fillFrame then fillFrame.Size = UDim2.new(ratio, 0, 1, 0) end
342
+ if knobFrame then knobFrame.Position = UDim2.new(ratio, -knobFrame.Size.X.Offset / 2, 0.5, 0) end
343
+ if valueLabel then valueLabel.Text = string.format("%.2f", val) end
344
+ end
345
+ ```
346
+
347
+ ---
348
+
349
+ ### C. Switch Button / Toggle (`ToggleSwitch`)
350
+ Diadopsi dari `NVMusic.GuiController.ToggleSwitch` (DJ Switch Controller):
351
+ - **Struktur Instance:**
352
+ - `SwitchContainer` (Frame Pill / Kapsul: `UICorner` CornerRadius `UDim.new(1, 0)`, background `#141414`).
353
+ - `SwitchFrame` (Indicator bulat / knob kecil di dalam container, `UICorner` `UDim.new(1, 0)`).
354
+ - `SwitchButton` (TextButton transparan `Size = UDim2.new(1, 0, 1, 0)`, `BackgroundTransparency = 1`).
355
+ - **Logika & Animasi Tween Luau:**
356
+ ```lua
357
+ local ThemeColors = {
358
+ ActiveItem = Color3.fromRGB(0, 170, 255), -- Cyan saat ON
359
+ Background = Color3.fromRGB(20, 20, 20), -- Gelap saat OFF
360
+ White = Color3.fromRGB(255, 255, 255),
361
+ SubText = Color3.fromRGB(150, 150, 150)
362
+ }
363
+
364
+ local function SetToggleState(isOn: boolean, container: Frame, indicator: Frame, animate: boolean)
365
+ local bgColor = isOn and ThemeColors.ActiveItem or ThemeColors.Background
366
+ local offset = indicator and indicator.Size.X.Offset or 14
367
+ local indicatorPos = isOn and UDim2.new(1, -(offset + 2), 0.5, 0) or UDim2.new(0, 2, 0.5, 0)
368
+ local indicatorColor = isOn and ThemeColors.White or ThemeColors.SubText
369
+
370
+ if animate then
371
+ TweenService:Create(container, TweenInfo.new(0.2), { BackgroundColor3 = bgColor }):Play()
372
+ TweenService:Create(indicator, TweenInfo.new(0.2), { Position = indicatorPos, BackgroundColor3 = indicatorColor }):Play()
373
+ else
374
+ container.BackgroundColor3 = bgColor
375
+ indicator.Position = indicatorPos
376
+ indicator.BackgroundColor3 = indicatorColor
377
+ end
378
+ end
379
+ ```
380
+
381
+ ---
382
+
383
+ ### D. Segment Frame (Tab Bar Segment)
384
+ Diadopsi dari `NVMembership`, `NVServer`, `NVMusic`, dan `NVDance`:
385
+ - **Struktur Instance:**
386
+ - `SegmentFrame` (Container Tab Horizontal: `BackgroundColor3 = Color3.fromRGB(25, 25, 25)`, `UICorner` kapsul `UDim.new(1, 0)` atau `0.5`, `UIPadding`, `UIStroke`).
387
+ - `UIListLayout` (FillDirection `Horizontal`, HorizontalAlignment `Center`, SortOrder `LayoutOrder`).
388
+ - Tab Buttons (`ServerListButton`, `PlayerListButton`, `ManageButton`, dll) dengan `UICorner` rounded.
389
+ - **Logika Luau:**
390
+ ```lua
391
+ local function SetActiveTab(tabButtons: { [string]: TextButton }, activeTabName: string, contentFrames: { [string]: GuiObject })
392
+ for name, btn in pairs(tabButtons) do
393
+ if name == activeTabName then
394
+ btn.BackgroundColor3 = Color3.fromRGB(55, 55, 60) -- Active Tab
395
+ else
396
+ btn.BackgroundColor3 = Color3.fromRGB(25, 25, 25) -- Inactive Tab
397
+ end
398
+ end
399
+
400
+ for name, frame in pairs(contentFrames) do
401
+ frame.Visible = (name == activeTabName)
402
+ end
403
+ end
404
+ ```
405
+
406
+ ---
407
+
408
+ ### E. Standar Tombol Aktif & Inaktif (Button State Styling)
409
+ 1. **Gaya Tab / Segment (`ThemeColors`):**
410
+ - **Active:** `Color3.fromRGB(55, 55, 60)` (abu-abu terang kontras).
411
+ - **Inactive:** `Color3.fromRGB(25, 25, 25)` (abu-abu gelap menyatu dengan background).
412
+ 2. **Gaya Seleksi Item (`SelectStroke`):**
413
+ - Menggunakan child `UIStroke` bernama `SelectStroke` pada tombol:
414
+ - **Active (Selected):** `Color3.fromRGB(0, 255, 255)` (Cyan menyala).
415
+ - **Inactive:** `Color3.fromRGB(60, 60, 65)` (Outline abu-abu gelap).
416
+ 3. **Gaya Disable / Cooldown (`AutoButtonColor`):**
417
+ - Saat dalam cooldown / limit habis: `AutoButtonColor = false`, teks diubah menjadi `"WAIT (Xs)"` atau `"OUT OF QUOTA"`, dan warna teks/latar diredupkan.
418
+
419
+
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: nvstudio-mcp-guide
3
+ description: Panduan umum untuk berinteraksi dengan Roblox Studio menggunakan MCP nvstudio-mcp.
4
+ ---
5
+ # nvstudio-mcp-guide
6
+
7
+ Gunakan skill ini sebagai referensi umum saat bekerja dengan Roblox Studio via `nvstudio-mcp`.
8
+
9
+ ## Dasar Pemanggilan
10
+ 1. Jika target path tidak diketahui, gunakan perintah eksplorasi seperti `get_children` atau `search_instance` (khusus pencarian berdasarkan nama) terlebih dahulu.
11
+ 2. Tentukan target path string yang valid (misalnya `game.Workspace.Model.Part`). LLM/Agent tidak bisa berinteraksi menggunakan reference Luau langsung.
12
+ 3. Gunakan native semantic tools yang tersedia (`execute_luau`, `batch_create_instances`, `get_roblox_docs`, dll.) atau gunakan legacy dispatcher `call_mcp_tool` untuk kompatibilitas.
13
+
14
+ ## Batch Instance Builder (`batch_create_instances` & `batch_set_properties`)
15
+ Saat membuat banyak instance (terutama struktur GUI seperti `MainFrame`, `UICorner`, `UIStroke`, `UIListLayout`, `UIPadding`, `ScrollingFrame`, `Spacer`), **JANGAN** membuat instance satu per satu dengan `create_instance`. Gunakan `batch_create_instances`:
16
+ - **Mode `tree` (Rekomendasi Utama UI):**
17
+ Membangun seluruh hierarki bersarang dalam 1 roundtrip:
18
+ ```json
19
+ {
20
+ "target": "StarterGui.NVMenu",
21
+ "tree": {
22
+ "className": "Frame",
23
+ "name": "MainFrame",
24
+ "properties": {
25
+ "Size": [0.5, 0, 0.6, 0],
26
+ "Position": [0.5, 0, 0.5, 0],
27
+ "AnchorPoint": [0.5, 0.5],
28
+ "BackgroundColor3": [28, 28, 30]
29
+ },
30
+ "children": [
31
+ { "className": "UICorner", "name": "UICorner", "properties": { "CornerRadius": [0, 10] } },
32
+ { "className": "UIStroke", "name": "UIStroke", "properties": { "Color": [63, 63, 63], "Thickness": 1 } },
33
+ { "className": "UIListLayout", "name": "UIListLayout", "properties": { "SortOrder": "LayoutOrder" } },
34
+ { "className": "UIPadding", "name": "UIPadding", "properties": { "PaddingTop": [0, 10] } }
35
+ ]
36
+ }
37
+ }
38
+ ```
39
+ - **Mode `instances` (Flat Array):**
40
+ Membuat banyak objek di berbagai parent berbeda sekaligus:
41
+ ```json
42
+ {
43
+ "instances": [
44
+ { "className": "Part", "name": "LightA", "parent": "Workspace.Lights", "properties": { "Anchored": true } },
45
+ { "className": "Part", "name": "LightB", "parent": "Workspace.Lights", "properties": { "Anchored": true } }
46
+ ]
47
+ }
48
+ ```
49
+ - **Pembaruan Massal (`batch_set_properties`):**
50
+ Untuk mengubah properti banyak instance secara atomik dalam 1 pemanggilan:
51
+ ```json
52
+ {
53
+ "updates": [
54
+ { "target": "Workspace.PartA", "properties": { "Transparency": 0.5, "CanCollide": false } },
55
+ { "target": "Workspace.PartB", "properties": { "Color": [255, 0, 0] } }
56
+ ]
57
+ }
58
+ ```
59
+
60
+ ## Verifikasi Dokumentasi Engine Resmi (`get_roblox_docs`)
61
+ Sebelum menulis skrip Luau atau menggunakan API/Service yang belum pasti nama properti atau method-nya, **selalu panggil `get_roblox_docs`**:
62
+ - **Format Panggilan:**
63
+ - `query`: Nama class, service, datatype, enum, atau library (contoh: `"TweenService"`, `"CanvasGroup"`, `"UDim2"`, `"EasingStyle"`, `"task"`).
64
+ - `member` *(opsional)*: Untuk menghemat token, tentukan nama method atau properti spesifik (contoh: `query: "CanvasGroup", member: "GroupTransparency"`).
65
+ - **Keunggulan:** Tool ini membaca data resmi terverifikasi dari Creator Docs dengan caching lokal berkecepatan tinggi (~2 ms) untuk mencegah halusinasi kode Luau.
66
+
67
+ ## Modifikasi Skrip Cerdas (`patch_script_source`)
68
+ - Jika hanya ingin mengubah sebagian baris kode tanpa menimpa seluruh file skrip, gunakan `patch_script_source` dengan parameter `search` dan `replacement`.
69
+ - Untuk penggantian skrip utuh, gunakan `update_script_source` (otomatis dicek oleh AST Validator di Node.js untuk mencegah syntax error fatal).
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: nvstudio-roblox-sync-guide
3
+ description: Aturan anti-halusinasi untuk membaca, mengedit, dan me-rollback script Roblox.
4
+ ---
5
+ # nvstudio-roblox-sync-guide
6
+
7
+ Panduan sinkronisasi dan manajemen skrip (properti `.Source`) di Roblox Studio.
8
+
9
+ ## Aturan update_script_source
10
+ 1. **PENTING:** Anda WAJIB melakukan `get_script_source` terlebih dahulu sebelum mengedit script apa pun. Ini agar Anda memahami kode apa yang ada saat ini dan mencegah hilangnya kode krusial.
11
+ 2. Operasi ini menimpa KESELURUHAN kode di dalam skrip.
12
+ 3. Server jembatan memiliki "Luau AST Validator" internal. Jika script Anda mengandung syntax error fatal (seperti lupa `end`), file tidak akan diupdate dan Anda akan menerima pesan error.
13
+
14
+ ## Aturan rollback_script
15
+ 1. Anda dapat membatalkan perubahan terakhir pada script dengan memanggil `rollback_script`.
16
+ 2. Pastikan target path cocok persis dengan script yang baru saja Anda edit.
@@ -117,5 +117,60 @@ function copyToRobloxPlugins() {
117
117
  }
118
118
  catch { }
119
119
  }
120
+ // Auto-sync dist, package.json, and agent-plugin to ~/.nvstudio-mcp & Antigravity
121
+ syncRuntimeAndPlugins();
122
+ }
123
+ function syncRuntimeAndPlugins() {
124
+ const homeDir = os_1.default.homedir();
125
+ const nvRuntimeDir = path_1.default.join(homeDir, '.nvstudio-mcp');
126
+ const antigravityPluginDir = path_1.default.join(homeDir, '.gemini', 'config', 'plugins', 'nvstudio-mcp');
127
+ // Sync to ~/.nvstudio-mcp
128
+ if (fs_1.default.existsSync(nvRuntimeDir)) {
129
+ try {
130
+ const rootDir = process.cwd();
131
+ const distSrc = path_1.default.join(rootDir, 'dist');
132
+ const distDest = path_1.default.join(nvRuntimeDir, 'dist');
133
+ if (fs_1.default.existsSync(distSrc)) {
134
+ fs_1.default.cpSync(distSrc, distDest, { recursive: true, force: true });
135
+ }
136
+ const pkgSrc = path_1.default.join(rootDir, 'package.json');
137
+ const pkgDest = path_1.default.join(nvRuntimeDir, 'package.json');
138
+ if (fs_1.default.existsSync(pkgSrc)) {
139
+ fs_1.default.copyFileSync(pkgSrc, pkgDest);
140
+ }
141
+ const pluginSrc = path_1.default.join(rootDir, 'studio-plugin');
142
+ const pluginDest = path_1.default.join(nvRuntimeDir, 'studio-plugin');
143
+ if (fs_1.default.existsSync(pluginSrc)) {
144
+ fs_1.default.cpSync(pluginSrc, pluginDest, { recursive: true, force: true });
145
+ }
146
+ const srcSrc = path_1.default.join(rootDir, 'src');
147
+ const srcDest = path_1.default.join(nvRuntimeDir, 'src');
148
+ if (fs_1.default.existsSync(srcSrc)) {
149
+ fs_1.default.cpSync(srcSrc, srcDest, { recursive: true, force: true });
150
+ }
151
+ const publicSrc = path_1.default.join(rootDir, 'public');
152
+ const publicDest = path_1.default.join(nvRuntimeDir, 'public');
153
+ if (fs_1.default.existsSync(publicSrc)) {
154
+ fs_1.default.cpSync(publicSrc, publicDest, { recursive: true, force: true });
155
+ }
156
+ console.log(`[Build] ✅ Berhasil menyinkronkan runtime ${nvRuntimeDir} (dist, public, studio-plugin, src)`);
157
+ }
158
+ catch (err) {
159
+ console.warn(`[Build] Gagal menyinkronkan ke ~/.nvstudio-mcp: ${err.message}`);
160
+ }
161
+ }
162
+ // Sync to ~/.gemini/config/plugins/nvstudio-mcp
163
+ if (fs_1.default.existsSync(antigravityPluginDir)) {
164
+ try {
165
+ const agentPluginSrc = path_1.default.join(process.cwd(), 'agent-plugin');
166
+ if (fs_1.default.existsSync(agentPluginSrc)) {
167
+ fs_1.default.cpSync(agentPluginSrc, antigravityPluginDir, { recursive: true, force: true });
168
+ console.log(`[Build] ✅ Berhasil menyinkronkan agent-plugin ke ${antigravityPluginDir}`);
169
+ }
170
+ }
171
+ catch (err) {
172
+ console.warn(`[Build] Gagal menyinkronkan ke agent-plugin Antigravity: ${err.message}`);
173
+ }
174
+ }
120
175
  }
121
176
  buildRbxmx();