- fix: replace field-level Minecraft.getInstance() with lazy locals — Phase 5 sweep complete - docs: plan Phase 7 — new modules inspired by SoupVisuals (re-implementation, no code copied) - test: Phase 6 — EventBus, config values, profile codec/diff, LegacyTextUtil (54 new tests) - fix: LegacyTextUtil.isColor via explicit EnumSet instead of ChatFormatting.ordinal() - fix: last 4 field-level Minecraft instances (ESP, NameTags, TargetHud, ViewModel) — true end of Phase 5 - fix: ClickPearl + ElytraRecastUtil 'client' fields -> lazy access (final mc-field leftovers) - feat: Phase 7 — Light, Hitboxes, NameProtect, Watermark (re-implemented from SoupVisuals ideas) - fix: ChinaHat geometry — proper shallow cone per SoupVisuals profile - refactor: Watermark — static UI element in our style, not a Soup port - fix: startup crash and trollface texture errors from in-game log - docs+feat: DEVELOPMENT.md guide, Watermark username field, README refresh - feat: Reach HUD widget (Settings -> UI) — last hit distance, pure info - feat: Phase 7 Delta QoL modules + command prefix % with full autocomplete
569 lines
29 KiB
Markdown
569 lines
29 KiB
Markdown
# LoVisual Mod — Dev Guide / Справочник по созданию модулей
|
||
|
||
Цель: быстро посмотреть структуру и написать новый модуль / HUD-элемент /
|
||
команду, не перекапывая весь код. Всё ниже — сводка по реальному коду.
|
||
|
||
- Пакеты: `dev.loki.lovisual.*`
|
||
- Minecraft 26.2, Fabric, Java 25, Lombok
|
||
- Правила проекта (важно!): файл ≤200 строк, папка ≤3 файлов,
|
||
`./gradlew build` после каждого этапа, тесты для новой логики,
|
||
никаких `static final Minecraft` в полях — только ленивый `Minecraft.getInstance()`.
|
||
|
||
---
|
||
|
||
## 1. Архитектура (главные пакеты)
|
||
|
||
```
|
||
dev.loki.lovisual
|
||
├── LoVisual.java — ClientModInitializer: весь старт (тики, рендер-фазы)
|
||
├── Lifecycle.java — INIT → ACTIVE → SHUTDOWN; гейты isActive/canRunRender/canRunHud
|
||
├── features/
|
||
│ ├── module/ — СИСТЕМА МОДУЛЕЙ (главное для вас)
|
||
│ │ ├── Module.java — базовый класс (1181 стр, фасад)
|
||
│ │ ├── ModuleInfo — @annotation для модуля
|
||
│ │ ├── ModuleManager — реестр + диспетчер фаз
|
||
│ │ ├── ModuleAutoLoader — авто-обнаружение по аннотации (ClassGraph)
|
||
│ │ ├── Modules — удобный доступ: Modules.get(Класс.class)
|
||
│ │ ├── ModuleCategory — COMBAT/MOVEMENT/PLAYER/VISUALS/MISC
|
||
│ │ ├── HudPhase / WorldPhase — фазы рендера
|
||
│ │ └── modules/{combat,player,visuals,misc}/
|
||
│ ├── command/ — команды: @CommandInfo + ClientCommand
|
||
│ ├── gui/
|
||
│ │ ├── clickgui/ — экран настроек (Setting → виджет)
|
||
│ │ └── hud/ — HUD-элементы
|
||
│ │ ├── draggable/ — перетаскиваемые (Fps, Coords, ModuleList, TargetHud…)
|
||
│ │ └── nondraggable/ — статичные (CustomBar, CustomHotbar, BetterChat…)
|
||
│ ├── relations/ — друзья/враги/категории (CategoryService)
|
||
│ ├── security/ — BackdoorProtection (SSRF/translate-защита)
|
||
│ └── theme/ — темы: Theme.theme() → Themes.Theme (цвета)
|
||
├── config/
|
||
│ ├── values/ — типы настроек: ConfigValue (bool/num/mode/color…)
|
||
│ ├── SettingDef.java — GUI-нейтральный дескриптор настройки
|
||
│ └── common/CommonSettingSchemas — переиспользуемые схемы (i18n-ключи)
|
||
├── events/
|
||
│ ├── EventBus (Events.BUS) — шина событий
|
||
│ ├── @EventHandler — аннотация хендлера
|
||
│ └── impl/ — все события (см. §5)
|
||
├── mixins/ — миксины + accessors (mixins/accessors/)
|
||
├── addon/ — система аддонов (API v0)
|
||
├── api/v0/ — публичный API аддонов (module/client/render/clickgui)
|
||
├── render/
|
||
│ ├── engine/
|
||
│ │ ├── renderer/Renderer2D — весь 2D-рендер (прямоугольники, круги, текст, items)
|
||
│ │ ├── renderer/Renderer3D — 3D (линии, меши, quads)
|
||
│ │ ├── text/ — Fonts.renderer("Onest", Regular), TextRenderer
|
||
│ │ ├── animation/AnimationUtility
|
||
│ │ ├── pipeline/LoVisualRenderPipelines — готовые пайплайны (UI_COLORED и т.д.)
|
||
│ │ └── uniform/MeshBuilder — построение мешей
|
||
│ └── engine/color/RenderColor — ARGB
|
||
└── util/ — куча хелперов (см. §8)
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Жизненный цикл и старт
|
||
|
||
В `LoVisual.onInitializeClient()` по порядку:
|
||
|
||
1. `LoVisualRenderEngineBootstrap.init()`
|
||
2. `MainConfig.get()`, `AccountConfig.get()`, `CommandManager.init()`
|
||
3. `ModuleAutoLoader.load("dev.loki.lovisual.features.module.modules")` — сканирует пакет,
|
||
ищет классы с `@ModuleInfo`, создаёт через no-args конструктор и зовёт `module.postInit()`.
|
||
4. `ModuleManager.loadAllModuleConfigs()` — применяет сохранённые конфиги, включает enabled-модули.
|
||
5. `HudElements.init()`, `StaticHudElementBootstrap.init()` — HUD-элементы.
|
||
6. `Events.BUS.register(...)` — реестрируются глобальные сервисы.
|
||
7. `Lifecycle.activate()` — после этого события/тики работают.
|
||
|
||
**Вывод:** чтобы добавить модуль — просто положи класс с `@ModuleInfo` в пакет
|
||
`...modules.<категория>` и всё. Реестрировать руками не нужно.
|
||
|
||
`Lifecycle` — главный гейт: `Lifecycle.isActive()`, `canRunRender()`, `canRunHud()`.
|
||
До `activate()` модули и события не работают.
|
||
|
||
---
|
||
|
||
## 3. Модуль — быстрый старт
|
||
|
||
Минимальный модуль (по образцу `FullBright`):
|
||
|
||
```java
|
||
package dev.loki.lovisual.features.module.modules.misc;
|
||
|
||
import dev.loki.lovisual.config.values.NumberValue;
|
||
import dev.loki.lovisual.events.EventHandler;
|
||
import dev.loki.lovisual.events.impl.GameTickEvent;
|
||
import dev.loki.lovisual.features.module.Module;
|
||
import dev.loki.lovisual.features.module.ModuleCategory;
|
||
import dev.loki.lovisual.features.module.ModuleInfo;
|
||
|
||
@ModuleInfo(
|
||
id = "my_feature",
|
||
displayName = "My Feature",
|
||
aliases = {"mf"},
|
||
category = ModuleCategory.MISC,
|
||
enabledByDefault = false
|
||
)
|
||
public class MyFeature extends Module {
|
||
|
||
private final NumberValue<Integer> amount =
|
||
num("myFeatureAmount", 3, 0, 20); // имя_конфига, дефолт, мин, макс
|
||
|
||
@Override
|
||
public void onEnable() {
|
||
// вызывается при включении
|
||
}
|
||
|
||
@Override
|
||
public void onDisable() {
|
||
}
|
||
|
||
@Override
|
||
public void onTick() {
|
||
// каждый игровой тик, если модуль включён
|
||
if (!isEnabled()) return;
|
||
int a = amount.get();
|
||
}
|
||
}
|
||
```
|
||
|
||
### `@ModuleInfo`
|
||
```java
|
||
id = "имя" // нижний регистр, уникальный, это ключ конфига и команды
|
||
displayName = "Имя" // показывается в UI; можно i18n-ключ
|
||
aliases = {} // необязательно
|
||
category = ModuleCategory.XXX
|
||
enabledByDefault = false
|
||
description = "" // или i18n-ключ "module.<id>.description"
|
||
```
|
||
|
||
### Автогенерируемые настройки модуля
|
||
У каждого модуля уже есть (создаются в конструкторе `Module`):
|
||
- `enabled` (BooleanValue) — состояние,
|
||
- `bind` (KeyBindSetting, значение `KeyBindValue`) — клавиша,
|
||
- `activation_source`,
|
||
- `show_in_module_list`.
|
||
|
||
`postInit()` вызывается автоматически лоадером: читает поля-настройки, делает
|
||
`SettingFactory.fromDefs(...)`, регистрирует модуль в `ModuleManager`.
|
||
|
||
### Хуки жизненного цикла (переопределяемые)
|
||
| Метод | Когда |
|
||
|-------|-------|
|
||
| `onEnable()` / `onDisable()` | переключение модуля |
|
||
| `onTick()` | каждый игровой тик (в мире) |
|
||
| `onFrame(float tickDelta)` | каждый кадр |
|
||
| `onKey(int key, int action)` | нажатия клавиш |
|
||
| `onRender2D(GuiGraphicsExtractor)` | vanilla HUD pass |
|
||
| `onRenderHudEngine(Renderer2D, TextRenderer, ...)` | кастомный 2D-рендер (см. §6) |
|
||
| `onRenderHudEngineForeground(...)` | 2D поверх всего |
|
||
| `onRenderWorld(PoseStack, SubmitNodeCollector)` | 3D vanilla-style |
|
||
| `onRenderWorldEngine(Renderer3D, Renderer3D, float)` | 3D через движок (см. §7) |
|
||
| `getHudPhase()` / `getWorldPhase()` | фаза, в которой рендерить |
|
||
|
||
> `getHudPhase()`/`getWorldPhase()` по умолчанию `NONE` — модуль вообще не рендерится
|
||
> диспетчером. Верните фазу, чтобы рендер-хуки вызывались (см. §6/§7).
|
||
|
||
### Управление состоянием
|
||
- `isEnabled()`, `setEnabled(boolean)`, `setEnabled(boolean, ModuleActivationSource)`, `toggle()`
|
||
- `isAvailable()` / `getAvailabilityReason()` — переопределите `getUnavailableReason()`
|
||
- `name()`, `getDisplayName()`, `getAliases()`, `getCategory()`, `getDescription()`
|
||
- `saveConfig()`, `getConfigValue(String)`, `getSettings()`
|
||
|
||
### Доступ к другим модулям
|
||
```java
|
||
import dev.loki.lovisual.features.module.Modules;
|
||
|
||
Freecam fc = Modules.get(Freecam.class); // null если не активен/не зарегистрирован
|
||
boolean on = Modules.enabled("freecam"); // по id
|
||
```
|
||
`ModuleManager.get(String id)` / `ModuleManager.get(Class)` / `ModuleManager.require(Class)` тоже доступны.
|
||
|
||
---
|
||
|
||
## 4. Настройки (ConfigValue → Setting)
|
||
|
||
Базовый класс: `config/values/ConfigValue<T>` — `get()`, `set(v)`, `getName()`,
|
||
`toJson()`, `fromJson(Object)`, `toDisplay()`.
|
||
|
||
Внутри модуля настройки создаются **хелперами** в `Module` (все `protected final`).
|
||
Имя в конфиге и `settingId` (i18n) могут различаться.
|
||
|
||
### Хелперы в Module / BaseHudElement
|
||
| Хелпер | Тип | Пример |
|
||
|--------|-----|--------|
|
||
| `bool(name, def)` | `BooleanValue` | `bool("my_on", true)` |
|
||
| `num(name, def, min, max)` | `NumberValue<Integer/Long/Float/Double>` | `num("my_num", 3, 0, 20)` |
|
||
| `mode(name, def, options...)` | `ModeValue` (String) | `mode("my_mode", "A", "A", "B", "C")` |
|
||
| `enumMode(name, defEnum)` / `enumSetting(name, defEnum, ...)` | `EnumValue<E>` | см. `Reach` |
|
||
| `color(name, "#AARRGGBB")` | `RGBAColorValue` | `color("my_bg", "#F7343434")` |
|
||
| `colorNoAlpha(name, "#RRGGBB")` | `RGBColorValue` | `colorNoAlpha("my_fg", "#FFFFFF")` |
|
||
| `text(name, def)` | `StringValue` | `text("my_text", "")` |
|
||
| `textList(name)` / `textList(name, pickerMode)` | `SetValue` (множество строк) | `textList("my_list")` |
|
||
| `itemList(name)` | `ItemIdSetValue` (список предметов) | `itemList("my_items")` |
|
||
| `group(name, defaultsMap)` | `BooleanMapValue` (группа чекбоксов) | `group("gui_sounds", Map.of(...))` |
|
||
| `bind(name, def, mode)` | `KeyBindValue` | `bind("my_bind", "R", BindMode.PRESS)` |
|
||
| `action(name, defaultKey, mode)` | `FunctionBindSetting` | см. ниже |
|
||
| `setDefaultBind("KEY")` | — | клавиша модуля (см. `ClickGui`) |
|
||
|
||
**Варианты с общим i18n** (`*Common`): `boolCommon`, `numCommon`, `modeCommon`,
|
||
`enumCommon`, `color` (без common) — берут схему из `CommonSettingSchemas`:
|
||
|
||
```java
|
||
distance = numCommon("reachDistance", "range", CommonSettingSchemas.COMBAT_RANGE, 4.5, 3.0, 6.0);
|
||
mode = enumCommon("reachMode", "mode", CommonSettingSchemas.ANTICHEAT_MODE, Mode.NORMAL, Mode.values());
|
||
```
|
||
|
||
### Условная видимость / доступность
|
||
```java
|
||
private final NumberValue<Double> wallDistance =
|
||
visibleWhen(numCommon("reachWallDistance", "wall_range", schema, 3.0, 0.0, 6.0),
|
||
() -> raycast.get() == RaycastMode.THROUGH_WALLS);
|
||
|
||
// недоступен (показывается серым + причина):
|
||
appliesWhen(mySetting, () -> mc.player != null, "Только в мире");
|
||
```
|
||
|
||
### Экшены (вторая кнопка на модуль)
|
||
```java
|
||
action("climb", "G", BindMode.PRESS); // создаёт FunctionBindSetting
|
||
|
||
// в тике:
|
||
if (isActionPressedOnce("climb")) { ... } // фронт нажатия
|
||
if (isActionHeld("climb")) { ... } // удержание
|
||
```
|
||
|
||
### Настройки в HUD-элементах
|
||
У `BaseHudElement` те же хелперы (`bool`, `num`, `mode`, `enumSetting`, `color`,
|
||
`textList`, `bind`, `group`, `visibleWhen`), но объявление идёт в
|
||
`defineSettings(List<SettingDef>)` (см. §9).
|
||
|
||
### Виджеты в UI (SettingDef)
|
||
`SettingDef` — GUI-нейтральный дескриптор. `SettingFactory.fromDef(def)` превращает
|
||
его в виджет. Доступные `Kind`: `BOOLEAN, NUMBER, MODE, COLOR, COLOR_NO_ALPHA,
|
||
TEXT, TEXT_LIST, COOLDOWN_RULES, PROTOCOL_HEURISTICS, GROUP, BIND`.
|
||
Для модулей SettingDef строятся автоматически из хелперов — руками не нужно.
|
||
`@DisableSettingI18n(name = false, options = true)` — отключить i18n у настройки.
|
||
|
||
---
|
||
|
||
## 5. События (EventBus)
|
||
|
||
`Events.BUS.post(event)` — доставка хендлерам. Хендлер — любой метод с одним
|
||
аргументом-наследником `Event`, помеченный `@EventHandler(priority = N)`.
|
||
Подписчики регистрируются автоматически при `ModuleManager.register()` (модули),
|
||
или явно `Events.BUS.register(obj)` (в `LoVisual.onInitializeClient`).
|
||
|
||
Модули-подписчики автоматически гейтятся: если модуль выключен — его хендлеры не вызываются.
|
||
Поэтому в хендлерах модуля проверка `if (!isEnabled()) return;` не обязательна, но
|
||
встречается в коде для ясности.
|
||
|
||
`Event` — база с `cancelled`/`cancel()`. Наследование событий поддерживается.
|
||
|
||
Полный список `events/impl/` (по имени файла):
|
||
|
||
```
|
||
AttackEntityEvent BlinkPacketEvent CombatProtocolBossbarEvent
|
||
CrosshairTargetUpdateEvent EventBreakBlock EventCollision EventPostSync
|
||
EventPushOutOfBlocks EventSync EventTargetChanged FireworkEvent GameTickEvent
|
||
I18nPreflightCollectEvent KeybindIsPressedEvent KeyInputEvent LightmapEvent
|
||
LightmapModifyEvent MovementInputEvent PacketEvent PlayerJumpEvent PlayerMoveEvent
|
||
PlayerSafeWalkEvent PlayerStepEvent PlayerStepSuccessEvent PlayerVelocityStrafe
|
||
PostPlayerUpdateEvent PvpChatEvent PvpOverlayEvent PvpTabEvent
|
||
RenderPrewarmCollectEvent RotationUpdateEvent SprintControlEvent
|
||
```
|
||
|
||
Типичные события для нового модуля:
|
||
- `GameTickEvent` — каждый тик,
|
||
- `PacketEvent` — сетевые пакеты,
|
||
- `AttackEntityEvent`, `EventTargetChanged` — бой/цели,
|
||
- `LightmapModifyEvent` — свет (пример `FullBright`),
|
||
- `PlayerMoveEvent`, `MovementInputEvent` — движение,
|
||
- `RenderPrewarmCollectEvent` — «прогреть» шрифты/текстуры для вашего рендера.
|
||
|
||
Пример:
|
||
```java
|
||
@EventHandler
|
||
private void onTick(GameTickEvent event) { ... }
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 2D-рендер (HUD)
|
||
|
||
### Фазы
|
||
Верните не-`NONE` фазу из `getHudPhase()`:
|
||
```java
|
||
HudPhase: FIRST, BEFORE_MISC_OVERLAYS, AFTER_MISC_OVERLAYS, AFTER_BOSS_BAR,
|
||
BEFORE_DEMO_TIMER, BEFORE_CHAT, AFTER_SUBTITLES, LAST
|
||
```
|
||
`ModuleManager.renderHudEngine(phase, renderer, textRenderer, ctx, tickDelta)` вызывает
|
||
у модуля `onRenderHudEngine(...)`; `renderHudEngineForeground` → `onRenderHudEngineForeground`.
|
||
|
||
### Renderer2D — главный 2D API
|
||
Включается/выключается через `begin()` / `render()` (уже делает диспетчер).
|
||
Готовые методы (фасад `Renderer2D.java`, все в `px`):
|
||
|
||
- **Фигуры:** `quad(x,y,w,h,argb)`, `quad(x,y,w,h,cTL,cTR,cBR,cBL)`,
|
||
`roundedRect(x,y,w,h,radius,argb)`, `roundedRectStroke(...)`,
|
||
`roundedRectGradient(...)`, `roundedRectGlow/Shadow/SoftShadow(...)`,
|
||
`circle(cx,cy,r,argb)`, `circleStroke(...)`, `arcStroke(...)`,
|
||
`line(x1,y1,x2,y2,argb)`, `boxLines(...)`, `roundedSmokeFill(...)`.
|
||
- **Текст:** см. §6.2.
|
||
- **Предметы:** `item(ItemStack, x, y, scale)`, `itemUnscaled(...)`, `itemPivot(...)`.
|
||
- **SVG/текстуры:** `svg(Identifier/Path, x, y, w, h)`, `textureQuad(...)`, `msdfTextureQuad(...)`.
|
||
- **Effect (стеклянные панели):** `effect(UiEffectSpec)`.
|
||
|
||
### Текст
|
||
```java
|
||
TextRenderer tr = Fonts.renderer("Onest", FontInfo.Type.Regular, textRenderer);
|
||
// или Fonts.renderer("OnestMedium", Regular), "OnestBold", "Inter", "Iosevka", "Icons"...
|
||
tr.begin(scale, true, false);
|
||
double w = tr.getWidth("text", false);
|
||
double h = tr.getHeight(false);
|
||
tr.render("text", x, y, new RenderColor(0xFFFFFFFF), false);
|
||
tr.end();
|
||
```
|
||
Универсальная запись «в одном блоке»:
|
||
```java
|
||
textRenderer.begin(scale, true, false);
|
||
textRenderer.render("FPS", x, y, new RenderColor(argb), false);
|
||
textRenderer.end();
|
||
```
|
||
`TextRenderer.get()` — текущий рендерер (передаётся в хук). `HudScale.scale(screenW, screenH)`
|
||
даёт масштаб для адаптивного HUD (см. `Fps`).
|
||
|
||
### Иконки / текстуры
|
||
Идентификаторы: `Identifier.fromNamespaceAndPath("lovisual", "textures/hud/elements/fps.png")`.
|
||
SVG лежат в `assets/lovisual/svg/`.
|
||
|
||
### Анимации
|
||
`AnimationUtility`: `approach(value, target, dt, speed)`, `fast(...)`, `lerp(...)`,
|
||
`easeOutCubic`, `easeInOutCubic`, `easeOutBack`, `blink(ms)`, `deltaTime()`, `snap(...)`.
|
||
|
||
### Хелперы
|
||
- `HudRenderUtil`: `setAlpha(argb, a)`, `mixColor(a, b, t)`, `glassBackground()`,
|
||
`drawLiquidGlass(...)`, `animateVisibility(...)`, `visibilityScale(...)`.
|
||
- `HudGlobalConfig.get()`: `getFontSize()`, `getBlurRadius()` и т.д.
|
||
- Тема: `Theme.theme()` (статический импорт `dev.loki.lovisual.features.theme.Theme.theme`)
|
||
→ `Themes.Theme` c полями `windowBg, windowHeader, windowStroke, surface, surfaceHover,
|
||
cardEnabled, cardDisabled, textPrimary, textMuted, accent, accentSoft, strokeSoft`
|
||
(всё `int` ARGB). См. `Fps.updatePalette()`.
|
||
|
||
---
|
||
|
||
## 7. 3D-рендер (мир)
|
||
|
||
### Фазы
|
||
Верните не-`NONE` фазу из `getWorldPhase()`:
|
||
```java
|
||
WorldPhase: BEFORE_ENTITIES, AFTER_ENTITIES, BEFORE_TRANSLUCENT, END_MAIN, AFTER_POST_PROCESS
|
||
```
|
||
|
||
### Renderer3D — простой путь
|
||
В хуке `onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta)`:
|
||
|
||
```java
|
||
renderer.begin();
|
||
renderer.line(x1, y1, z1, x2, y2, z2, r, g, b, a); // линия
|
||
renderer.quad(x1,y1,z1, x2,y2,z2, x3,y3,z3, x4,y4,z4); // квад
|
||
renderer.triangle(...);
|
||
renderer.render(new PoseStack()); // в конце
|
||
```
|
||
Пример: `Tracers.onRenderWorldEngine` (линии от камеры к игрокам).
|
||
|
||
### MeshBuilder + RenderPipeline — продвинутый путь
|
||
```java
|
||
MeshBuilder mesh = new MeshBuilder(LoVisualRenderPipelines.UI_TEXTURED_ADDITIVE);
|
||
mesh.begin();
|
||
mesh.ensureQuadCapacity();
|
||
int i1 = mesh.vec2(x, y).vec2(u, v).color(r, g, b, a).next();
|
||
int i2 = mesh.vec2(...).vec2(...).color(...).next();
|
||
mesh.quad(i1, i2, i3, i4);
|
||
mesh.end();
|
||
```
|
||
Готовые пайплайны в `LoVisualRenderPipelines` (фрагмент):
|
||
```
|
||
UI_COLORED, UI_COLORED_LINES, UI_TEXTURED, UI_TEXTURED_ADDITIVE, UI_TEXT, UI_TEXT_MSDF,
|
||
UI_SVG_MSDF, UI_BLUR, WORLD_COLORED, WORLD_COLORED_LINES, WORLD_COLORED_DEPTH,
|
||
WORLD_TEXTURED, WORLD_TEXTURED_DEPTH, WORLD_TEXT, WORLD_TEXT_MSDF, WORLD_DECAL_SDF,
|
||
WORLD_COLORED_LINES_DEPTH, WORLD_WIDE_COLORED_LINES, ...HAND_*, SHADER_ESP_*, POST_FX...
|
||
```
|
||
Отправить на отрисовку (2D-стрелки из `Tracers`):
|
||
```java
|
||
MeshRenderer.begin()
|
||
.attachments(mc.gameRenderer.mainRenderTarget())
|
||
.pipeline(LoVisualRenderPipelines.UI_TEXTURED_ADDITIVE)
|
||
.mesh(mesh).transform(matrix4f)
|
||
.sampler("u_Texture", view, sampler)
|
||
.end();
|
||
```
|
||
`Renderer3D.Cull` — утилиты отсечения: `isInFrustum(...)`, `isSectionVisible(...)`, `isInFront(...)`.
|
||
|
||
---
|
||
|
||
## 8. Полезные утилиты (`util/`)
|
||
|
||
| Пакет / класс | Что даёт |
|
||
|---------------|----------|
|
||
| `util.target.TargetManager` | текущая цель: `getTarget()`, `getTarget(true/false)`, `onAttack()`, `setPredictionTarget()`, `Source` |
|
||
| `util.pvp.PvpTracker`, `PvpTargetTracker` | статистика PvP / отслеживание цели |
|
||
| `util.aiming.RotationManager` | плавные/снап-ротации |
|
||
| `util.combat.VulcanReachController` | клампинг дистанции (режим античита) |
|
||
| `util.time.GameClock` | `millis()` — игровое время (тики×50мс), вне мира — стенное |
|
||
| `util.time.TimerController` | таймеры |
|
||
| `util.input.KeyManager` | `wasPressed("func")`, `isHeld("func")`, `isComboHeldAllowScreen(...)` |
|
||
| `util.screen.ClientScreen` | текущий экран: `ClientScreen.current(mc)` / `.show(mc, screen)` |
|
||
| `util.text` | текст-хелперы |
|
||
| `util.entity.simulation.PlayerSimulationCache`, `BoatSimulationCache` | предсказание позиций |
|
||
| `util.raycast` | рейкасты |
|
||
| `util.item.OmniItemUtils` | утилиты предметов |
|
||
| `util.combat.protocol.CombatProtocolHeuristics` | эвристики античита |
|
||
| `util.logging.DebugLog` | `DebugLog.info/warn/error/config(...)` (printf-style) |
|
||
| `util.resources.RenderResourceReadiness` | готовность рендер-ресурсов |
|
||
| `features.relations.CategoryService` | `isFriend(Player)`, `getColor(Player)` — категории игроков |
|
||
| `util.FastFps` | `getFps()` — быстрый FPS без накладок |
|
||
|
||
Ленивый доступ к MC:
|
||
```java
|
||
private final Minecraft mc = Minecraft.getInstance();
|
||
```
|
||
(поле, но это ок — поле инициализируется при создании модуля, а модули создаются
|
||
в рантайме; правило «никакого `static final Minecraft` в полях»).
|
||
|
||
---
|
||
|
||
## 9. HUD-элементы
|
||
|
||
### Draggable (перетаскиваемый) — пример `Fps`
|
||
```java
|
||
@HudElementInfo(id = "fps", displayName = "FPS", enabledByDefault = true, order = 120)
|
||
public final class Fps extends DraggableHudElement {
|
||
// настройки — прямо поля:
|
||
private final NumberValue<Double> scale = num("fps_scale", 2.37, 0.5, 5.0);
|
||
private final RGBColorValue iconColor = visibleWhen(colorNoAlpha("fps_icon_color", "#FFFFFF"), this::isCustomMode);
|
||
|
||
@Override
|
||
public void applyDefaultPosition(int screenW, int screenH) { this.x = 16f; this.y = 20f; }
|
||
|
||
@Override
|
||
public boolean usesEngineRenderer() { return true; }
|
||
|
||
@Override
|
||
public void renderEngine(Renderer2D renderer, TextRenderer textRenderer,
|
||
GuiGraphicsExtractor ctx, float tickDelta,
|
||
int screenW, int screenH) {
|
||
if (!preview && !isEnabled()) { width = 0f; height = 0f; return; }
|
||
// считаем width/height, рисуем через renderer + Fonts.renderer(...)
|
||
}
|
||
}
|
||
```
|
||
- Настройки объявляются как поля с хелперами (автоматом попадают в SettingDef).
|
||
- `defaultLayout(x, y, anchorX, anchorY)` / `defaultLinkedLayout(...)` — стартовое положение.
|
||
- `x, y, width, height` — позиция и размер (обновляйте width/height в renderEngine!).
|
||
- Регистрация автоматическая (сканирование по `@HudElementInfo`).
|
||
- Интерактив: `isMouseOverInteractive(mx, my)`, `onMouseClicked(mx, my, button)`.
|
||
|
||
### Nondraggable (статичный)
|
||
Классы в `features/gui/hud/nondraggable/impl/` (CustomBar, CustomHealthBar, CustomHotbar,
|
||
BetterTooltips, BetterButtons, DynamicIsland). Наследуют `BaseHudElement`, рендер через
|
||
`renderEngine(...)` + `getRenderSpace()`/`getHudPhase()`.
|
||
|
||
---
|
||
|
||
## 10. Команды
|
||
|
||
Интерфейс `ClientCommand` + аннотация `@CommandInfo`, регистрация — вручную в `CommandManager`.
|
||
|
||
Префикс команд в чате — **`%`** (старый `@` тоже принимается). Источник истины —
|
||
`CommandPrefix` (`CHAR`/`STRING`/`isCommandLike`/`strip`). Автокомплит: имена команд +
|
||
аргументы из `suggest()`, а если команда не дала вариантов — подсказка-usage через
|
||
`UsageHints` (например `%toggle <module>`).
|
||
|
||
```java
|
||
@CommandInfo(
|
||
id = "toggle",
|
||
aliases = "t",
|
||
usage = "%toggle <module> [on|off|toggle]",
|
||
descriptionKey = "command.toggle.description"
|
||
)
|
||
public final class ToggleCommand implements ClientCommand {
|
||
@Override
|
||
public boolean execute(CommandContext ctx) {
|
||
// ctx.arg(0), ctx.arg(1), ...
|
||
CommandOutput.success("Done");
|
||
return true;
|
||
}
|
||
|
||
@Override
|
||
public List<String> suggest(CommandContext ctx, int argIndex, String token) {
|
||
return List.of();
|
||
}
|
||
}
|
||
```
|
||
- `CommandContext(mc, raw, name, args)` — `arg(i)` возвращает null если нет.
|
||
- `CommandOutput` — `success/warning/error/send(String)` с префиксом `[LoVisual]`.
|
||
- `CommandManager.init()` регистрирует команды из `impl/` (через ClassGraph, как модули).
|
||
|
||
---
|
||
|
||
## 11. API для аддонов (`api/v0/`)
|
||
|
||
- `LoVisualAddon` — entrypoint `"lovisual:addon"`: `onConfigureModules`,
|
||
`onInitialize(LoVisualAddonContext)`, `onClientReady`, `onShutdown`.
|
||
- `LoVisualModuleExtension` — расширение модуля (`ModuleExtensionContext`),
|
||
перехватывает `beforeEnable/afterEnable/beforeTick/...` (см. `ModuleExtensionManager`).
|
||
- `LoVisualClientApi.get()` — `modules()`, `module(id)`, `isModuleEnabled(id)`, `setModuleEnabled`.
|
||
- `render/` — `LoVisualRenderCallback` + `LoVisualRenderStage`
|
||
(HUD_RAW, HUD_SCALED, HUD_LOGICAL, WORLD_*, SCREEN_*), `LoVisualPostProcessCallback`,
|
||
`LoVisualUniforms`, `LoVisualRenderPipelineBuilder`.
|
||
- `clickgui/` — `LoVisualClickGuiSection`, `LoVisualClickGuiRenderContext` — свои секции в кликгую.
|
||
|
||
---
|
||
|
||
## 12. i18n (языковые ключи)
|
||
|
||
Файлы: `src/main/resources/assets/lovisual/lang/en_us.json`, `ru_ru.json`.
|
||
|
||
Соглашения по ключам:
|
||
- Модуль: `module.<id>`, `module.<id>.name`, `module.<id>.description`,
|
||
настройка: `module.<id>.setting.<setting_id>`, опции: `module.<id>.option.<option>`.
|
||
- HUD draggable: `hud.draggable.<id>` / `setting.draggable.<id>.<setting>`.
|
||
- HUD static: `hud.static.<id>`.
|
||
- Команды: `command.<id>.description`.
|
||
- Общие схемы: `CommonSettingSchemas` указывают `commonI18nKeys` (например `combat.range`)
|
||
— они ищутся в lang-файлах с префиксом `setting.common.<key>` (проверьте по `CommonKey`).
|
||
|
||
`I18n.get("key")` — получение перевода. Если ключа нет — возвращается сам ключ.
|
||
В `Module.getDisplayName()`/`getDescription()` fallback на `displayName`/`description`.
|
||
|
||
---
|
||
|
||
## 13. Миксины и accessors
|
||
|
||
- Все миксины в `mixins/` (`@Mixin(...)`). Пакеты: `accessors`, `iris`, `sodium`, `moreculling`,
|
||
`xaero`, `security`.
|
||
- Accessors в `mixins/accessors/` — примеры: `MinecraftAccessor`, `LocalPlayerAccessor`,
|
||
`PlayerInventoryAccessor`, `LivingEntityAccessor`, `EntityAccessor`, `ItemCooldownManagerAccessor`.
|
||
- Реестр: `resources/lovisual.mixins.json`. `LoVisualMixinPlugin` — условная загрузка.
|
||
- Правило: «никаких мёртвых миксинов» — добавляйте миксин только если реально нужен.
|
||
- `mixininterface/` — интерфейсы-протуберанцы для доступа к данным из миксинов (`IGuiGraphics`, `IEntity`).
|
||
|
||
---
|
||
|
||
## 14. Чек-лист нового модуля
|
||
|
||
1. `@ModuleInfo` на классе в `features/module/modules/<категория>/`.
|
||
2. Унаследовать `Module`, переопределить `onEnable/onDisable/onTick` и т.д.
|
||
3. Настройки — поля с хелперами (`num`, `bool`, `mode`, `color`, `visibleWhen`…).
|
||
4. Если нужен рендер — вернуть `getHudPhase()`/`getWorldPhase()` и реализовать
|
||
`onRenderHudEngine`/`onRenderWorldEngine`.
|
||
5. Если нужны события — методы с `@EventHandler`.
|
||
6. `i18n`: добавить `module.<id>.name/description` и ключи настроек в `en_us.json`/`ru_ru.json`.
|
||
7. Проверки: `./gradlew build` (сборка), `./gradlew test` (тесты), для чистой логики — JUnit.
|
||
8. Файл ≤200 строк — иначе вынести логику в хелпер-классы в том же пакете.
|
||
|
||
**Не делайте:**
|
||
- `static final Minecraft` в полях → только ленивый `Minecraft.getInstance()`.
|
||
- `System.currentTimeMillis()` в модулях → `GameClock.millis()`.
|
||
- this-escape в конструкторе, `#[allow(dead_code)]`-аналогов (мёртвый код удаляется),
|
||
мёртвые миксины.
|