Сущность это всё, что живёт и двигается в мире: игроки, мобы, стрелы, выпавшие предметы, лодки.
world это не сам мир Minecraft, а удобная надстройка над ним. Она выдаёт списки сущностей, ближайшего игрока и цель Aura. И она не падает, когда мира ещё нет: просто вернёт пустой список или None.
| Метод | Возвращает | Что это |
|---|---|---|
world.ingame() | bool | Мир и игрок на месте? |
world.self() | Entity | None | Ты сам, то же самое, что mc.player |
world.entities() | list | Вообще все сущности: игроки, мобы, предметы, стрелы |
world.living() | list | Только живые: игроки и мобы |
world.players() | list | Другие игроки, без тебя |
world.nearest_player() | Entity | None | Ближайший игрок |
world.target() | Entity | None | Кого сейчас бьёт Aura |
world.time() | int | Игровое время мира в тиках |
Если этого мало, настоящий мир лежит в mc.world, и у него есть все обычные методы игры: mc.world.getEntityById(id), mc.world.getBlockState(pos), mc.world.getTime().
Списки самые обычные, питоновские: работают срезы, sorted, генераторы. У каждого элемента можно спрашивать координаты, здоровье, имя.
me = world.self()
for entity in world.living(): # игроки и мобы
if entity == me: # ты тоже в списке, себя пропускаем
continue
if not entity.isAlive():
continue
print(entity.getX(), entity.getZ(), entity.getHealth())С именем есть подвох. Самый короткий путь это entity.getNameForScoreboard(): он сразу отдаёт обычную строку. А вот getName() вернёт объект Text, то есть текст с цветами и форматированием, и строку из него надо ещё достать:
def nick(entity):
return str(entity.getNameForScoreboard())
# то же самое, но через Text: n это сколько символов взять
def nick_text(entity):
return str(entity.getName().asTruncatedString(64))Соберём что-то настоящее: список игроков рядом прямо на экране. Ник, дистанция, здоровье, красным раненые. Рисование живёт в событии render_2d.
mod = Module("PlayerList", "Visuals")
mod.setDesc("Список игроков рядом: ник, дистанция, хп")
radius = Slider(mod, "Radius").min(5).max(100).step(5).set(40).suffix(" м")
lines = Slider(mod, "Max lines").min(1).max(12).step(1).set(6)
FONT = font("medium", 8)
WHITE = Color(220, 225, 240)
RED = Color(255, 95, 95)
BG = Color(9, 9, 11, 190)
def nick(entity):
return str(entity.getNameForScoreboard())
@events.render_2d
def draw(event):
if not mod.isEnabled() or not world.ingame():
return
me = world.self()
max_dist = float(radius.get())
near = [p for p in world.players() if float(me.distanceTo(p)) <= max_dist]
near.sort(key=lambda p: float(me.distanceTo(p)))
near = near[:int(lines.get())]
if not near:
return
ctx = event.getContext() # холст клиента, рисуем прямо на нём
x, y = 8.0, 40.0
ctx.drawRoundedRect(x - 4, y - 4, 152, len(near) * 11 + 8, border(6), BG)
for p in near:
dist = float(me.distanceTo(p))
hp = float(p.getHealth())
text = "%s %.0f m %.0f hp" % (nick(p), dist, hp)
ctx.drawText(FONT, text, x, y, RED if hp <= 10.0 else WHITE)
y += 11.0Почему дистанция через me.distanceTo(p), а не через теорему Пифагора руками? Это метод самой игры: он берёт свежие позиции сущностей в этом же тике, и ничего импортировать не надо.
Самый частый вопрос: кто ко мне ближе всех. Для игроков ответ готов, world.nearest_player(). Он уже выкидывает тебя самого и возвращает None, если рядом никого.
@events.tick
def on_tick(event):
if not world.ingame():
return
target = world.nearest_player()
if target is None:
return
dist = float(world.self().distanceTo(target))
if dist <= 4.0:
client.overlay("Рядом: %s (%.1f м)" % (target.getNameForScoreboard(), dist))А если нужен не игрок, а любая живая цель (моб, животное), пиши свой поиск. Схема всегда одна: один проход по world.living(), запоминаем минимум.
def nearest_living(max_dist=6.0):
me = world.self()
if me is None:
return None
best, best_dist = None, float(max_dist)
for entity in world.living():
if entity == me or not entity.isAlive():
continue
dist = float(me.distanceTo(entity))
if dist < best_dist:
best, best_dist = entity, dist
return bestНайденную цель удобно тут же подсветить в 3D. render3d умеет принимать сущность целиком:
@events.render_3d
def draw3d(event):
target = world.nearest_player() # вне игры вернёт None, падать нечему
if target is not None:
render3d.glowing_box(event, target, Color(255, 80, 80, 140))
render3d.ring(event, target, radius=0.9, color=Color(255, 80, 80))Клиент знает про блоки только в загруженных чанках, и ровно это отдаёт скрипту. world.block(x, y, z) возвращает идентификатор блока в точке, а если чанк не загружен, то None: это честнее, чем говорить «воздух» про место, где может стоять что угодно.
@command("look")
def look(ctx):
"""Что подо мной и где ближайший сундук"""
pos = mc.player.getBlockPos()
under = world.block(pos.getX(), pos.getY() - 1, pos.getZ())
ctx.msg("Стою на: " + str(under))
chest = world.nearest_block("chest", radius=32)
if chest:
ctx.msg("Ближайший сундук: %d %d %d" % (chest[0], chest[1], chest[2]))
else:
ctx.msg("Сундуков рядом нет")| Вызов | Что делает |
|---|---|
world.block(x, y, z) | Идентификатор блока или None, если чанк не загружен |
world.block_name(x, y, z) | Имя блока так, как его видит игрок |
world.block_info(x, y, z) | Словарь: id, name, air, liquid, solid, pos, state |
world.is_block("chest", x, y, z) | Тот ли это блок |
world.is_air / is_solid / is_liquid(x, y, z) | Пусто, можно встать, жидкость |
world.nearest_block("chest", radius=32) | Ближайший такой блок: [x, y, z] или None |
world.find_blocks("chest", radius=32, limit=64) | Все такие блоки, ближние первыми |
world.top(x, z) | Высота верхнего непустого блока в колонке |
world.known_block("chest") | Есть ли вообще такой блок в игре: ловит опечатку в имени |
world.dimension() | minecraft:overworld, minecraft:the_nether, minecraft:the_end |
world.dimension_name() | Оно же коротко: overworld, the_nether, the_end |
world.is_overworld() / is_nether() / is_end() | Проверка измерения одной строкой |
Координаты можно давать тремя числами, списком или объектом с координатами: сущность и BlockPos тоже подойдут. Имя блока пишется как chest или полностью, minecraft:chest, разницы нет.
События прилетают и тогда, когда мира нет. connect срабатывает ещё до загрузки мира. При смене измерения (портал в Ад, например) игра на пару мгновений выкидывает старый мир и игрока, и mc.world становится None прямо между событиями. Дёрнешь в этот момент mc.player, получишь [Python Error] в чат и выгруженный скрипт.
@events.render_2d
def draw(event):
if not world.ingame(): # одна строчка, и скрипт больше не падает
return
...
@events.tick
def on_tick(event):
if mc.world is None or mc.player is None: # то же самое, но вручную
return
...| Проверка | Когда нужна |
|---|---|
world.ingame() | Первая строка почти любого обработчика |
mc.player is None | Когда нужен именно игрок, например разбираешь очередь из фонового потока |
mod.isEnabled() | Модуль выключен, не тратим тик впустую |
client.menu_opened() | Открыто меню клиента, можно не рисовать свои подсказки |