MCCTP
Fabric-мод, который поднимает WebSocket-сервер в твоём клиенте Minecraft. Он стримит состояние игры каждый тик и принимает команды действий — позволяя любой программе на любом языке управлять игроком.
- Загрузчики
- Fabric
- Скачиваний на Modrinth
- 367
- Подписчиков на Modrinth
- 2
- версии
- 3
- Обновлён
- 3 марта 20263 мар.
- Опубликован
- в 2026
Коротко о моде
Ставится только в игру; на сервер ставить не нужно
MCCTP - Minecraft Control Transfer Protocol
Мод для Fabric, который поднимает WebSocket-сервер на вашем клиенте Minecraft. Он стримит состояние игры каждый тик и принимает команды действий — позволяя любой программе на любом языке управлять игроком.
Возможности
- WebSocket API — JSON поверх WebSocket, не зависит от языка
- 13 типов действий — движение, бой, инвентарь, камера и другое
- Состояние игры каждый тик — здоровье, позиция, предмет в руке, боевой контекст
- Категоризация предметов — автоматическая классификация (SWORD, BOW, BLOCK, FOOD и т.д.)
- Круговой оверлей хотбара — круговой HUD со всеми 9 слотами, управляется командами или клавишей V
- Потокобезопасность — WebSocket работает в собственной группе потоков Netty, действия отправляются в игровой поток
Требования
- Minecraft 1.21.11
- Fabric Loader >= 0.18.0
- Fabric API
- Java 21+
Установка
./gradlew buildСкопируйте build/libs/mcctp-1.0.0.jar в папку .minecraft/mods/ (рядом с Fabric Loader + Fabric API).
Как это работает
Когда вы заходите в мир, мод запускает WebSocket-сервер на порту 8765. Любой клиент может подключиться к `ws://:8765/mcctp` и:
- Получать JSON состояния игры каждый тик (50мс по умолчанию)
- Отправлять команды действий в виде JSON для управления игроком
Сервер работает в собственной NioEventLoopGroup Netty, отдельно от сетевого стека Minecraft. Входящие действия отправляются в игровой поток через MinecraftClient.execute(), так что они безопасны и ведут себя идентично реальному вводу. Сервер останавливается, когда вы выходите из мира.
Протокол
Подключение
ws://<host>:8765/mcctpСтандартное WebSocket-соединение. Без аутентификации. Несколько клиентов могут подключаться одновременно.
Состояние игры (Сервер → Клиент)
Транслируется всем подключённым клиентам каждый тик. Все поля присутствуют всегда.
{
"type": "game_state",
"timestamp": 1700000000000,
"selectedSlot": 0,
"heldItem": {
"name": "minecraft:diamond_sword",
"category": "SWORD",
"stackCount": 1,
"maxDurability": 1561,
"currentDurability": 1500
},
"offhandItem": {
"name": "minecraft:shield",
"category": "SHIELD",
"stackCount": 1,
"maxDurability": 336,
"currentDurability": 336
},
"playerState": {
"health": 20.0,
"maxHealth": 20.0,
"hunger": 20,
"saturation": 5.0,
"x": 100.5,
"y": 64.0,
"z": -200.3,
"yaw": 45.0,
"pitch": -10.0,
"onGround": true,
"sprinting": false,
"sneaking": false,
"swimming": false,
"flying": false,
"inWater": false,
"onFire": false
},
"combatContext": {
"isUsingItem": false,
"isBlocking": false,
"activeHand": "MAIN_HAND",
"crosshairTarget": "ENTITY",
"crosshairEntityType": "minecraft:zombie",
"crosshairBlockPos": null
}
}Поля состояния игры
| Поле | Тип | Описание |
|---|---|---|
type | string | Всегда "game_state" |
timestamp | long | Unix-миллисекунды |
selectedSlot | int | Активный слот хотбара (0-8) |
heldItem / offhandItem:
| Поле | Тип | Описание |
|---|---|---|
name | string | ID в реестре, например "minecraft:diamond_sword" |
category | string | См. Категории предметов |
stackCount | int | Текущий размер стака |
maxDurability | int | Максимальная прочность (0, если не имеет прочности) |
currentDurability | int | Оставшаяся прочность |
playerState:
| Поле | Тип | Описание |
|---|---|---|
health | float | Текущее здоровье (0-20) |
maxHealth | float | Максимальное здоровье |
hunger | int | Уровень еды (0-20) |
saturation | float | Уровень насыщения |
x, y, z | float | Позиция в мире |
yaw, pitch | float | Поворот камеры (в градусах) |
onGround | bool | Стоит на твёрдой земле |
sprinting | bool | Сейчас бежит |
sneaking | bool | Сейчас крадётся |
swimming | bool | Сейчас плывёт |
flying | bool | Полёт в креативе/на элитрах |
inWater | bool | Находится под водой |
onFire | bool | Горит |
combatContext:
| Поле | Тип | Описание |
|---|---|---|
isUsingItem | bool | Удерживается правая кнопка мыши (еда, натягивание лука и т.д.) |
isBlocking | bool | Блокирование щитом |
activeHand | string | "MAIN_HAND" или "OFF_HAND" |
crosshairTarget | string | "ENTITY", "BLOCK" или "MISS" |
crosshairEntityType | string? | ID сущности в реестре или null |
crosshairBlockPos | int[3]? | Позиция блока [x, y, z] или null |
Действия (клиент → сервер)
Отправьте JSON-сообщение с action и params:
{"action": "<action_name>", "params": {<params>}}Справочник действий
| Действие | Параметры | Описание |
|---|---|---|
move | direction: "forward" "backward" "left" "right", state: "start" "stop" | Движение WASD. Удерживает клавишу, пока не отправите "stop". |
look | yaw: float, pitch: float, relative: bool | Управление камерой. relative: true прибавляет к текущим углам. relative: false задаёт абсолютные значения. |
jump | (нет) | Одиночный прыжок. |
attack | (нет) | Левый клик — взмах оружием, удар по сущности, начало ломания блока. |
use_item | state: "start" "stop" | Удержание правого клика. Используйте "start", чтобы начать (натянуть лук, поесть, поднять щит), и "stop", чтобы отпустить. |
throw_item | (нет) | Одиночный импульс правого клика — для снежков, эндер-жемчужин, выстрела из заряженного арбалета. |
select_slot | slot: int (0-8) | Переключить активный слот хотбара. |
sneak | state: "start" "stop" | Присесть. Удерживается до остановки. |
sprint | state: "start" "stop" | Бег. Удерживается до остановки. |
drop_item | full_stack: bool | Выбросить предмет из руки. true выбрасывает весь стак (Ctrl+Q). |
swap_hands | (нет) | Поменять местами основную руку и вторую руку (клавиша F). |
open_inventory | (нет) | Открыть/закрыть экран инвентаря (клавиша E). |
toggle_wheel | (нет) | Переключить HUD-оверлей колеса хотбара. |
Примеры действий
// Идти вперёд
{"action": "move", "params": {"direction": "forward", "state": "start"}}
// Остановиться
{"action": "move", "params": {"direction": "forward", "state": "stop"}}
// Повернуть на 90 градусов вправо
{"action": "look", "params": {"yaw": 90.0, "pitch": 0.0, "relative": true}}
// Смотреть на север, прямо перед собой
{"action": "look", "params": {"yaw": 180.0, "pitch": 0.0, "relative": false}}
// Натянуть и выстрелить из лука
{"action": "use_item", "params": {"state": "start"}}
// ... подождать ~1 секунду ...
{"action": "use_item", "params": {"state": "stop"}}
// Бросить снежок
{"action": "throw_item", "params": {}}
// Переключиться на слот 3 и атаковать
{"action": "select_slot", "params": {"slot": 3}}
{"action": "attack", "params": {}}Категории предметов
Каждый предмет в руке относится к одной из этих категорий:
| Категория | Примеры |
|---|---|
SWORD | Алмазный меч, незеритовый меч |
BOW | Лук |
CROSSBOW | Арбалет |
AXE | Железный топор, деревянный топор |
PICKAXE | Алмазная кирка |
SHOVEL | Каменная лопата |
HOE | Незеритовая мотыга |
TRIDENT | Трезубец |
SHIELD | Щит |
FOOD | Стейк, золотое яблоко |
BLOCK | Булыжник, земля, доски |
THROWABLE | Снежок, яйцо, эндер-жемчужина |
FISHING_ROD | Удочка |
EMPTY | Пустая рука |
OTHER | Всё, что не подошло выше |
Конфигурация
Файл конфигурации: .minecraft/config/mcctp.json (создаётся при первом запуске)
{
"port": 8765,
"tickInterval": 1
}| Поле | По умолчанию | Описание |
|---|---|---|
port | 8765 | Порт WebSocket-сервера |
tickInterval | 1 | Рассылать состояние игры каждые N тиков (20 тиков = 1 секунда) |
Архитектура
Любой клиент ←── WebSocket (JSON) ──→ Fabric Mod
│
├── WebSocketServer (Netty, своя группа потоков)
│ ├── WebSocketServerInitializer (HTTP → WS upgrade)
│ ├── WebSocketFrameHandler (парсит входящий JSON)
│ └── ConnectionManager (отслеживает каналы, рассылает)
│
├── GameStateCollector (END_CLIENT_TICK → JSON)
│ ├── GameStatePayload
│ ├── HeldItemInfo + ItemCategorizer
│ ├── PlayerStateInfo
│ └── CombatContextInfo
│
├── ActionDispatcher (JSON → ActionHandler)
│ ├── MoveHandler, LookHandler, JumpHandler
│ ├── AttackHandler, UseItemHandler, ThrowItemHandler
│ ├── SelectSlotHandler, SneakHandler, SprintHandler
│ ├── DropItemHandler, SwapHandsHandler
│ ├── OpenInventoryHandler, ToggleWheelHandler
│ └── KeyReleaseScheduler (timed key pulses)
│
└── HotbarWheelRenderer (HUD overlay)Ключевые детали реализации:
- WebSocket-сервер использует Netty
NioEventLoopGroup, полностью отдельную от сетевого потока Minecraft - Все обработчики действий выполняются в игровом потоке через
MinecraftClient.execute()— так же, как настоящий ввод игрока - Импульсные действия (прыжок, атака, бросок, смена рук, инвентарь) используют
KeyReleaseScheduler, который удерживает клавишу 3 тика и увеличиваетtimesPressed, повторяя то, как Minecraft обрабатывает одиночные нажатия клавиш - Удерживаемые действия (движение, приседание, спринт, use_item) напрямую выставляют
KeyBinding.pressedи остаются зажатыми до команды"stop" - Сбор состояния завязан на
END_CLIENT_TICKи сериализуется через Gson - Мод использует Mixin-аксессор (
KeyBindingAccessor) для доступа кKeyBinding.pressedиKeyBinding.timesPressed
Сборка из исходников
# Build
./gradlew build
# Output jar
build/libs/mcctp-1.0.0.jarСборка под Java 21. Протестировано с Gradle 9.2.0 и Fabric Loom 1.15.3.
Python-клиент
Референсный Python-клиент включён в python/. Установка:
cd python
pip install -e .Предоставляет асинхронный и синхронный WebSocket-клиенты с типизированными датаклассами для состояния игры:
import time
from mcctp import SyncMCCTPClient, Actions
with SyncMCCTPClient("localhost", 8765) as client:
client.on_state(lambda s: print(s.player_state.health))
client.send(Actions.move("forward", "start"))
time.sleep(2)
client.send(Actions.move("forward", "stop"))Также включает remote_play.py — проброс клавиатуры и мыши, позволяющий кому-то играть в вашем мире с другого компьютера.
Лицензия
MIT
Частые вопросы
Как установить MCCTP?
Установите Fabric под свою версию Minecraft, скачайте MCCTP под ту же версию и загрузчик, положите файл в папку mods и запустите игру с профилем загрузчика.
Для каких версий Minecraft есть MCCTP?
У MCCTP есть сборки для Minecraft 1.21.11.
Нужен ли MCCTP на сервере?
Ставится только в игру; на сервер ставить не нужно