- refactor(mixins): разбивка render/entity (crystal/item) и render/level/particles — регресс 8.5.1 - refactor(structure): разбивка 10 папок >4 файлов — регресс 8.5.1 после Фаз 7/10 - refactor(ui): OrderedUiBatcher 944 → 188 + 8 хелперов ≤200 (8.5.2, гейт 9.2) - refactor(text): MsdfFont 613 → 163 + atlas/ (Atlas,AtlasLoader,Json,Glyph) + GlyphDrawer 179 (8.5.2, гейт 9.2) - refactor(text): CustomTextRenderer 540 → 235 + CustomFontSet 142 + GradientTexts 95 + HorizontalFade 77 (8.5.2, гейт 9.2) - refactor(buttons): BetterButtons 1033 → 388 + 7 хелперов ≤200 (8.5.2, гейт 9.2) - refactor(inventory): удалить InventorySwap 1038 + ShitDropper → ShitESP (Вариант A) - fix(shitesp): цвет через color() helper как у ESP/Chams (крутой пиккер) - fix(blockesp): камень→спавнер подсвечивается сразу, docs: DEV_GUIDE + DEVELOPMENT → один гайд - docs(dev-guide): дополнен §18 современными правилами (web-поиск 2025-2026) - chore(quality): PMD 7.27.0 + Checkstyle 14.1.0 + SpotBugs 6.5.11 + обновы зависимостей - refactor(svg): SvgMeshBackend 1020 → 611 + SvgParser 181 + SvgGeometry 173 (8.5.2) - refactor(svg): SvgMeshBackend 611 → 267 + SvgTexture 223 + SvgRender 35 + SvgCache 25 (8.5.2) - refactor(theme): Themes 1012 → 562 + 3×163 пресета (8.5.2) - refactor(particles): WorldParticles 999 → 821 + geometry 93 + sphere 67 (8.5.2)
20 KiB
LoVisual Mod — Dev Guide (Единый справочник)
Цель: быстро добавить модуль / HUD-элемент / команду / миксин, не перекапывая весь код. Сводка по реальному коду, объединено из DEV_GUIDE.md + DEVELOPMENT.md.
- Пакеты:
dev.loki.lovisual.* - Стек: Java 25 · Fabric Loader 0.19.3 · Minecraft 26.2 (Fabric Loom), Lombok
- Правила — нарушение = откат (см. §2)
1. Сборка и запуск
cd combatant-client-26.2
./gradlew build # jar: build/libs/lovisual-<ver>.jar
./gradlew test # юнит-тесты (~320)
./gradlew runClient # dev-запуск Minecraft с модом
./gradlew runClient --rerun-tasks # если кэш врёт
Готовый jar для теста в реальном инстансе:
cp build/libs/lovisual-*.jar /storage/prismlauncher/instances/26.2/minecraft/mods/
Ресурсы: только настоящие PNG (сигнатура 89 50 4E 47). JPEG, переименованный в .png, уронит Bad PNG Signature при загрузке.
2. Правила проекта (ОБЯЗАТЕЛЬНЫЕ)
- KISS / DRY / SOLID — просто, без оверинжиниринга.
- ≤200 строк на файл. Фасад-делегат (
Renderer2D,Module≈1181) — исключение; больше логики → выносиpackage-privateхелперы в тот же пакет. - ≤4 файла на папку — больше → новая подпапка по смыслу.
- Никакого статика от Minecraft:
Minecraft.getInstance()только локальной переменной в месте использования, никогда в поле.INSTANCE— только для сервисов без lifecycle. - Lifecycle гейты:
Lifecycle.isActive()/canRunRender()/canRunHud()— доactivate()события/тики не работают;EventBus.post()сам гейтит. - Никаких this-escape — конструктор не публикует
this. - Никаких мёртвых миксинов — миксин только если реально нужен;
cancellable=trueтолько при реальномcancel. dev.loki.lovisual.mixins.*— ТОЛЬКО классы с@Mixin. Обычный хелпер там крашитIllegalClassLoadError(Knot). Хелперы — вutil/или пакет фичи (см.HandDynamics).- Без пустышек — файл без реализации не создаётся.
- Каждый этап компилируется:
./gradlew buildперед коммитом. - Тесты обязательны для новой чистой логики (JUnit 5, без моков Minecraft — выноси математику в
staticи тесть). - Референсы (
ref/) — чужой код (SoupVisuals SOUP-1.0, Delta и др.): только идеи, пере-реализация под наши правила, никакого копирования. System.currentTimeMillis()в модулях →GameClock.millis(),static final Minecraftв полях — баг.
3. Карта пакетов
dev.loki.lovisual
├── LoVisual.java — ClientModInitializer: старт (тики, рендер-фазы)
├── Lifecycle.java — INIT → ACTIVE → SHUTDOWN; гейты
├── events/ — EventBus (@EventHandler, приоритеты, гейт по Module)
│ └── impl/ — GameTickEvent, PacketEvent, AttackEntityEvent, LightmapModifyEvent...
├── config/ — MainConfig, SettingDef, .lvcfg (legacy .cbcfg читается)
│ ├── values/ — ConfigValue: bool/num/mode/color/set/... (есть тесты)
│ └── profile/ — профили (collect/apply/diff/binary codec)
├── features/
│ ├── module/ — СИСТЕМА МОДУЛЕЙ (главное)
│ │ ├── Module.java — базовый класс
│ │ ├── ModuleInfo — @annotation
│ │ ├── ModuleManager — реестр + диспетчер фаз
│ │ ├── ModuleAutoLoader — ClassGraph по @ModuleInfo (no-args ctor → postInit)
│ │ ├── Modules — Modules.get(Класс.class) / enabled(id)
│ │ ├── ModuleCategory — COMBAT/MOVEMENT/PLAYER/VISUALS/MISC (легит-направление: читы удаляем)
│ │ ├── HudPhase / WorldPhase — фазы рендера
│ │ └── modules/{combat,player,visuals,misc}/
│ ├── command/ — @CommandInfo + ClientCommand (ClassGraph, префикс %)
│ ├── gui/
│ │ ├── clickgui/ — экран настроек (Setting → виджет)
│ │ └── hud/ — HUD-элементы
│ │ ├── draggable/ — перетаскиваемые (Fps, Coords, ModuleList, TargetHud…)
│ │ └── nondraggable/ — статичные (CustomBar, BetterChat, BetterButtons, DynamicIsland…)
│ ├── relations/ — друзья/враги (PlayerRelations, CategoryService)
│ ├── security/ — BackdoorProtection (SSRF/translate)
│ └── theme/ — Themes.Theme (windowBg/header/surface/accent...), Theme.theme()
├── render/engine/ — Renderer2D/3D, пайплайны, шрифты, MeshBuilder
│ ├── renderer/Renderer2D — 2D (quad/roundedRect/circle/line/item/svg/effect)
│ ├── renderer/Renderer3D — 3D (line/quad/triangle)
│ ├── text/ — Fonts.renderer("Onest", Regular), TextRenderer
│ ├── animation/AnimationUtility
│ ├── pipeline/LoVisualRenderPipelines — UI_COLORED, WORLD_COLORED, HAND_*, SHADER_ESP_*, POST_FX...
│ └── uniform/MeshBuilder
├── mixins/ — @Mixin-классы (mixins.json, LoVisualMixinPlugin)
│ └── mixins/accessors/ — MinecraftAccessor, PlayerInventoryAccessor...
│ └── mixininterface/ — IGuiGraphics, IEntity...
├── addon/ + api/v0/ — система аддонов (LoVisualAddon, LoVisualModuleExtension, RenderCallback)
└── util/ — хелперы: target, pvp, aiming, time/GameClock, input/KeyManager, screen, text, raycast...
4. Жизненный цикл и старт
В LoVisual.onInitializeClient() по порядку:
LoVisualRenderEngineBootstrap.init(),PostProcessManager.register(...)MainConfig.get(),AccountConfig.get(),CommandManager.init(),MediaSessionServiceModuleAutoLoader.load("dev.loki.lovisual.features.module.modules")— ищет@ModuleInfo,new+postInit()ModuleManager.loadAllModuleConfigs()— применяет.lvcfg, включаетenabledHudElements.init(),StaticHudElementBootstrap.init()Events.BUS.register(...)— глобальные сервисы (RotationManager,PvpTracker...)Lifecycle.activate()— после этого тики/события/рендер работают
Вывод: новый модуль — просто класс с @ModuleInfo в ...modules.<категория>; регистрировать руками не нужно.
5. Модуль — быстрый старт
package dev.loki.lovisual.features.module.modules.misc;
import dev.loki.lovisual.config.values.primitive.NumberValue;
import dev.loki.lovisual.events.meta.EventHandler;
import dev.loki.lovisual.events.impl.render.GameTickEvent;
import dev.loki.lovisual.features.module.core.Module;
import dev.loki.lovisual.features.module.core.ModuleCategory;
import dev.loki.lovisual.features.module.core.ModuleInfo;
@ModuleInfo(id = "my_feature", displayName = "My Feature", category = ModuleCategory.MISC)
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;
Minecraft mc = Minecraft.getInstance();
if (mc.level == null) return;
}
@EventHandler private void onTick(GameTickEvent e) {}
}
@ModuleInfo: id (snake, уникальный, ключ конфига/команды), displayName, aliases, category, enabledByDefault, description (i18n).
Автогенерируемые поля Module: enabled/bind/activation_source/show_in_module_list (postInit → SettingFactory.fromDefs → ModuleManager).
Хуки: onEnable/onDisable/onTick/onFrame/onKey/onRender2D/onRenderHudEngine/onRenderWorldEngine, getHudPhase()/getWorldPhase() (по умолч. NONE — не рендерится). Управление: isEnabled()/toggle()/setEnabled(), isAvailable(), Modules.get(Freecam.class).
6. Настройки (ConfigValue → SettingDef)
База ConfigValue<T>: get()/set()/getName()/toJson()/fromJson().
Хелперы в Module/BaseHudElement (protected final):
| Хелпер | Тип | Пример |
|---|---|---|
bool(name, def) |
BooleanValue |
bool("my_on", true) |
num(name, def, min, max) |
NumberValue |
num("my_num", 3, 0, 20) |
mode(name, def, opts...) |
ModeValue |
mode("my_mode", "A", "A","B") |
enumMode(name, defEnum) |
EnumValue |
см. Reach |
color(name, "#AARRGGBB") |
RGBAColorValue |
color("my_bg", "#F7343434") |
text/textList/itemList/group/bind/action |
StringValue/SetValue/ItemIdSetValue/... |
itemList("my_items") |
*Common варианты (boolCommon/numCommon/enumCommon) берут схему из CommonSettingSchemas.
Условность: visibleWhen(numCommon(...), () -> cond), appliesWhen(setting, () -> mc.player!=null, "Только в мире"). Экшены: action("climb","G", PRESS) → isActionPressedOnce("climb").
HUD-элементы объявляют настройки в defineSettings(List<SettingDef>); модули — полями (автоматом). Виджеты SettingDef.Kind: BOOLEAN, NUMBER, MODE, COLOR, TEXT, TEXT_LIST, GROUP, BIND... (@DisableSettingI18n).
7. HUD-элементы
Draggable (hud/draggable/impl/, @HudElementInfo(id, displayName, enabledByDefault, order), DraggableHudElement): applyDefaultPosition(w,h), renderEngine(Renderer2D, TextRenderer, GuiGraphicsExtractor, tickDelta, w,h), usesEngineRenderer()=true, x/y/width/height.
Static (hud/nondraggable/impl/, @HudElementRegister(order), AbstractHudElement super(id,"Title",defaultEnabled) + INSTANCE): для вкладки UI добавить new StaticHudCard(...) в MenuSettingsResolver.STATIC_UI_CARDS. Тема: Theme.theme().accent()/windowBg(), HudRenderUtil.setAlpha/mixColor, текст Fonts.renderer(...).
8. Команды
ClientCommand + @CommandInfo(id, aliases, usage, descriptionKey), регистрация в CommandManager (ClassGraph). Префикс % (legacy @). CommandPrefix — источник истины; автокомплит suggest() + UsageHints.
@CommandInfo(id="toggle", aliases="t", usage="%toggle <module> [on|off]", descriptionKey="command.toggle.description")
public final class ToggleCommand implements ClientCommand {
public boolean execute(CommandContext ctx){ CommandOutput.success("Done"); return true; }
public List<String> suggest(CommandContext ctx,int i,String token){ return List.of(); }
}
CommandContext.arg(i), CommandOutput.success/warning/error.
9. События (EventBus)
Events.BUS.post(event) → @EventHandler(priority=N) (один аргумент-Event). Модули гейтятся isEnabled(). Event имеет cancel().
events/impl/ : GameTickEvent, PacketEvent, AttackEntityEvent, EventBreakBlock, LightmapModifyEvent, PlayerMoveEvent, RenderPrewarmCollectEvent...
@EventHandler private void onTick(GameTickEvent e){ }
10. 2D-рендер (HUD)
Фазы getHudPhase(): FIRST, BEFORE_MISC_OVERLAYS, AFTER_MISC_OVERLAYS, AFTER_BOSS_BAR, BEFORE_DEMO_TIMER, BEFORE_CHAT, AFTER_SUBTITLES, LAST → onRenderHudEngine.
Renderer2D (фасад, begin()/render() делает диспетчер): quad/roundedRect/roundedRectGradient/circle/line/item/svg/effect. Текст: Fonts.renderer("Onest", Regular, textRenderer).begin(scale,true,false) → getWidth/render → end() или textRenderer.begin.... Анимации AnimationUtility, хелперы HudRenderUtil/HudGlobalConfig/Theme.theme().
11. 3D-рендер (мир)
Фазы getWorldPhase(): BEFORE_ENTITIES, AFTER_ENTITIES, BEFORE_TRANSLUCENT, END_MAIN, AFTER_POST_PROCESS → onRenderWorldEngine(Renderer3D, Renderer3D, tickDelta).
Простой путь: renderer.begin(); renderer.line(...); renderer.quad(...); renderer.render(new PoseStack()); (см. Tracers). Продвинутый: MeshBuilder + LoVisualRenderPipelines (UI_COLORED, WORLD_COLORED...). Renderer3D.Cull.isInFrustum.
12. Миксины и accessors
Все в mixins/ (@Mixin), реестр lovisual.mixins.json (LoVisualMixinPlugin). Accessors в mixins/accessors/ (PlayerInventoryAccessor...), mixininterface/ (IGuiGraphics). Правило: только @Mixin в mixins.*.
13. Конфиг, профили и i18n
Модули/HUD → <game>/lovisual/*.lvcfg (legacy .cbcfg читается). Профили config/profile/ (ClickGUI → Configs).
i18n assets/lovisual/lang/{en_us,ru_ru}.json:
- модуль
module.<id>.description,setting.<id>.<setting_id>,option.<Option> - draggable
hud.draggable.<id>, staticsetting.hud_element.<id>.<key>, командыcommand.<id>.description, общиеsetting.common.<key>
Пропущенный ключ ловит MissingI18nReporter в dev.
14. Полезные утилиты (util/)
TargetManager, PvpTracker, RotationManager, GameClock.millis(), TimerController, KeyManager, ClientScreen, PlayerSimulationCache, OmniItemUtils, DebugLog, RenderResourceReadiness, CategoryService, FastFps...
Ленивый Minecraft.getInstance() — только локально.
15. API для аддонов (api/v0/)
LoVisualAddon (onConfigureModules/onInitialize/onClientReady/onShutdown), LoVisualModuleExtension (beforeEnable/afterEnable/beforeTick...), LoVisualClientApi.get().modules()/isModuleEnabled(), LoVisualRenderCallback (HUD_RAW, WORLD_...), LoVisualClickGuiSection.
16. Тесты
src/test/java/ зеркалит main, только чистая логика (без MC рантайма): util/text/NameProtectorTest, config/values/*Test, config/profile/ConfigProfileValueCodecTest, events/EventBusTest (нужен Lifecycle.activate()).
Выноси static int tint(...) / record Layout(...) и тесть.
17. Чек-лист нового модуля / перед коммитом
@ModuleInfoвmodules/<категория>/,extends Module,onEnable/onTick+@EventHandlerесли нужно- Настройки хелперами (
num/bool/color/visibleWhen),i18nв обаlangфайла - Рендер: верни
getHudPhase()/getWorldPhase()+onRenderHudEngine/onRenderWorldEngine ./gradlew buildзелёный,./gradlew testзелёный, файл ≤200, папка ≤4, нетstatic Minecraftв полях, нетSystem.currentTimeMillis()→GameClock, нет пустышек/мёртвых миксинов,mixinтолько@MixinTODO.mdотмечен,ref/не копировался
Не делай: static final Minecraft в полях, this-escape, мёртвый код, копипаст чужого ref/.
18. Современные рекомендации (2025-2026, web-поиск)
Clean Code — без догматизма
- YAGNI «You Aren't Gonna Need It» — не пиши на будущее; KISS > OCP в быстро меняющемся коде. Принцип — эвристика, не закон.
- DRY ловушка: дублирование дешевле неверной абстракции (Sandi Metz). Выноси только семантически одинаковое, синтаксически похожее
for-циклы — не повод для хелпера → лишняя связность. - SOLID критика: ортодоксальный SOLID плодит
Service → RepoInterface → RepoImpl → QueryBuilder, низкая связность, но нечитаемо и 15× медленнее (Casey Muratori «Clean Code, Horrible Performance» 2023: замена полиморфного OCP наswitchдала 15×). Применяй умеренно, только где абстракция реально нужна. - Читаемость: без глубокой вложенности — guard-clauses
if (user==null) return;, без магических чисел →static final MIN_DRINKING_AGE=21, говорящие именаcustomerOrderHistoryList>list, комментарии почему, а не что, консистентное форматирование, DI вместоnew.
Java 21/25 (LTS)
if (obj instanceof String s)/record Point(int x,int y)+if (o instanceof Point(int x,int y))/switchс pattern-matching иcase null,sealed interface Command permits Get,Put,Delete— меньше кастов, безопаснее API.- Virtual threads — не «pixie dust»; дают выигрыш на I/O-bound, но не на CPU-bound. ScopedValue (финал JDK25) + Structured Concurrency (preview JDK25) лучше
ThreadLocalпо памяти/синхронизации, особенно с виртуальными потоками. - JDK25 perf:
String.hash@Stable→ константная свёрткаMap.of("constant",...),Stable Valuepreview,ForkJoinPoolтеперьScheduledExecutorService(быстрее отмена timeout), ML-KEM/ML-DSA квантостойкая криптография ×2 на AVX-512/ARM,HKDFи др. — просто обнови JDK, код ускорится без изменений.
Secure Coding (OWASP Java Cheat Sheet)
- Injection: только параметризация (
PreparedStatement, JPQLsetParameter), не конкатенация строк; дляisReachableвместоRuntime.exec("ping "+host). - XSS:
OWASP Java HTML Sanitizer+OWASP Java Encoder(Encode.forHtml) при выводе к пользователю. - Крипто: никогда не пиши своё — используй JCA/JCE,
AES-GCM+ECDH, каждый раз новыйnonce, секреты — через cloud secret manager, ключи ротируй. СлабыеMD5/DESзапрещеныJEP 565в Java 21.
Fabric / Mixin (2025-2026)
- Порядок предпочтения:
@Inject(callback, стекается) >MixinExtras @WrapOperation/@ModifyExpressionValue>@Redirect/@ModifyConstant(только один на таргет → конфликт) >@Overwrite(почти никогда). @Unique modid$field, абстрактный mixin-класс (не надо имплементить интерфейсы родителя),@Shadowдля доступа,(Target)(Object)thisдляthis, внутренние классыOuter$Inner.- Сеть: пакеты приходят на net-thread — парсь там, а доступ к
Minecraft/Levelтолько черезclient.execute/ task-queue на main thread. - Избегай
java.awt/javax.swing— вешает игру; не патчи Fabric API напрямую, ищиFabricBlockSettingsи т.п.
Источники: Medium 2025 DRY/KISS/YAGNI, Baeldung Clean Code, JavaCodeGeeks Dark Side SOLID, Oracle Inside Java 2025-2026, Fabric Wiki mixin_tips, OWASP Java Security Cheat Sheet.