Пакет это одна реплика в разговоре клиента с сервером. Двадцать раз в секунду твой клиент говорит: я вот здесь, смотрю туда, стою на земле. Сервер отвечает: тебя откинуло, тебе прилетело три урона, рядом появился игрок. Всё, что происходит в мультиплеере, это пакеты.
Скрипт может влезть в этот разговор: прочитать пакет, поменять его или вообще не пропустить дальше. Для этого есть два события:
| Событие | Когда приходит | Что можно |
|---|---|---|
send_packet | Прямо перед отправкой пакета на сервер | getPacket(), setPacket(p), cancel(): прочитать, подменить целиком, не отправлять |
receive_packet | Пакет пришёл от сервера, но игра его ещё не применила | getPacket(), cancel(): прочитать, править поля, выкинуть |
send_packet ловит всё, что клиент собирается отправить. Поля пакета читаются сразу, как у любого объекта игры: готовить его ничем не нужно.
MOVE = "net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket"
mod = Module("MoveLog", "Other")
mod.setDesc("Пишет в лог всё, что уходит серверу про наше движение")
@events.send_packet
def on_send(event):
if not mod.isEnabled():
return
p = event.getPacket()
if not is_instance(p, MOVE): # какой это пакет
return
print("move: %.2f %.2f %.2f | yaw=%.1f pitch=%.1f | ground=%s" % (
p.x, p.y, p.z, p.yaw, p.pitch, p.onGround,
))print пишет в лог клиента (latest.log), а не в чат. Для потока в двадцать строк в секунду это единственный вменяемый вариант.
receive_packet зеркальная история. Пакет уже прилетел от сервера, но игра его ещё не применила. Заменить пакет целиком тут нельзя (setPacket есть только у send_packet), зато можно прочитать поля, поправить их или вообще выкинуть пакет.
Классика это антикнокбэк. Сервер шлёт нам вектор отбрасывания, а мы его не принимаем. Проверяем, что пакет о скорости адресован именно нам (сравниваем номер сущности со своим), и рубим.
VELOCITY = "net.minecraft.network.packet.s2c.play.EntityVelocityUpdateS2CPacket"
EXPLOSION = "net.minecraft.network.packet.s2c.play.ExplosionS2CPacket"
mod = Module("NoVelocity", "Combat")
mod.setDesc("Не даёт серверу нас откидывать")
@events.receive_packet
def on_receive(event):
if not mod.isEnabled() or not world.ingame():
return
packet = event.getPacket()
if is_instance(packet, VELOCITY):
# такой пакет прилетает на любую сущность, нас волнует только своя
if packet.getEntityId() == mc.player.getId():
event.cancel()
return
if is_instance(packet, EXPLOSION):
event.cancel() # взрывы тоже толкают игрокаУ обоих событий есть cancel() и isCancelled(). Но чаще нужна не отмена, а подмена: пусть пакет уйдёт, но с другими данными. Проще всего переписать поля прямо в готовом пакете, это и есть самый короткий путь к классическому NoFall.
MOVE = "net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket"
mod = Module("NoFall", "Player")
mod.setDesc("Всегда говорит серверу, что мы на земле")
@events.send_packet
def on_send(event):
if not mod.isEnabled() or not world.ingame():
return
packet = event.getPacket()
if not is_instance(packet, MOVE):
return
if not mc.player.isOnGround():
# сервер думает, что мы приземлились, значит урона от падения нет
packet.onGround = TrueПоля пакета читаются и пишутся как обычные атрибуты. Что лежит внутри PlayerMoveC2SPacket:
| Поле | Тип | Что значит |
|---|---|---|
x / y / z | float | Позиция, которую увидит сервер |
yaw / pitch | float | Углы взгляда, которые увидит сервер |
onGround | bool | Стоим ли на земле. Именно его и правит NoFall |
horizontalCollision | bool | Упёрлись ли в стену |
У send_packet есть и полная замена, setPacket(p), если у тебя уже есть готовый объект пакета. Но собирать пакет с нуля почти никогда не нужно: правка полей делает то же самое и не требует знать конструктор.
Пакетов десятки видов, и все они валятся в одно и то же событие. Значит, первым делом надо понять, какой пакет к тебе пришёл.
Привычный isinstance тут не сработает. Тип пакета проверяет is_instance(объект, полное имя класса). Заодно проверка ловит все разновидности пакета, а не только его самого.
MOVE = "net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket"
SWING = "net.minecraft.network.packet.c2s.play.HandSwingC2SPacket"
@events.send_packet
def on_send(event):
packet = event.getPacket()
# PlayerMoveC2SPacket это родительский класс, проверка ловит и всех его детей
# ($Full, $PositionAndOnGround, $LookAndOnGround, $OnGroundOnly)
if is_instance(packet, MOVE):
...
elif is_instance(packet, SWING):
...Шпаргалка по самым ходовым пакетам:
| Класс | Куда | О чём |
|---|---|---|
PlayerMoveC2SPacket | send | Позиция, углы взгляда и флаг onGround |
PlayerInteractEntityC2SPacket | send | Удар или взаимодействие с сущностью |
HandSwingC2SPacket | send | Взмах рукой |
ClientCommandC2SPacket | send | Присед, спринт, прыжок на лошади |
ChatMessageC2SPacket | send | Сообщение в чат |
EntityVelocityUpdateS2CPacket | receive | Сервер задаёт сущности скорость, это и есть кнокбэк |
ExplosionS2CPacket | receive | Взрыв, он тоже толкает игрока |
HealthUpdateS2CPacket | receive | Обновление здоровья и голода |
PlayerPositionLookS2CPacket | receive | Сервер силой ставит тебя на место (тот самый резиновый рывок назад) |
GameMessageS2CPacket | receive | Сообщение из чата сервера |