- Updated RobloxMCPPlugin with HTTP polling (auto-enables HttpService) - Added 20-weapon FPS game example (CoD-style) - Added Python studio-inject.py for command bar injection via Win32 API - Added auto-connect setup scripts (VBS + PowerShell) - Updated MCP server with all FPS game tools Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
177 lines
5.1 KiB
Python
177 lines
5.1 KiB
Python
"""
|
|
Inject all 5 FPS game parts into Roblox Studio command bar sequentially.
|
|
"""
|
|
import ctypes
|
|
import ctypes.wintypes
|
|
import subprocess
|
|
import time
|
|
import sys
|
|
import os
|
|
|
|
user32 = ctypes.windll.user32
|
|
|
|
def find_studio():
|
|
target = [None]
|
|
def cb(hwnd, _):
|
|
length = user32.GetWindowTextLengthW(hwnd)
|
|
if length > 0:
|
|
buf = ctypes.create_unicode_buffer(length + 1)
|
|
user32.GetWindowTextW(hwnd, buf, length + 1)
|
|
if "Roblox Studio" in buf.value:
|
|
target[0] = hwnd
|
|
return False
|
|
return True
|
|
WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.wintypes.HWND, ctypes.wintypes.LPARAM)
|
|
user32.EnumWindows(WNDENUMPROC(cb), 0)
|
|
return target[0]
|
|
|
|
def set_foreground(hwnd):
|
|
SW_RESTORE = 9
|
|
user32.ShowWindow(hwnd, SW_RESTORE)
|
|
time.sleep(0.3)
|
|
fg = user32.GetForegroundWindow()
|
|
if fg != hwnd:
|
|
tid_fg = user32.GetWindowThreadProcessId(fg, None)
|
|
tid_target = user32.GetWindowThreadProcessId(hwnd, None)
|
|
user32.AttachThreadInput(tid_fg, tid_target, True)
|
|
user32.SetForegroundWindow(hwnd)
|
|
user32.AttachThreadInput(tid_fg, tid_target, False)
|
|
time.sleep(0.3)
|
|
|
|
def set_clipboard(text):
|
|
# Use PowerShell for reliable clipboard
|
|
# Write to temp file first to avoid escaping issues
|
|
tmp = os.path.join(os.environ["TEMP"], "roblox_clipboard.lua")
|
|
with open(tmp, "w", encoding="utf-8") as f:
|
|
f.write(text)
|
|
result = subprocess.run(
|
|
["powershell", "-Command",
|
|
f"Get-Content '{tmp}' -Raw | Set-Clipboard"],
|
|
capture_output=True, text=True, timeout=10
|
|
)
|
|
return result.returncode == 0
|
|
|
|
def press_key(vk):
|
|
user32.keybd_event(vk, 0, 0, 0)
|
|
time.sleep(0.03)
|
|
user32.keybd_event(vk, 0, 2, 0)
|
|
time.sleep(0.05)
|
|
|
|
def ctrl_v():
|
|
user32.keybd_event(0x11, 0, 0, 0) # Ctrl down
|
|
time.sleep(0.02)
|
|
user32.keybd_event(0x56, 0, 0, 0) # V down
|
|
time.sleep(0.03)
|
|
user32.keybd_event(0x56, 0, 2, 0) # V up
|
|
time.sleep(0.02)
|
|
user32.keybd_event(0x11, 0, 2, 0) # Ctrl up
|
|
time.sleep(0.1)
|
|
|
|
def click_at(x, y):
|
|
screen_w = user32.GetSystemMetrics(0)
|
|
screen_h = user32.GetSystemMetrics(1)
|
|
nx = int(x * 65535 / screen_w)
|
|
ny = int(y * 65535 / screen_h)
|
|
user32.mouse_event(0x8001, nx, ny, 0, 0) # Move
|
|
time.sleep(0.02)
|
|
user32.mouse_event(0x8002, nx, ny, 0, 0) # Down
|
|
time.sleep(0.03)
|
|
user32.mouse_event(0x8004, nx, ny, 0, 0) # Up
|
|
time.sleep(0.05)
|
|
|
|
def inject_script(lua_code, part_num, total):
|
|
print(f"\n [{part_num}/{total}] Injecting {len(lua_code)} bytes...")
|
|
|
|
if not set_clipboard(lua_code):
|
|
print(f" ERROR: Clipboard failed for part {part_num}")
|
|
return False
|
|
|
|
time.sleep(0.3)
|
|
|
|
# Press Escape to clear any selection
|
|
press_key(0x1B)
|
|
time.sleep(0.2)
|
|
|
|
# Click in command bar area
|
|
hwnd = find_studio()
|
|
if not hwnd:
|
|
print(" ERROR: Studio window lost!")
|
|
return False
|
|
|
|
rect = ctypes.wintypes.RECT()
|
|
user32.GetWindowRect(hwnd, ctypes.byref(rect))
|
|
w = rect.right - rect.left
|
|
h = rect.bottom - rect.top
|
|
cmd_x = rect.left + w // 2
|
|
cmd_y = rect.bottom - 50
|
|
|
|
click_at(cmd_x, cmd_y)
|
|
time.sleep(0.3)
|
|
|
|
# Select all + delete existing text
|
|
user32.keybd_event(0x11, 0, 0, 0) # Ctrl
|
|
press_key(0x41) # A
|
|
user32.keybd_event(0x11, 0, 2, 0) # Ctrl up
|
|
time.sleep(0.1)
|
|
press_key(0x2E) # Delete
|
|
time.sleep(0.1)
|
|
|
|
# Paste
|
|
ctrl_v()
|
|
time.sleep(0.5)
|
|
|
|
# Execute
|
|
press_key(0x0D) # Enter
|
|
time.sleep(1.5) # Wait for execution
|
|
|
|
print(f" [{part_num}/{total}] Done.")
|
|
return True
|
|
|
|
def main():
|
|
parts = [
|
|
r"C:\Users\Admin\ClaudeCode-Roblox-Studio-MCP\examples\fps-game\part1_map.lua",
|
|
r"C:\Users\Admin\ClaudeCode-Roblox-Studio-MCP\examples\fps-game\part2_weapons.lua",
|
|
r"C:\Users\Admin\ClaudeCode-Roblox-Studio-MCP\examples\fps-game\part3_ai.lua",
|
|
r"C:\Users\Admin\ClaudeCode-Roblox-Studio-MCP\examples\fps-game\part4_hud.lua",
|
|
r"C:\Users\Admin\ClaudeCode-Roblox-Studio-MCP\examples\fps-game\part5_client.lua",
|
|
]
|
|
total = len(parts)
|
|
|
|
print("=" * 50)
|
|
print(" MINI CALL OF DUTY - Injecting into Roblox Studio")
|
|
print("=" * 50)
|
|
|
|
# Find and focus Studio
|
|
hwnd = find_studio()
|
|
if not hwnd:
|
|
print("ERROR: Roblox Studio not found!")
|
|
sys.exit(1)
|
|
|
|
print(f"\n Studio found: HWND={hwnd}")
|
|
set_foreground(hwnd)
|
|
time.sleep(1)
|
|
|
|
for i, path in enumerate(parts, 1):
|
|
if not os.path.exists(path):
|
|
print(f"\n WARNING: {path} not found. Skipping.")
|
|
continue
|
|
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
lua_code = f.read()
|
|
|
|
if not inject_script(lua_code, i, total):
|
|
print(f"\n FATAL: Part {i} failed. Stopping.")
|
|
sys.exit(1)
|
|
|
|
# Re-focus between injections
|
|
set_foreground(hwnd)
|
|
time.sleep(1)
|
|
|
|
print("\n" + "=" * 50)
|
|
print(" ALL PARTS INJECTED SUCCESSFULLY!")
|
|
print(" Press PLAY in Roblox Studio to start the game.")
|
|
print("=" * 50)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|