@el4cteo/rbx-studio-mcp 0.3.7 → 0.3.8
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/dist/tools/screenshot.js
CHANGED
|
@@ -103,8 +103,26 @@ export function registerScreenshotTools(context) {
|
|
|
103
103
|
const emulating = response.device
|
|
104
104
|
? ` Emulating ${response.device} — call \`device op="stop"\` for the normal viewport.`
|
|
105
105
|
: "";
|
|
106
|
+
/*
|
|
107
|
+
* A capture that came back entirely black is almost never the game.
|
|
108
|
+
*
|
|
109
|
+
* It is a valid PNG of nothing, so nothing about the reply says it
|
|
110
|
+
* failed, and the picture is the reply -- an agent looking at it reads a
|
|
111
|
+
* dark room and reasons on from there, which is worse than an error
|
|
112
|
+
* because it is confident. Studio produces this when the window is not
|
|
113
|
+
* rendering: minimised, fully covered, or on another virtual desktop. The
|
|
114
|
+
* possibility that the scene really is black is left open rather than
|
|
115
|
+
* ruled out, because it is, occasionally.
|
|
116
|
+
*/
|
|
117
|
+
const blank = response.black
|
|
118
|
+
? " WARNING: every pixel in this capture is black, which usually means Studio was not" +
|
|
119
|
+
" rendering when it was taken rather than that the scene is dark — the window being" +
|
|
120
|
+
" minimised, fully covered by another window, or on a virtual desktop that is not" +
|
|
121
|
+
" on screen will all do it. Ask the user to bring Studio to the front and take it" +
|
|
122
|
+
" again before drawing any conclusion from what is in this image."
|
|
123
|
+
: "";
|
|
106
124
|
return image(png, `Studio viewport (${response.context}), ${response.width}x${response.height}` +
|
|
107
|
-
` scaled from ${response.sourceWidth}x${response.sourceHeight}.${emulating}`);
|
|
125
|
+
` scaled from ${response.sourceWidth}x${response.sourceHeight}.${emulating}${blank}`);
|
|
108
126
|
});
|
|
109
127
|
}
|
|
110
128
|
//# sourceMappingURL=screenshot.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAmB,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,UAAU,EAAoB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/plugin/src/Config.luau
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
local Config = {}
|
|
11
11
|
|
|
12
|
-
Config.PLUGIN_VERSION = "0.3.
|
|
12
|
+
Config.PLUGIN_VERSION = "0.3.8"
|
|
13
13
|
|
|
14
14
|
-- Fingerprint of plugin/src, stamped in by scripts/build-plugin.mjs. The server
|
|
15
15
|
-- computes the same hash from its own copy of the sources and compares, so a
|
|
@@ -40,6 +40,37 @@ local COMPRESSION_LEVEL = 3
|
|
|
40
40
|
|
|
41
41
|
local Capture = {}
|
|
42
42
|
|
|
43
|
+
--[[
|
|
44
|
+
Channel value at or under which a byte counts as black.
|
|
45
|
+
|
|
46
|
+
Not zero. A capture that failed comes back at exactly zero, but so does a
|
|
47
|
+
frame that picked up a hair of dithering, and being off by one is not worth
|
|
48
|
+
an argument.
|
|
49
|
+
]]
|
|
50
|
+
local BLACK_LEVEL = 8
|
|
51
|
+
|
|
52
|
+
--[[
|
|
53
|
+
Whether every pixel in the image is black.
|
|
54
|
+
|
|
55
|
+
A black screenshot is the one failure this tool cannot report by failing: the
|
|
56
|
+
call succeeds, the PNG is valid, and the agent reads it as "the game is dark"
|
|
57
|
+
and reasons on from there. So it is measured and said out loud instead.
|
|
58
|
+
|
|
59
|
+
Written to leave on the first byte that is not black, which is what makes it
|
|
60
|
+
free. A real frame -- even a night scene, which still has a skybox gradient,
|
|
61
|
+
a GUI or the topbar somewhere -- exits within the first handful of pixels.
|
|
62
|
+
The whole buffer is only ever walked when the answer is yes, and at that
|
|
63
|
+
point the walk has earned itself.
|
|
64
|
+
]]
|
|
65
|
+
local function isBlack(rgb: buffer): boolean
|
|
66
|
+
for offset = 0, buffer.len(rgb) - 1 do
|
|
67
|
+
if buffer.readu8(rgb, offset) > BLACK_LEVEL then
|
|
68
|
+
return false
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
return true
|
|
72
|
+
end
|
|
73
|
+
|
|
43
74
|
-- Only a content id comes back over the remote, so this is the time to start a
|
|
44
75
|
-- LocalScript and take one shot, not to move any pixels.
|
|
45
76
|
local CLIENT_TIMEOUT = 25
|
|
@@ -74,6 +105,7 @@ local CLIENT_TIMEOUT = 25
|
|
|
74
105
|
]]
|
|
75
106
|
local CLIENT_SOURCE = [==[
|
|
76
107
|
local CaptureService = game:GetService("CaptureService")
|
|
108
|
+
local RunService = game:GetService("RunService")
|
|
77
109
|
|
|
78
110
|
local relay = script
|
|
79
111
|
local report = relay:WaitForChild("Report", 10)
|
|
@@ -81,6 +113,15 @@ if report == nil then
|
|
|
81
113
|
return
|
|
82
114
|
end
|
|
83
115
|
|
|
116
|
+
-- One rendered frame before the shot. The RemoteEvent is already a child of
|
|
117
|
+
-- this script when it lands, so `WaitForChild` returns at once and the capture
|
|
118
|
+
-- would otherwise happen on the frame the relay arrived -- which is the frame
|
|
119
|
+
-- least likely to have finished drawing. Roblox's own working repro for
|
|
120
|
+
-- CaptureService waits on PreRender first, and it costs a sixtieth of a second.
|
|
121
|
+
pcall(function()
|
|
122
|
+
RunService.PreRender:Wait()
|
|
123
|
+
end)
|
|
124
|
+
|
|
84
125
|
local contentId = nil
|
|
85
126
|
local okShot, shotErr = pcall(function()
|
|
86
127
|
CaptureService:CaptureScreenshot(function(id)
|
|
@@ -259,6 +300,7 @@ local function encode(contentId: string, width: number, context: string): { [str
|
|
|
259
300
|
end
|
|
260
301
|
|
|
261
302
|
local rgb, outWidth, outHeight = Png.downscaleToRgb(pixels, sourceWidth, sourceHeight, width)
|
|
303
|
+
local black = isBlack(rgb)
|
|
262
304
|
|
|
263
305
|
--[[
|
|
264
306
|
Raw pixels, compressed by the engine, assembled into a PNG by Node.
|
|
@@ -298,6 +340,7 @@ local function encode(contentId: string, width: number, context: string): { [str
|
|
|
298
340
|
rawBytes = buffer.len(rgb),
|
|
299
341
|
bytes = #(packed :: string),
|
|
300
342
|
context = context,
|
|
343
|
+
black = black,
|
|
301
344
|
device = Emulation.deviceId(),
|
|
302
345
|
}
|
|
303
346
|
end
|
|
@@ -313,6 +356,7 @@ local function encode(contentId: string, width: number, context: string): { [str
|
|
|
313
356
|
sourceHeight = sourceHeight,
|
|
314
357
|
bytes = buffer.len(png),
|
|
315
358
|
context = context,
|
|
359
|
+
black = black,
|
|
316
360
|
device = Emulation.deviceId(),
|
|
317
361
|
}
|
|
318
362
|
end
|