chore(history): squash 13 commit(s) from 2026-09-08

- 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
This commit is contained in:
loki5512344 2026-09-08 23:46:29 +02:00
parent e1d7d19a83
commit 707820e418
167 changed files with 5034 additions and 345 deletions

43
TODO.md
View file

@ -27,4 +27,45 @@
- [x] Фаза 3.5: Удаление читерских модулей (легит-направление) - [x] Фаза 3.5: Удаление читерских модулей (легит-направление)
- [x] Фаза 4: Security (BackdoorProtection, StaffTracker) - [x] Фаза 4: Security (BackdoorProtection, StaffTracker)
- [x] Фаза 5: Нормализация модулей (ленивый Minecraft, GameClock) - [x] Фаза 5: Нормализация модулей (ленивый Minecraft, GameClock)
- [ ] Фаза 6: Тесты (EventBus, конфиг, утилиты) — см. `combatant-client-26.2/TODO.md` - [x] Фаза 6: Тесты (EventBus, конфиг, утилиты) — см. `combatant-client-26.2/TODO.md`
- [ ] Фаза 7: Новые модули по мотивам SoupVisuals — см. ниже
---
## Фаза 7: Новые модули — референс SoupVisuals
> Референс: `ref/soupvisuals-src/` — SoupVisuals 3.2.0 (MC 1.21.11, лицензия SOUP-1.0).
> Код референса НЕ копируем (чужая лицензия + декомпил) — только пере-реализация идей
> под наши правила (≤200 строк/файл, ≤4 файла/папку, без статики, тесты).
> Модули референса лежат в `ref/soupvisuals-src/padej/soup/implement/features/modules/`.
- [x] **Hitboxes** (visuals) — цветные хитбоксы сущностей, как у Soup: ТОЛЬКО рендер
контейнера с настройкой цвета/видимости, БЕЗ увеличения хитбокса (увеличение —
чит, удалён в Фазе 3.5). Референс: `visuals/Hitboxes.java`
- [x] **Watermark** (HUD, draggable) — обещан в README, в коде отсутствует
- [ ] **Gps** (world) — луч/маркер направления к точке (у Soup ~400 стр.); связка
с Xaero-waypoints и координатами. Референс: `world/Gps.java`
- [x] **Light** (world) — кастомный цвет освещения (у Soup 41 стр.). Референс: `world/Light.java`
- [ ] **HitBubbles** (particles) — пузыри при ударах по игрокам (у Soup 323 стр.).
Референс: `particles/HitBubbles.java`
- [ ] **AmbientParticles + FireFlyParticle** (particles) — атмосферные частицы мира /
светлячки (у Soup 775 + 241 стр.). Референс: `particles/AmbientParticles.java`,
`particles/types/FireFlyParticle.java`
- [x] **NameProtect** (misc) — подмена своего ника и ников друзей в тексте чата/скорборда
(у Soup 42 стр.). Референс: `other/NameProtect.java`
- [ ] **EndCrystal** (visuals) — улучшенный рендер энд-кристаллов (у Soup 217 стр.).
Референс: `other/EndCrystal.java`
- [ ] **Trinket 2.0** — апгрейд нашего CosmeticDisplay до уровня Soup Trinket:
иконки-спутники (Atom/Ball/Cat/Donut/Flame/Flower/Gem/Ghost/MCTiers), трейл
(цвет sync/custom, длина, толщина), звуки (типы + громкость), частицы
(у Soup 315 стр.). Референс: `other/Trinket.java`
- [ ] **Balancer** (mctiers) — тиры игроков с mctiers.info (у Soup отдельная категория
MCTiers). Референс: `mctiers/Balancer.java`
- [ ] **HandsShader: новые шейдеры руки** — у Soup 10 режимов (Solid, Chroma, Balatro,
Smoke, Stripes, Glow, Glass, Invert, Snow) + dual-режим с blend-режимами
(Mix/Add/Multiply/Screen/Overlay/Difference); шейдеры лежат в
`ref/soupvisuals-src/assets/soupapi/shaders/hand/`. Пере-реализовать недостающие
и добавить к нашим (BackSword/Chams hand shaders уже есть dual — сверить набор).
Референс: `visuals/HandsShader.java`
- [x] **Фикс ChinaHat** — текущая геометрия кривая (смахивает на самбреро/зонт);
переделать модель в нормальную конусную шляпу по референсу Soup
(`visuals/ChinaHat.java` + `ChinaHatModel.java`).

View file

@ -0,0 +1,200 @@
# LoVisual — руководство разработчика
Как устроен проект, как добавлять фичи и какие правила обязательны.
Версии: **Java 25 · Fabric Loader 0.19.3 · Minecraft 26.2** (Loom).
---
## 1. Сборка и запуск
```bash
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 для теста в реальной сборке — скопировать в инстанс:
```bash
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`) — исключение.
Больше логики — выноси package-private хелперы в тот же пакет.
- **≤ 4 файла на папку** — больше, дели папку по смыслу (подпапка).
- **Никакого статики от Minecraft**: `Minecraft.getInstance()` — только локальной
переменной в месте использования, НИКОГДА в поле (`private final Minecraft mc = ...` — баг).
Функциональный синглтон (`INSTANCE`) — только для сервисов без состояния жизненного цикла.
- **`Lifecycle.isActive()/canRunHud()` — гейты** для рендера/событий; `EventBus.post()`
сам игнорирует события до activation.
- **Никаких this-escape** — конструктор не публикует `this`.
- **Никаких мёртвых миксинов** — миксин только если реально нужен;
`cancellable=true` только при реальном cancel.
- **`dev.loki.lovisual.mixins.*` — ТОЛЬКО классы с `@Mixin`.** Обычный helper-класс
внутри mixin-пакета крашит игру: Knot не даёт transformed-классу обратиться к нему
(`IllegalClassLoadError`). Хелперы — в `util/` или пакет фичи (см. `HandDynamics`).
- **Без пустышек** — файл без реализации не создаётся.
- **Каждый этап компилируется**: `./gradlew build` перед коммитом.
- **Тесты обязательны** для новой чистой логики (JUnit 5, без моков Minecraft —
выноси математику/парсинг в чистые статические методы и тесть их).
- **Референсы (`ref/`)** — чужой код (SoupVisuals SOUP-1.0, Delta и др.):
**только идеи**, пере-реализация под наши правила, никакого копирования.
---
## 3. Карта пакетов
```
dev.loki.lovisual
├── LoVisual.java — инициализация ClientModInitializer
├── Lifecycle.java — INIT → ACTIVE → SHUTDOWN, гейты canRunHud/Render
├── events/ — EventBus (@EventHandler, приоритеты, гейт по Module)
│ └── impl/ — конкретные события (Tick, RenderHud, Packet, Lightmap...)
├── config/ — MainConfig, SettingDef, сериализация (.lvcfg, legacy .cbcfg)
│ ├── values/ — ConfigValue: bool/num/mode/color/set/... (есть тесты)
│ └── profile/ — профили конфигов (collect/apply/diff/binary codec)
├── features/
│ ├── module/ — Module, ModuleCategory, ModuleInfo, ModuleManager,
│ │ └── modules/... — сами модули по категориям
│ ├── gui/ — ClickGUI, HUD (draggable + nondraggable), BetterChat
│ ├── command/ — чат-команды (@CommandInfo, ClassGraph-поиск)
│ ├── relations/ — друзья/враги/стаф (PlayerRelations, CategoryService)
│ └── security/ — AntiBot-фильтр, StaffTracker
├── render/engine/ — Renderer2D/3D, пайплайны, шрифты, MeshBuilder
├── mixins/ — @Mixin-классы (см. правило выше)
└── util/ — чистые утилиты (text/animation/time/...) — тут живут тесты
```
---
## 4. Как добавить МОДУЛЬ
Модули находятся в `features/module/modules/<категория>/`. Категории:
`COMBAT · MOVEMENT · PLAYER · VISUALS · MISC` (см. `ModuleCategory`).
Легит-направление: читерские модули (автоудары, полёты, speed...) **удаляем, не добавляем**.
Регистрация автоматическая: **ClassGraph сканирует `@ModuleInfo`** — ничего никуда
дописывать не надо. Пустой конструктор обязателен.
Шаблон:
```java
@ModuleInfo(id = "mymodule", displayName = "My Module", category = ModuleCategory.VISUALS)
public class MyModule extends Module {
private static final String SETTING_X = "x_amount";
private final NumberValue<Integer> amount = num("myModuleAmount", SETTING_X, 5, 1, 10);
@Override
public void onTick() {
Minecraft mc = Minecraft.getInstance(); // локально, не в поле!
if (mc.level == null) return;
// ...
}
@EventHandler
private void onRenderHud(RenderHudEvent event) { /* авто-подписка через EventBus */ }
}
```
Ключевые хуки `Module`: `onEnable/onDisable/onTick/onFrame`,
`onRenderHudEngine(...)`, `onRenderWorldEngine(...)`, `getWorldPhase()/getHudPhase()`.
Хелперы настроек сами объявляют `SettingDef`: `bool/num/mode/color/colorNoAlpha/
enumSetting/text/group/bind` + `visibleWhen(value, () -> cond)`.
Модуль-лист по умолчанию недоступен цели — смотри также `boolCommon`/`modeCommon`
для общих схем (`CommonSettingSchemas`).
Доступ к чужому модулю: `Modules.get(X.class)`, проверка состояния — `isEnabled()`.
Мировой рендер (боксы/линии) — через `Renderer3D.batch(WORLD_COLORED_LINES, DepthMode...)`
и MeshBuilder; готовые куски геометрии — `EspMeshBuilder.addOutlineBox` и т.п.
---
## 5. Как добавить HUD-элемент
Два вида, оба ищутся ClassGraph-ом автоматически:
**A. Draggable (перетаскиваемый, вкладка Settings → HUD):**
`features/gui/hud/draggable/impl/`, аннотация `@HudElementInfo(id, displayName,
enabledByDefault, order)`, базовый класс `DraggableHudElement`.
Обязательно: `applyDefaultPosition(screenW, screenH)`, рендер в `renderEngine(...)`,
`usesEngineRenderer() = true`. Позиция сохраняется через `layoutValue`.
**B. Static (фиксированный, вкладка Settings → UI):**
`features/gui/hud/nondraggable/impl/`, аннотация `@HudElementRegister(order=N)`,
базовый класс `AbstractHudElement`, конструктор `super(id, "Title", defaultEnabled)`,
синглтон `public static final X INSTANCE` (лоадер забирает его из статического поля).
Чтобы карточка появилась во вкладке **UI**, добавь `new StaticHudCard("<id>", "<Title>")`
в `STATIC_UI_CARDS` в `MenuSettingsResolver.java` (draggable-виджеты там не нужны —
они собираются из реестра сами).
Цвет из темы: `Theme.theme().accent()/windowBg()/textMuted()`; альфа —
`HudRenderUtil.setAlpha(argb, a)`; текст — `Fonts.renderer(name, type, fallback)`
(`begin(scale,...)` → `getWidth`/`render` → `end()`).
---
## 6. Как добавить СОБЫТИЕ / МИКСИН
1. Событие: наследуй `Event` (есть `cancel()`), положи в `events/impl/`.
2. Публикация: `Events.BUS.post(new MyEvent(...))` из миксина.
3. Миксин: класс с `@Mixin(Target.class)` в `mixins/` **и имя в `lovisual.mixins.json`**
(секции `mixins`/`client`). Миксин, которого нет в json, не применится.
4. Если миксин опциональный (зависит от чужого мода, напр. Sodium) — цель может
отсутствовать: Mixin напечатает WARN и пройдёт (см. `SodiumChunkMeshFormatsMixin`).
Такой WARN — норма, краш — нет.
5. Чистая логика из миксина — вобы в обычный класс вне `mixins/`-пакета (см. правило про `HandDynamics`).
---
## 7. Настройки, конфиг и i18n
- Конфиги модулей/HUD пишутся в `<game>/lovisual/` в формате **`.lvcfg`**
(legacy `.cbcfg` читается). Профили — `config/profile/` (кнопка в ClickGUI → Configs).
- Каждой настройке нужен i18n-ключ в **обоих** файлах
`src/main/resources/assets/lovisual/lang/{en_us,ru_ru}.json`:
- модуль: `module.<id>.description`, настройки `setting.<id>.<setting_id>`,
опции режима `setting.<id>.<setting_id>.option.<Option>`;
- draggable HUD: `hud.draggable.<id>` / `setting.draggable.<id>.<key>`;
- static HUD: `setting.hud_element.<id>.<key>` (см. Watermark, Swap Tooltip).
- `setting_id` — snake_case (`"show_ping"`); configName —camelCase с префиксом мода
(`"watermark_show_ping"`) — из него авто-выводится id.
- Пропущенный ключ ловит `MissingI18nReporter` в дев-окружении — проверяй консоль.
---
## 8. Тесты
`src/test/java/` зеркалит main. Пишутся **только на чистую логику** (без Minecraft-
рантайма): парсеры, математика, codec'ы, матчинги. Примеры:
- `util/text/NameProtectorTest`, `LegacyTextUtilTest` — парсинг/подмена,
- `config/values/*Test` — значения конфига,
- `config/profile/ConfigProfileValueCodecTest` — бинарный round-trip,
- `events/EventBusTest` — события (нужен `Lifecycle.activate()` в `@BeforeAll`).
Если фича завязана на Minecraft — выделяй статический pure-метод
(`static int tint(...)` / `static record Layout(...)`) и тесть его (см. `Light.tintAmbient`).
---
## 9. Чеклист перед коммитом
- [ ] `./gradlew build` зелёный, `./gradlew test` зелёный
- [ ] новых файлов >200 строк нет; папка не разрослась >4 файлов
- [ ] нет полей `Minecraft` / `getInstance()` в полях
- [ ] lang-ключи en_us + ru_ru добавлены для всех новых настроек
- [ ] тест на новую чистую логику есть
- [ ] в mixin-пакете только `@Mixin`-классы
- [ ] TODO.md обновлён (галка/заметка), если это этап плана
- [ ] в чужой код (`ref/`) не заглядывался копипаст

View file

@ -474,11 +474,16 @@ BetterTooltips, BetterButtons, DynamicIsland). Наследуют `BaseHudElemen
Интерфейс `ClientCommand` + аннотация `@CommandInfo`, регистрация — вручную в `CommandManager`. Интерфейс `ClientCommand` + аннотация `@CommandInfo`, регистрация — вручную в `CommandManager`.
Префикс команд в чате — **`%`** (старый `@` тоже принимается). Источник истины —
`CommandPrefix` (`CHAR`/`STRING`/`isCommandLike`/`strip`). Автокомплит: имена команд +
аргументы из `suggest()`, а если команда не дала вариантов — подсказка-usage через
`UsageHints` (например `%toggle <module>`).
```java ```java
@CommandInfo( @CommandInfo(
id = "toggle", id = "toggle",
aliases = "t", aliases = "t",
usage = "@toggle <module> [on|off|toggle]", usage = "%toggle <module> [on|off|toggle]",
descriptionKey = "command.toggle.description" descriptionKey = "command.toggle.description"
) )
public final class ToggleCommand implements ClientCommand { public final class ToggleCommand implements ClientCommand {

View file

@ -19,7 +19,7 @@ Developed and maintained by loki5512344.
### Overview ### Overview
LoVisual is an open-source utility and PvP client for Minecraft. It provides configurable combat, movement, visual, automation, HUD, and UI features with a custom ClickGUI, theme system, and addon API. LoVisual is an open-source legit utility client for Minecraft. It provides configurable visual, HUD, and UI features with a custom ClickGUI, theme system, and addon API.
LoVisual is developed and maintained by [loki5512344](https://github.com/loki5512344). The goal is to keep adding new features and maintain compatibility with the latest Minecraft versions. LoVisual is developed and maintained by [loki5512344](https://github.com/loki5512344). The goal is to keep adding new features and maintain compatibility with the latest Minecraft versions.
@ -34,14 +34,13 @@ LoVisual is developed and maintained by [loki5512344](https://github.com/loki551
| Feature | Description | | Feature | Description |
|---------|-------------| |---------|-------------|
| Combat | KillAura, AutoCrystal, AutoAnchor, AutoBed, AutoBow, Criticals, Reach, Velocity, Backtrack, and more | | Combat | ProjectilePuncher, Reach, RubberHand, SpearAssist, TPSSync, PvpCooldowns, NoFriendDamage, TargetESP, Predictions, DamageTint |
| Movement | Speed, Flight, ElytraFly, ElytraHelper, Sprint, Timer, Strafe, TargetStrafe, Scaffold, Parkour | | Visuals | ESP, NameTags, Chams, Crosshair, FullBright, MotionBlur, NoRender, Freecam, Hitboxes, Light, ChinaHat, Trinkets, and more |
| Visuals | ESP, NameTags, Chams, Tracers, Crosshair, Zoom, Freecam, FullBright, MotionBlur, NoRender | | Player | AutoTool, ClickPearl, FakeLag, NoDelay, LockSlot, AutoRespawn, DeathCoords, NoInteract |
| Player | AutoTool, AutoEat, AutoArmor, AutoTotem, Scaffold, ClickPearl, FakeLag, XCarry | | Misc | BetterMinecraft, FakePlayer, HitSounds, NameProtect, MessageFilter, NoSound, DefineTarget |
| Misc | BetterMinecraft, FakePlayer, HitSounds, Panic, NoSound | | HUD | Draggable & static elements: TargetHUD, ModuleList, Watermark, Reach, Armor, Potions, Keystrokes, Coords, Notifications, Scoreboard |
| HUD | Draggable elements, TargetHUD, ModuleList, Watermark, Armor, Potions, Keystrokes, Coords, Notifications, Scoreboard |
| ClickGUI | Full-featured GUI with module browser, settings editor, theme editor, config profiles | | ClickGUI | Full-featured GUI with module browser, settings editor, theme editor, config profiles |
| Config profiles | `.lvisual` profiles for modules, HUD, and themes — shareable between users | | Config profiles | `.lvcfg` profiles for modules, HUD, and themes — shareable between users |
| Addon API | Versioned addon API for extending the client | | Addon API | Versioned addon API for extending the client |
| Custom renderer | Custom UI rendering with MSDF fonts, SVG icons, and shader support | | Custom renderer | Custom UI rendering with MSDF fonts, SVG icons, and shader support |
| Iris/Sodium/Xaero | Optional compatibility with popular mods | | Iris/Sodium/Xaero | Optional compatibility with popular mods |
@ -75,7 +74,7 @@ LoVisual is developed and maintained by [loki5512344](https://github.com/loki551
### Обзор ### Обзор
LoVisual — открытый утилитарный и PvP-клиент для Minecraft. Включает настраиваемые боевые, двигательные, визуальные и автоматизированные модули, кастомный ClickGUI, систему тем и API для аддонов. LoVisual — открытый легитный утилитарный клиент для Minecraft. Включает настраиваемые визуальные, HUD и UI-модули, кастомный ClickGUI, систему тем и API для аддонов.
LoVisual разрабатывается и поддерживается [loki5512344](https://github.com/loki5512344). Цель — добавлять новые возможности и поддерживать актуальные версии Minecraft. LoVisual разрабатывается и поддерживается [loki5512344](https://github.com/loki5512344). Цель — добавлять новые возможности и поддерживать актуальные версии Minecraft.
@ -90,14 +89,13 @@ LoVisual разрабатывается и поддерживается [loki551
| Возможность | Описание | | Возможность | Описание |
|-------------|----------| |-------------|----------|
| Combat | KillAura, AutoCrystal, AutoAnchor, AutoBed, AutoBow, Criticals, Reach, Velocity, Backtrack и другие | | Combat | ProjectilePuncher, Reach, RubberHand, SpearAssist, TPSSync, PvpCooldowns, NoFriendDamage, TargetESP, Predictions, DamageTint |
| Movement | Speed, Flight, ElytraFly, ElytraHelper, Sprint, Timer, Strafe, TargetStrafe, Scaffold, Parkour | | Visuals | ESP, NameTags, Chams, Crosshair, FullBright, MotionBlur, NoRender, Freecam, Hitboxes, Light, ChinaHat, Trinkets и другие |
| Visuals | ESP, NameTags, Chams, Tracers, Crosshair, Zoom, Freecam, FullBright, MotionBlur, NoRender | | Player | AutoTool, ClickPearl, FakeLag, NoDelay, LockSlot, AutoRespawn, DeathCoords, NoInteract |
| Player | AutoTool, AutoEat, AutoArmor, AutoTotem, Scaffold, ClickPearl, FakeLag, XCarry | | Misc | BetterMinecraft, FakePlayer, HitSounds, NameProtect, MessageFilter, NoSound, DefineTarget |
| Misc | BetterMinecraft, FakePlayer, HitSounds, Panic, NoSound | | HUD | Перетаскиваемые и статические элементы: TargetHUD, ModuleList, Watermark, Reach, Armor, Potions, Keystrokes, Coords, Notifications, Scoreboard |
| HUD | Перетаскиваемые элементы: TargetHUD, ModuleList, Watermark, Armor, Potions, Keystrokes, Coords, Notifications, Scoreboard |
| ClickGUI | Полноценный GUI с браузером модулей, редактором настроек, тем и профилей конфигов | | ClickGUI | Полноценный GUI с браузером модулей, редактором настроек, тем и профилей конфигов |
| Профили `.lvisual` | Профили для модулей, HUD и тем — можно шарить между пользователями | | Профили `.lvcfg` | Профили для модулей, HUD и тем — можно шарить между пользователями |
| Addon API | Версионированное API для аддонов | | Addon API | Версионированное API для аддонов |
| Кастомный рендер | UI-рендеринг с MSDF-шрифтами, SVG-иконками и шейдерами | | Кастомный рендер | UI-рендеринг с MSDF-шрифтами, SVG-иконками и шейдерами |
| Iris/Sodium/Xaero | Опциональная совместимость с популярными модами | | Iris/Sodium/Xaero | Опциональная совместимость с популярными модами |
@ -137,7 +135,7 @@ LoVisual разрабатывается и поддерживается [loki551
### Docs ### Docs
- [Architecture](docs/ARCHITECTURE.md) - [Development guide](DEVELOPMENT.md) — project rules, how to add modules/HUD/events/config
### License ### License

View file

@ -148,8 +148,10 @@
чистый import-churn без функциональной ценности; делать только отдельным скриптовым проходом чистый import-churn без функциональной ценности; делать только отдельным скриптовым проходом
## Фаза 6: Тесты (пункт 6) ## Фаза 6: Тесты (пункт 6)
- [ ] Юнит-тесты: EventBus, конфиг, утилиты (игровые классы — mock) - [x] Юнит-тесты: EventBus (12), конфиг (values: NumberValue/Boolean/String/Set/ItemIdSet/BooleanMap,
- [ ] По мере переписи модулей — тест на каждый ModeValue, EnumValue, RGB/RGBA color; profile: DiffEngine, ValueCodec),
утилиты (LegacyTextUtil — 12). Игровые классы без моков: тесты только на чистую логику.
- [x] По мере переписи модулей — тест на каждый (Текущий итог: 292 теста, все зелёные)
## Фаза 7: QoL-модули из Delta (легит) — ПЛАН, обсуждаем ## Фаза 7: QoL-модули из Delta (легит) — ПЛАН, обсуждаем
> Референс: `ref/delta-26.2-main` (чит-клиент, код обфусцирован — берём только идеи, > Референс: `ref/delta-26.2-main` (чит-клиент, код обфусцирован — берём только идеи,
@ -167,41 +169,64 @@
- [x] Правило папок: максимум 4 файла (было 3) - [x] Правило папок: максимум 4 файла (было 3)
### Перенос (список пользователя) ### Перенос (список пользователя)
- [ ] **StreamerMode** (misc, ~80 у них) — при записи/стриме скрывает: ники игроков, - [x] **StreamerMode** (misc) — маскирует ники-подобные токены и названия серверов в чате
имена друзей, названия серверов («Анархия-N») в чате/таб-листе/скорборде; скрытие скинов. (общая точка NameProtect: ChatComponentMixin.addMessage), скины заменяются на
Интеграция: наши BetterChat + CustomTabList + Scoreboard-виджет + рендер скинов. стандартные (AbstractClientPlayerMixin.getSkin). Чистая логика — `util/text/NameMasker`
(тест NameMaskerTest).
- [x] **LockSlot** (player, ~36) — запрет выброса из выбранных слотов 1–9 (анти-случайный Q), - [x] **LockSlot** (player, ~36) — запрет выброса из выбранных слотов 1–9 (анти-случайный Q),
опция «только в PVP», предупреждение в чат. Точка: перехват drop-пакета. опция «только в PVP», предупреждение в чат. Точка: перехват drop-пакета.
- [x] **AutoRespawn** (player, ~28) — авто-возрождение после смерти (tick на death screen). - [x] **AutoRespawn** (player, ~28) — авто-возрождение после смерти (tick на death screen).
- [ ] **AutoLeave** (player, ~61) — выход в хаб по триггерам: малое ХП, игроки рядом (N, радиус), задержка. - [x] **AutoLeave** (player) — выход по триггерам: низкое ХП, N игроков в радиусе
- [ ] **AutoAccept** (player, ~67) — авто-принятие запросов: клан/ТП; опция «только от друзей» (друзья игнорируются опцией), задержка + предупреждение. Выход через
через наш PlayerRelations. Точка: перехват chat-пакетов. `mc.disconnect(null, false)`; чистая state-machine `LeavePolicy` (AutoLeaveTest).
- [ ] **SoundReducer** (player, ~42) — приглушение выбранных звуков/категорий (громкость 0..N), - [x] **AutoAccept** (player) — авто-ответ на ТП/клан-запросы: паттерны фраз (SetValue),
в отличие от нашего NoSound (он только вкл/выкл всего). Вопрос: новый модуль или развитие NoSound. команда ответа, «только друзья» (PlayerRelations), задержка, кулдаун ≥500мс.
- [ ] **Wasted** (misc, ~100) — ОТМЕНЕНО (решение пользователя) Пакетный путь: PvpChatEvent (handleSystemChat/handlePlayerChat), отправка
- [ ] **ThirdPerson** (player, ~77) — свободный обзор от 3-го лица без поворота движения `connection.sendCommand(...)`. Чистый парсер `TeleportRequestParser` (AutoAcceptTest).
(по нажатию/зажатию бинда). Не дублирует Freecam (он про отдельную камеру-сущность). - [x] **SoundReducer** (player, ~42) — РЕШЕНО: развитие NoSound, а не новый модуль.
Точка: наш CameraMixin. Режимы Mute (старое поведение через ClientPacketListenerMixin) и Reduce —
- [ ] **UseTracker** (player, ~86) — уведомления об использованиях: тотем, зелья, предметы. новая громкость 1–100% через SoundEngineMixin (@Redirect getVolume в
Вывод через наш HudNotifier. Точка: пакеты + инвентарь. SoundEngine.play). Чистая математика `NoSound.resolveScale` (NoSoundTest).
- [x] **NoServerPack** (misc) — у Delta ПУСТЫШКА (пустой класс). Сделаем по-настоящему: - [x] **Wasted** (misc, ~100) — ОТМЕНЕНО (решение пользователя)
авто-отклонение принудительного серверного ресурспака (спрашивать/отклонять всегда). - [x] **ThirdPerson** (player) — свободный обзор от 3-го лица по удержанию бинда (L,
Точка: наш ClientCommonPacketListenerImplMixin. настраивается), движение игрока не поворачивается. MouseMixin.turnPlayer копит
orbit-yaw/pitch, CameraMixin.update (TAIL) ставит камеру за игрока по орбите
(+ cull-frustum). Орбиты сбрасываются, когда бинд отпущен.
- [x] **UseTracker** (player) — уведомления: мои тотемы (N осталось), тотемы других,
эффекты оппонентов (группа: invisibility/speed/strength/regeneration/fire_res/weakness).
Пакетный путь: ClientboundEntityEventPacket (event 35) + ClientboundUpdateMobEffectPacket.
Тест UseTrackerTest.
- [x] **NoServerPack** (misc) — авто-отклонение принудительного серверного ресурспака
(спрашивать/отклонять всегда). Точка: ClientCommonPacketListenerImplMixin.
- [x] **NoFriendDamage** (combat, ~19) — отмена атаки, если цель — друг (PlayerRelations). - [x] **NoFriendDamage** (combat, ~19) — отмена атаки, если цель — друг (PlayerRelations).
Точка: перехват атаки (AttackEvent у них; у нас миксин на attack/interact-пакет). - [x] **TapeMouse** — ОСТАВЛЕН (решение владельца 2026-09-08): автоклик по таймеру
- [x] **TapeMouse** (combat, ~66) — автоклик по таймеру. ⚠️ ПРОТИВОРЕЧИТ легит-политике Фазы 3.5 (Attack/Use) для AFK-ферм, интервал, опция «только при зажатии». Считаем
(тот же класс, что удалённые AutoAttack/TriggerBot). По умолчанию НЕ делаем / делаем допустимым исключением из Фазы 3.5 по явному решению владельца.
легит-вариант «напоминание о клике» без автоматического клика — РЕШИТЬ.
### Кандидаты из отчёта (не в списке пользователя — обсудить) ### Кандидаты из отчёта (не в списке пользователя — обсудить)
- [x] **DeathCoords** (~27) — координаты последней смерти в чат. Дёшево, полезно. - [x] **DeathCoords** (~27) — координаты последней смерти в чат. Дёшево, полезно.
- [ ] **ItemScroller** — перекладывание предметов прокруткой в инвентарях. Крупный QoL (~200+). - [x] **ItemScroller** (misc) — shift-перекладка предметов колёсиком в контейнерах:
- [ ] **ColorPlus** — ⚠️ это по сути наш удалённый PostFX (экспозиция/гамма/контраст/насыщенность/ QUICK_MOVE клик по слоту под курсором, иначе слот хотбара (опция).
виньетка/резкость/LUT). Восстанавливать под новым именем или забыть — РЕШИТЬ. Точка: AbstractContainerScreenMixin.mouseScrolled; чистая логика
- [ ] **Arrows / off-screen индикаторы** — дополнение к Pointers (у нас их нет вообще). `ScrollMovePolicy` (ScrollMovePolicyTest).
- [x] **Arrows / off-screen индикаторы** — ОТМЕНЕНО (решение владельца 2026-09-08).
### Проверка после каждого модуля: `./gradlew build` ✓, тесты на чистую логику ✓ ### Проверка после каждого модуля: `./gradlew build` ✓, тесты на чистую логику ✓
## Фаза 7.5: Команды — префикс % + автокомплит
- [x] Префикс команд сменён `@` → **`%`** (старый `@` принимается как legacy).
Источник истины — `features/command/CommandPrefix` (CHAR/STRING/isCommandLike/strip);
вызовы обновлены: CommandManager.handle/suggest, ScreenMixin (RunCommand),
BetterChat (TabHandler, SuggestionState, SuggestionRenderer, ContextMenuHandler),
usage-строки и RunCommand-ссылки всех команд (help/modules и др.).
- [x] Аргументный автокомплит дополнен usage-подсказками: если `suggest()` команды
не дал вариантов на позиции — показывается подсказка вида `%toggle <module>`
(`features/command/UsageHints`, парсит `<...>`/`[...]` из usage-строки).
Существующий UI автокомплита BetterChat (стрелки/Tab/клик) работает как раньше.
### Проверка: `./gradlew build` ✓, `./gradlew test` ✓ (CommandPrefixTest, UsageHintsTest,
### CommandDiscoveryTest обновлён)
## Фаза 8: Структура «как у Delta» (маленькие файлы) — ПЛАН ## Фаза 8: Структура «как у Delta» (маленькие файлы) — ПЛАН
> Правило ≤200 уже есть (Фаза 1–3), но долг: ~240 файлов >200 строк. > Правило ≤200 уже есть (Фаза 1–3), но долг: ~240 файлов >200 строк.
> У Delta файлы компактные и слои разделены: api / client(features, ui, utils, processors) / > У Delta файлы компактные и слои разделены: api / client(features, ui, utils, processors) /

View file

@ -0,0 +1,13 @@
{
"enabled": true,
"watermark_scale": 1.0,
"watermark_color_mode": "Theme",
"watermark_show_fps": true,
"watermark_show_ping": true,
"watermark_show_server": true,
"watermark_show_user": false,
"watermark_show_dimensions": false,
"watermark_title_color": "#5CC8E7",
"watermark_meta_color": "#9B9B9B",
"watermark_bg_color": "#1A1D23"
}

View file

@ -0,0 +1,10 @@
{
"enabled": true,
"reach_scale": 1.0,
"reach_keep_ms": 2500.0,
"reach_sync_theme": true,
"reach_show_max": true,
"reach_highlight_overreach": true,
"reach_value_color": "#5CC8E7",
"reach_bg_color": "#1A1D23"
}

View file

@ -0,0 +1,12 @@
{
"enabled": true,
"watermark_scale": 1.0,
"watermark_sync_theme": true,
"watermark_show_version": true,
"watermark_show_user": false,
"watermark_show_fps": true,
"watermark_show_ping": true,
"watermark_title_color": "#5CC8E7",
"watermark_meta_color": "#9B9B9B",
"watermark_bg_color": "#1A1D23"
}

View file

@ -55,10 +55,10 @@ public enum CommandManager {
public static boolean handle(String raw) { public static boolean handle(String raw) {
if (raw == null) return false; if (raw == null) return false;
String trimmed = raw.trim(); String trimmed = raw.trim();
if (!isCommandLike(trimmed)) return false; if (!CommandPrefix.isCommandLike(trimmed)) return false;
if (!initialized) init(); if (!initialized) init();
String body = trimmed.substring(1).trim(); String body = CommandPrefix.strip(trimmed);
if (body.isEmpty()) return false; if (body.isEmpty()) return false;
String[] parts = body.split("\\s+"); String[] parts = body.split("\\s+");
if (parts.length == 0) return false; if (parts.length == 0) return false;
@ -66,7 +66,8 @@ public enum CommandManager {
ClientCommand cmd = find(name); ClientCommand cmd = find(name);
if (cmd == null) { if (cmd == null) {
CommandOutput.error("Unknown client command: @" + name + ". Use @help."); CommandOutput.error("Unknown client command: " + CommandPrefix.STRING + name
+ ". Use " + CommandPrefix.STRING + "help.");
return true; return true;
} }
if (!cmd.isAvailable()) { if (!cmd.isAvailable()) {
@ -82,7 +83,9 @@ public enum CommandManager {
public static List<Suggestion> suggest(String input, int cursor) { public static List<Suggestion> suggest(String input, int cursor) {
if (input == null) return List.of(); if (input == null) return List.of();
int start = firstNonWhitespace(input); int start = firstNonWhitespace(input);
if (start < 0 || input.charAt(start) != '@') return List.of(); if (start < 0) return List.of();
char prefix = input.charAt(start);
if (prefix != CommandPrefix.CHAR && prefix != CommandPrefix.LEGACY) return List.of();
if (!initialized) init(); if (!initialized) init();
int cursorClamped = Math.max(start + 1, Math.min(cursor, input.length())); int cursorClamped = Math.max(start + 1, Math.min(cursor, input.length()));
@ -110,6 +113,12 @@ public enum CommandManager {
args.addAll(Arrays.asList(parts).subList(1, parts.length)); args.addAll(Arrays.asList(parts).subList(1, parts.length));
CommandContext ctx = new CommandContext(Minecraft.getInstance(), input, cmdName, args); CommandContext ctx = new CommandContext(Minecraft.getInstance(), input, cmdName, args);
suggestions = cmd.suggest(ctx, argIndex, token); suggestions = cmd.suggest(ctx, argIndex, token);
if (suggestions == null || suggestions.isEmpty()) {
// Fall back to the usage hint so there is always guidance on
// what can be typed at the current position.
String hint = UsageHints.forCommand(cmd, argIndex);
suggestions = hint == null ? List.of() : List.of(hint);
}
} }
if (suggestions == null || suggestions.isEmpty()) return List.of(); if (suggestions == null || suggestions.isEmpty()) return List.of();
@ -138,15 +147,6 @@ public enum CommandManager {
return null; return null;
} }
private static boolean isCommandLike(String text) {
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (Character.isWhitespace(c)) continue;
return c == '@';
}
return false;
}
private static List<String> suggestCommandNames(String token) { private static List<String> suggestCommandNames(String token) {
String lower = token == null ? "" : token.toLowerCase(); String lower = token == null ? "" : token.toLowerCase();
List<String> out = new ArrayList<>(); List<String> out = new ArrayList<>();

View file

@ -36,7 +36,7 @@ public final class CommandMetadata {
boolean localizedDescription) { boolean localizedDescription) {
this.id = normalizeId(id); this.id = normalizeId(id);
this.aliases = normalizeAliases(aliases, this.id); this.aliases = normalizeAliases(aliases, this.id);
this.usage = usage == null || usage.isBlank() ? "@" + this.id : usage.trim(); this.usage = normalizeUsage(usage, this.id);
if (description == null || description.isBlank()) { if (description == null || description.isBlank()) {
this.description = this.usage; this.description = this.usage;
this.localizedDescription = false; this.localizedDescription = false;
@ -94,15 +94,34 @@ public final class CommandMetadata {
private static CommandMetadata fromLegacy(ClientCommand command) { private static CommandMetadata fromLegacy(ClientCommand command) {
String id = command.name(); String id = command.name();
List<String> aliases = command.aliases(); List<String> aliases = command.aliases();
// Legacy default usage()/description() resolve back to the id with no
// prefix; they are normalized here the same way as annotated strings.
String usage = command.usage(); String usage = command.usage();
String description = command.description(); String description = command.description();
return new CommandMetadata(id, aliases, usage, description, false); String normalizedUsage = usage.equals(id) || usage.isBlank() ? null : usage;
String normalizedDescription =
description.equals(id) || description.equals(usage) || description.isBlank() ? null : description;
return new CommandMetadata(id, aliases, normalizedUsage, normalizedDescription, false);
}
private static String normalizeUsage(String usage, String id) {
if (usage == null || usage.isBlank()) return CommandPrefix.STRING + id;
String trimmed = usage.trim();
// Usage strings were written with the legacy '@' prefix; re-prefix them
// with the active prefix so help output stays consistent.
if (!trimmed.isEmpty() && (trimmed.charAt(0) == CommandPrefix.LEGACY
|| trimmed.charAt(0) == CommandPrefix.CHAR)) {
trimmed = trimmed.substring(1);
}
return CommandPrefix.STRING + trimmed;
} }
private static String normalizeId(String id) { private static String normalizeId(String id) {
if (id == null || id.isBlank()) throw new IllegalArgumentException("Command id cannot be blank"); if (id == null || id.isBlank()) throw new IllegalArgumentException("Command id cannot be blank");
String normalized = id.trim().toLowerCase(Locale.ROOT); String normalized = id.trim().toLowerCase(Locale.ROOT);
if (normalized.charAt(0) == '@') normalized = normalized.substring(1); if (normalized.charAt(0) == '@' || normalized.charAt(0) == CommandPrefix.CHAR) {
normalized = normalized.substring(1);
}
if (normalized.isBlank() || normalized.chars().anyMatch(Character::isWhitespace)) { if (normalized.isBlank() || normalized.chars().anyMatch(Character::isWhitespace)) {
throw new IllegalArgumentException("Invalid command id: " + id); throw new IllegalArgumentException("Invalid command id: " + id);
} }
@ -115,7 +134,9 @@ public final class CommandMetadata {
for (String alias : aliases) { for (String alias : aliases) {
if (alias == null || alias.isBlank()) continue; if (alias == null || alias.isBlank()) continue;
String value = alias.trim().toLowerCase(Locale.ROOT); String value = alias.trim().toLowerCase(Locale.ROOT);
if (value.charAt(0) == '@') value = value.substring(1); if (!value.isEmpty() && (value.charAt(0) == '@' || value.charAt(0) == CommandPrefix.CHAR)) {
value = value.substring(1);
}
if (value.isBlank() || value.chars().anyMatch(Character::isWhitespace) || value.equals(id)) continue; if (value.isBlank() || value.chars().anyMatch(Character::isWhitespace) || value.equals(id)) continue;
if (!normalized.contains(value)) normalized.add(value); if (!normalized.contains(value)) normalized.add(value);
} }

View file

@ -0,0 +1,53 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.command;
/**
* Single source of truth for the client command prefix. Messages typed in chat
* starting with this character are handled by the client and never sent to the
* server. Also covers the legacy '@' so old profiles/links keep working.
*/
public final class CommandPrefix {
/** The active client command prefix. */
public static final char CHAR = '%';
/** Legacy prefix, still accepted for compatibility. */
public static final char LEGACY = '@';
/** The prefix as a string (for messages, usage strings and click events). */
public static final String STRING = String.valueOf(CHAR);
private CommandPrefix() {
}
/** @return true when the first non-space character is the (or a legacy) prefix */
public static boolean isCommandLike(String text) {
return leadingPrefix(text) != 0;
}
/**
* @return the prefix char found before any command text, 0 when the input
* does not start with a command prefix
*/
public static char leadingPrefix(String text) {
if (text == null) return 0;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (Character.isWhitespace(c)) continue;
return (c == CHAR || c == LEGACY) ? c : 0;
}
return 0;
}
/** Strips the leading prefix and surrounding whitespace; null-safe. */
public static String strip(String text) {
if (text == null) return null;
String trimmed = text.trim();
if (trimmed.isEmpty()) return trimmed;
char p = trimmed.charAt(0);
return (p == CHAR || p == LEGACY) ? trimmed.substring(1).trim() : trimmed;
}
}

View file

@ -0,0 +1,83 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.command;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Builds usage-hint suggestions shown in the autocomplete popup when a command
* has no concrete suggestions for the argument position the user is typing at.
* Pure string logic, unit-testable without Minecraft.
*/
public final class UsageHints {
private UsageHints() {
}
/**
* Extracts the placeholder at {@code argIndex} (1-based, after the command
* name) from a usage string like "%toggle &lt;module&gt; [on|off|toggle]".
*
* @return hint without brackets, e.g. "module" or "on|off|toggle";
* null when the usage has nothing at that position
*/
public static String placeholder(String usage, int argIndex) {
if (usage == null || usage.isBlank() || argIndex < 1) return null;
return placeholders(usage).stream()
.skip(argIndex - 1L)
.findFirst()
.orElse(null);
}
/**
* Usage hint with the command prefix: "&lt;module&gt;" becomes "%toggle &lt;module&gt;"
* so the popup always shows what the full input looks like so far.
*/
public static String forCommand(ClientCommand command, int argIndex) {
if (command == null || argIndex < 1) return null;
String usage = command.metadata().usage();
String placeholder = placeholder(usage, argIndex);
if (placeholder == null) return null;
String head = commandHead(usage);
String prefix = CommandPrefix.STRING + (head.isEmpty() ? command.metadata().id() : head);
return prefix + " <" + placeholder + ">";
}
/** Splits "[list|add|remove] [player]" into ["list|add|remove", "player"]. */
static List<String> placeholders(String usage) {
List<String> out = new ArrayList<>();
StringBuilder current = new StringBuilder();
boolean inside = false;
for (int i = 0; i < usage.length(); i++) {
char c = usage.charAt(i);
if (c == '[' || c == '<') {
inside = true;
current.setLength(0);
} else if (c == ']' || c == '>') {
inside = false;
if (!current.isEmpty()) out.add(current.toString());
current.setLength(0);
} else if (inside) {
current.append(c);
}
}
if (inside && !current.isEmpty()) out.add(current.toString());
return List.copyOf(out);
}
/** First word of the usage body, e.g. "toggle" from "%toggle <module>". */
static String commandHead(String usage) {
String trimmed = usage.trim();
if (trimmed.startsWith(CommandPrefix.STRING)) trimmed = trimmed.substring(1);
int space = trimmed.indexOf(' ');
String head = space < 0 ? trimmed : trimmed.substring(0, space);
return head.toLowerCase(Locale.ROOT);
}
}

View file

@ -20,7 +20,7 @@ import java.util.Locale;
@CommandInfo( @CommandInfo(
id = "addons", id = "addons",
usage = "@addons [list|scan|enable|disable] [id]", usage = "%addons [list|scan|enable|disable] [id]",
descriptionKey = "command.addons.description" descriptionKey = "command.addons.description"
) )
public final class AddonsCommand implements ClientCommand { public final class AddonsCommand implements ClientCommand {

View file

@ -20,7 +20,7 @@ import java.util.Locale;
@CommandInfo( @CommandInfo(
id = "bind", id = "bind",
aliases = {"binds", "keybind"}, aliases = {"binds", "keybind"},
usage = "@bind <module> <key|combo|none> | @bind list", usage = "%bind <module> <key|combo|none> | @bind list",
descriptionKey = "command.bind.description" descriptionKey = "command.bind.description"
) )
public final class BindCommand implements ClientCommand { public final class BindCommand implements ClientCommand {

View file

@ -21,7 +21,7 @@ import java.util.Locale;
@CommandInfo( @CommandInfo(
id = "config", id = "config",
aliases = {"cfg", "settings"}, aliases = {"cfg", "settings"},
usage = "@config [save|load|path]", usage = "%config [save|load|path]",
descriptionKey = "command.config.description" descriptionKey = "command.config.description"
) )
public final class ConfigCommand implements ClientCommand { public final class ConfigCommand implements ClientCommand {

View file

@ -17,7 +17,7 @@ import java.util.Locale;
@CommandInfo( @CommandInfo(
id = "coordinates", id = "coordinates",
aliases = {"coords", "position", "pos"}, aliases = {"coords", "position", "pos"},
usage = "@coordinates", usage = "%coordinates",
descriptionKey = "command.coordinates.description" descriptionKey = "command.coordinates.description"
) )
public final class CoordinatesCommand implements ClientCommand { public final class CoordinatesCommand implements ClientCommand {

View file

@ -10,6 +10,7 @@ package dev.loki.lovisual.features.command.impl;
import dev.loki.lovisual.features.command.ClientCommand; import dev.loki.lovisual.features.command.ClientCommand;
import dev.loki.lovisual.features.command.CommandContext; import dev.loki.lovisual.features.command.CommandContext;
import dev.loki.lovisual.features.command.CommandInfo; import dev.loki.lovisual.features.command.CommandInfo;
import dev.loki.lovisual.features.command.CommandPrefix;
import dev.loki.lovisual.features.command.CommandManager; import dev.loki.lovisual.features.command.CommandManager;
import dev.loki.lovisual.features.command.CommandMetadata; import dev.loki.lovisual.features.command.CommandMetadata;
import dev.loki.lovisual.features.command.CommandOutput; import dev.loki.lovisual.features.command.CommandOutput;
@ -25,7 +26,7 @@ import java.util.Locale;
@CommandInfo( @CommandInfo(
id = "help", id = "help",
aliases = {"commands", "cmds"}, aliases = {"commands", "cmds"},
usage = "@help [page|command]", usage = "%help [page|command]",
descriptionKey = "command.help.description" descriptionKey = "command.help.description"
) )
public final class HelpCommand implements ClientCommand { public final class HelpCommand implements ClientCommand {
@ -101,13 +102,13 @@ public final class HelpCommand implements ClientCommand {
private static MutableComponent commandLink(ClientCommand command) { private static MutableComponent commandLink(ClientCommand command) {
CommandMetadata metadata = command.metadata(); CommandMetadata metadata = command.metadata();
String insert = "@" + metadata.id() + " "; String insert = CommandPrefix.STRING + metadata.id() + " ";
String aliases = metadata.aliases().isEmpty() ? "none" : String.join(", ", metadata.aliases()); String aliases = metadata.aliases().isEmpty() ? "none" : String.join(", ", metadata.aliases());
Component hover = Component.literal(metadata.description() Component hover = Component.literal(metadata.description()
+ "\nUsage: " + metadata.usage() + "\nUsage: " + metadata.usage()
+ "\nAliases: " + aliases + "\nAliases: " + aliases
+ "\nClick to insert"); + "\nClick to insert");
return Component.literal("@" + metadata.id()) return Component.literal(CommandPrefix.STRING + metadata.id())
.withStyle(style -> style .withStyle(style -> style
.withColor(COMMAND_COLOR) .withColor(COMMAND_COLOR)
.withUnderlined(true) .withUnderlined(true)
@ -120,13 +121,13 @@ public final class HelpCommand implements ClientCommand {
.withStyle(style -> style .withStyle(style -> style
.withBold(true) .withBold(true)
.withColor(ACTIVE_COLOR) .withColor(ACTIVE_COLOR)
.withClickEvent(new ClickEvent.RunCommand("@help " + page)) .withClickEvent(new ClickEvent.RunCommand("%help " + page))
.withHoverEvent(new HoverEvent.ShowText(Component.literal("Open help page " + page)))); .withHoverEvent(new HoverEvent.ShowText(Component.literal("Open help page " + page))));
} }
private static void showDetails(ClientCommand command) { private static void showDetails(ClientCommand command) {
CommandMetadata metadata = command.metadata(); CommandMetadata metadata = command.metadata();
MutableComponent message = Component.literal("@" + metadata.id()) MutableComponent message = Component.literal(CommandPrefix.STRING + metadata.id())
.withStyle(style -> style.withBold(true).withColor(TITLE_COLOR)); .withStyle(style -> style.withBold(true).withColor(TITLE_COLOR));
message.append(Component.literal("\n" + metadata.description()).withStyle(style -> style.withColor(COMMAND_COLOR))); message.append(Component.literal("\n" + metadata.description()).withStyle(style -> style.withColor(COMMAND_COLOR)));
message.append(Component.literal("\nUsage: " + metadata.usage()).withStyle(style -> style.withColor(MUTED_COLOR))); message.append(Component.literal("\nUsage: " + metadata.usage()).withStyle(style -> style.withColor(MUTED_COLOR)));

View file

@ -19,7 +19,7 @@ import java.util.Locale;
@CommandInfo( @CommandInfo(
id = "hide", id = "hide",
aliases = {"modulevisibility", "showmodule"}, aliases = {"modulevisibility", "showmodule"},
usage = "@hide <module> [on|off|toggle]", usage = "%hide <module> [on|off|toggle]",
descriptionKey = "command.hide.description" descriptionKey = "command.hide.description"
) )
public final class HideCommand implements ClientCommand { public final class HideCommand implements ClientCommand {

View file

@ -23,7 +23,7 @@ import java.util.stream.Collectors;
@CommandInfo( @CommandInfo(
id = "iris", id = "iris",
aliases = "shaderpack", aliases = "shaderpack",
usage = "@iris", usage = "%iris",
descriptionKey = "command.iris.description" descriptionKey = "command.iris.description"
) )
public final class IrisCommand implements ClientCommand { public final class IrisCommand implements ClientCommand {

View file

@ -24,7 +24,7 @@ import java.util.Locale;
@CommandInfo( @CommandInfo(
id = "modules", id = "modules",
aliases = {"modulelist", "mods"}, aliases = {"modulelist", "mods"},
usage = "@modules [all|enabled|combat|movement|player|visuals|misc] [page]", usage = "%modules [all|enabled|combat|movement|player|visuals|misc] [page]",
descriptionKey = "command.modules.description" descriptionKey = "command.modules.description"
) )
public final class ModulesCommand implements ClientCommand { public final class ModulesCommand implements ClientCommand {
@ -54,7 +54,7 @@ public final class ModulesCommand implements ClientCommand {
message.append(Component.literal(module.getDisplayName()) message.append(Component.literal(module.getDisplayName())
.withStyle(style -> style .withStyle(style -> style
.withColor(color) .withColor(color)
.withClickEvent(new ClickEvent.RunCommand("@toggle " + module.name())) .withClickEvent(new ClickEvent.RunCommand("%toggle " + module.name()))
.withHoverEvent(new HoverEvent.ShowText(Component.literal( .withHoverEvent(new HoverEvent.ShowText(Component.literal(
module.getDescription() + "\nClick to toggle\nID: " + module.name()))))); module.getDescription() + "\nClick to toggle\nID: " + module.name())))));
message.append(Component.literal(" [" + (module.isEnabled() ? "ON" : "OFF") + "]") message.append(Component.literal(" [" + (module.isEnabled() ? "ON" : "OFF") + "]")
@ -105,6 +105,6 @@ public final class ModulesCommand implements ClientCommand {
return Component.literal(label).withStyle(style -> style return Component.literal(label).withStyle(style -> style
.withBold(true) .withBold(true)
.withColor(0xFF70E0A0) .withColor(0xFF70E0A0)
.withClickEvent(new ClickEvent.RunCommand("@modules " + filter + " " + page))); .withClickEvent(new ClickEvent.RunCommand("%modules " + filter + " " + page)));
} }
} }

View file

@ -16,7 +16,7 @@ import dev.loki.lovisual.util.player.NetworkStatsUtil;
@CommandInfo( @CommandInfo(
id = "ping", id = "ping",
aliases = "latency", aliases = "latency",
usage = "@ping", usage = "%ping",
descriptionKey = "command.ping.description" descriptionKey = "command.ping.description"
) )
public final class PingCommand implements ClientCommand { public final class PingCommand implements ClientCommand {

View file

@ -122,7 +122,7 @@ public abstract class RelationCommand implements ClientCommand {
@CommandInfo( @CommandInfo(
id = "friend", id = "friend",
aliases = {"friends", "f"}, aliases = {"friends", "f"},
usage = "@friend [list|add|remove|toggle|clear] [player]", usage = "%friend [list|add|remove|toggle|clear] [player]",
descriptionKey = "command.friend.description" descriptionKey = "command.friend.description"
) )
public static final class Friends extends RelationCommand { public static final class Friends extends RelationCommand {
@ -135,7 +135,7 @@ public abstract class RelationCommand implements ClientCommand {
@CommandInfo( @CommandInfo(
id = "enemy", id = "enemy",
aliases = {"enemies", "e"}, aliases = {"enemies", "e"},
usage = "@enemy [list|add|remove|toggle|clear] [player]", usage = "%enemy [list|add|remove|toggle|clear] [player]",
descriptionKey = "command.enemy.description" descriptionKey = "command.enemy.description"
) )
public static final class Enemies extends RelationCommand { public static final class Enemies extends RelationCommand {
@ -148,7 +148,7 @@ public abstract class RelationCommand implements ClientCommand {
@CommandInfo( @CommandInfo(
id = "staff", id = "staff",
aliases = {"staffs", "admin"}, aliases = {"staffs", "admin"},
usage = "@staff [list|add|remove|toggle|clear] [player]", usage = "%staff [list|add|remove|toggle|clear] [player]",
descriptionKey = "command.staff.description" descriptionKey = "command.staff.description"
) )
public static final class Staff extends RelationCommand { public static final class Staff extends RelationCommand {

View file

@ -19,7 +19,7 @@ import java.util.Locale;
@CommandInfo( @CommandInfo(
id = "serverinfo", id = "serverinfo",
aliases = {"server", "sinfo"}, aliases = {"server", "sinfo"},
usage = "@serverinfo", usage = "%serverinfo",
descriptionKey = "command.serverinfo.description" descriptionKey = "command.serverinfo.description"
) )
public final class ServerInfoCommand implements ClientCommand { public final class ServerInfoCommand implements ClientCommand {

View file

@ -19,7 +19,7 @@ import java.util.Locale;
@CommandInfo( @CommandInfo(
id = "toggle", id = "toggle",
aliases = "t", aliases = "t",
usage = "@toggle <module> [on|off|toggle]", usage = "%toggle <module> [on|off|toggle]",
descriptionKey = "command.toggle.description" descriptionKey = "command.toggle.description"
) )
public final class ToggleCommand implements ClientCommand { public final class ToggleCommand implements ClientCommand {

View file

@ -18,7 +18,7 @@ import java.util.Locale;
@CommandInfo( @CommandInfo(
id = "tps", id = "tps",
aliases = "servertps", aliases = "servertps",
usage = "@tps", usage = "%tps",
descriptionKey = "command.tps.description" descriptionKey = "command.tps.description"
) )
public final class TpsCommand implements ClientCommand { public final class TpsCommand implements ClientCommand {

View file

@ -12,10 +12,12 @@ import dev.loki.lovisual.features.command.CommandContext;
import dev.loki.lovisual.features.command.CommandInfo; import dev.loki.lovisual.features.command.CommandInfo;
import dev.loki.lovisual.features.command.CommandOutput; import dev.loki.lovisual.features.command.CommandOutput;
import java.util.List;
@CommandInfo( @CommandInfo(
id = "username", id = "username",
aliases = {"name", "ign", "whoami"}, aliases = {"name", "ign", "whoami"},
usage = "@username", usage = "%username",
descriptionKey = "command.username.description" descriptionKey = "command.username.description"
) )
public final class UsernameCommand implements ClientCommand { public final class UsernameCommand implements ClientCommand {
@ -29,4 +31,9 @@ public final class UsernameCommand implements ClientCommand {
CommandOutput.send("Username: " + profile.name() + " | UUID: " + profile.id()); CommandOutput.send("Username: " + profile.name() + " | UUID: " + profile.id());
return true; return true;
} }
@Override
public List<String> suggest(CommandContext ctx, int argIndex, String token) {
return List.of();
}
} }

View file

@ -21,7 +21,7 @@ import java.util.Locale;
@CommandInfo( @CommandInfo(
id = "xaero", id = "xaero",
aliases = {"xmark", "xwp", "waypoint", "marker"}, aliases = {"xmark", "xwp", "waypoint", "marker"},
usage = "@xaero here [name...] | @xaero [add] <x|~> <y|~> <z|~> [name...] | @xaero remove [all|#|name] | @xaero list", usage = "%xaero here [name...] | @xaero [add] <x|~> <y|~> <z|~> [name...] | @xaero remove [all|#|name] | @xaero list",
descriptionKey = "command.xaero.description" descriptionKey = "command.xaero.description"
) )
public final class XaeroWaypointCommand implements ClientCommand { public final class XaeroWaypointCommand implements ClientCommand {

View file

@ -6,6 +6,7 @@
*/ */
package dev.loki.lovisual.features.gui.chat; package dev.loki.lovisual.features.gui.chat;
import dev.loki.lovisual.features.command.CommandManager; import dev.loki.lovisual.features.command.CommandManager;
import dev.loki.lovisual.features.command.CommandPrefix;
import dev.loki.lovisual.features.gui.hud.draggable.impl.BetterChat; import dev.loki.lovisual.features.gui.hud.draggable.impl.BetterChat;
import dev.loki.lovisual.mixins.accessors.ChatScreenAccessor; import dev.loki.lovisual.mixins.accessors.ChatScreenAccessor;
import dev.loki.lovisual.util.screen.ClientScreen; import dev.loki.lovisual.util.screen.ClientScreen;
@ -128,7 +129,7 @@ final class BetterChatContextMenuHandler {
} else if (evt instanceof ClickEvent.CopyToClipboard) { } else if (evt instanceof ClickEvent.CopyToClipboard) {
ClipboardUtil.copy(val); ClipboardUtil.copy(val);
return true; return true;
} else if (evt instanceof ClickEvent.RunCommand && val.startsWith("@")) { } else if (evt instanceof ClickEvent.RunCommand && CommandPrefix.isCommandLike(val)) {
return CommandManager.handle(val); return CommandManager.handle(val);
} else if (evt instanceof ClickEvent.SuggestCommand || evt instanceof ClickEvent.RunCommand) { } else if (evt instanceof ClickEvent.SuggestCommand || evt instanceof ClickEvent.RunCommand) {
if (!ctrlDown) return false; if (!ctrlDown) return false;

View file

@ -5,6 +5,7 @@
* Licensed under the GNU General Public License v3.0. * Licensed under the GNU General Public License v3.0.
*/ */
package dev.loki.lovisual.features.gui.chat; package dev.loki.lovisual.features.gui.chat;
import dev.loki.lovisual.features.command.CommandPrefix;
import dev.loki.lovisual.mixins.accessors.ChatScreenAccessor; import dev.loki.lovisual.mixins.accessors.ChatScreenAccessor;
import dev.loki.lovisual.render.helpers.SystemCursor; import dev.loki.lovisual.render.helpers.SystemCursor;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@ -44,7 +45,7 @@ final class BetterChatSuggestionRenderer {
windowChanged = CommandSuggestorBridge.peekWindow(BetterChatRenderer.storedScreen) != CommandSuggestorBridge.lastWindow(); windowChanged = CommandSuggestorBridge.peekWindow(BetterChatRenderer.storedScreen) != CommandSuggestorBridge.lastWindow();
} }
if (inputChanged || windowChanged || BetterChatRenderer.lastSnapshot == null) { if (inputChanged || windowChanged || BetterChatRenderer.lastSnapshot == null) {
if (prefix == '@') { if (prefix == CommandPrefix.CHAR || prefix == CommandPrefix.LEGACY) {
BetterChatRenderer.lastSnapshot = BetterChatSuggestionState.buildCustomSnapshot(text, cursor); BetterChatRenderer.lastSnapshot = BetterChatSuggestionState.buildCustomSnapshot(text, cursor);
} else { } else {
BetterChatRenderer.lastSnapshot = CommandSuggestorBridge.snapshot(BetterChatRenderer.storedScreen); BetterChatRenderer.lastSnapshot = CommandSuggestorBridge.snapshot(BetterChatRenderer.storedScreen);
@ -56,7 +57,7 @@ final class BetterChatSuggestionRenderer {
BetterChatSuggestionState.resetSuggestionTracking(); BetterChatSuggestionState.resetSuggestionTracking();
return; return;
} }
if (prefix == '@') { if (prefix == CommandPrefix.CHAR || prefix == CommandPrefix.LEGACY) {
BetterChatRenderer.suggestWindow = null; BetterChatRenderer.suggestWindow = null;
} }
int hash = snap.texts().hashCode() ^ snap.selection() ^ snap.startIndex(); int hash = snap.texts().hashCode() ^ snap.selection() ^ snap.startIndex();

View file

@ -8,6 +8,7 @@
package dev.loki.lovisual.features.gui.chat; package dev.loki.lovisual.features.gui.chat;
import dev.loki.lovisual.features.command.CommandManager; import dev.loki.lovisual.features.command.CommandManager;
import dev.loki.lovisual.features.command.CommandPrefix;
import dev.loki.lovisual.mixins.accessors.ChatScreenAccessor; import dev.loki.lovisual.mixins.accessors.ChatScreenAccessor;
import dev.loki.lovisual.mixins.accessors.SuggestionWindowAccessor; import dev.loki.lovisual.mixins.accessors.SuggestionWindowAccessor;
import dev.loki.lovisual.util.screen.ClientScreen; import dev.loki.lovisual.util.screen.ClientScreen;
@ -80,7 +81,7 @@ final class BetterChatSuggestionState {
for (int i = 0; i < text.length(); i++) { for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i); char c = text.charAt(i);
if (Character.isWhitespace(c)) continue; if (Character.isWhitespace(c)) continue;
if (c == '/' || c == '@') return c; if (c == '/' || c == CommandPrefix.CHAR || c == CommandPrefix.LEGACY) return c;
return 0; return 0;
} }
return 0; return 0;

View file

@ -9,6 +9,7 @@ package dev.loki.lovisual.features.gui.chat;
import com.mojang.brigadier.suggestion.Suggestion; import com.mojang.brigadier.suggestion.Suggestion;
import dev.loki.lovisual.features.command.CommandManager; import dev.loki.lovisual.features.command.CommandManager;
import dev.loki.lovisual.features.command.CommandPrefix;
import dev.loki.lovisual.mixins.accessors.ChatScreenAccessor; import dev.loki.lovisual.mixins.accessors.ChatScreenAccessor;
import dev.loki.lovisual.util.screen.ClientScreen; import dev.loki.lovisual.util.screen.ClientScreen;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@ -43,7 +44,7 @@ public final class BetterChatTabHandler {
if (field == null) return false; if (field == null) return false;
String text = field.getValue(); String text = field.getValue();
if (text == null || text.isEmpty()) return false; if (text == null || text.isEmpty()) return false;
if (BetterChatSuggestionState.commandPrefix(text) != '@') return false; if (BetterChatSuggestionState.commandPrefix(text) != CommandPrefix.CHAR && BetterChatSuggestionState.commandPrefix(text) != CommandPrefix.LEGACY) return false;
int cursor = field.getCursorPosition(); int cursor = field.getCursorPosition();
List<Suggestion> suggestions = CommandManager.suggest(text, cursor); List<Suggestion> suggestions = CommandManager.suggest(text, cursor);

View file

@ -50,7 +50,9 @@ public enum MenuSettingsResolver {
new StaticHudCard("vanilla_bar", "Bar"), new StaticHudCard("vanilla_bar", "Bar"),
new StaticHudCard("vanilla_health", "Health"), new StaticHudCard("vanilla_health", "Health"),
new StaticHudCard("dynamic_island", "Dynamic Island"), new StaticHudCard("dynamic_island", "Dynamic Island"),
new StaticHudCard("tab_list", "Tab List") new StaticHudCard("tab_list", "Tab List"),
new StaticHudCard("reach_display", "Reach"),
new StaticHudCard("watermark", "Watermark")
); );
private static final SettingOwner RELATIONS_OWNER = private static final SettingOwner RELATIONS_OWNER =
new StaticOwner("relations_color", () -> PlayerRelations.get().save()); new StaticOwner("relations_color", () -> PlayerRelations.get().save());

View file

@ -41,7 +41,6 @@ public final class SettingsPanelComponent {
private final List<SettingRow> rows = new ArrayList<>(); private final List<SettingRow> rows = new ArrayList<>();
private final List<SettingHit> hits = new ArrayList<>(); private final List<SettingHit> hits = new ArrayList<>();
private final SettingRenderSurface renderSurface; private final SettingRenderSurface renderSurface;
private final Minecraft mc = Minecraft.getInstance();
private String targetId = ""; private String targetId = "";
private String title = "Settings"; private String title = "Settings";
private boolean hudContext = false; private boolean hudContext = false;
@ -487,6 +486,7 @@ public final class SettingsPanelComponent {
} }
private void renderHudEditorHints(float fallbackX, float fallbackY, float fallbackW, float fallbackH, float scale, float alpha) { private void renderHudEditorHints(float fallbackX, float fallbackY, float fallbackW, float fallbackH, float scale, float alpha) {
Minecraft mc = Minecraft.getInstance();
MainConfig config = MainConfig.get(); MainConfig config = MainConfig.get();
if (!hudContext || !config.isClickGuiHudEditorHintsEnabled()) return; if (!hudContext || !config.isClickGuiHudEditorHintsEnabled()) return;
@ -515,6 +515,7 @@ public final class SettingsPanelComponent {
} }
private void renderSettingsPanelHints(float fallbackX, float fallbackY, float fallbackW, float fallbackH, float scale, float alpha) { private void renderSettingsPanelHints(float fallbackX, float fallbackY, float fallbackW, float fallbackH, float scale, float alpha) {
Minecraft mc = Minecraft.getInstance();
MainConfig config = MainConfig.get(); MainConfig config = MainConfig.get();
if (hudContext || !isActive() || !config.isClickGuiHintsEnabled()) return; if (hudContext || !isActive() || !config.isClickGuiHintsEnabled()) return;

View file

@ -59,7 +59,6 @@ final class OnlineRelationPlayerPickerComponent {
private static final long DYNAMIC_REFRESH_MS = 250L; private static final long DYNAMIC_REFRESH_MS = 250L;
private static final String I18N = "clickgui.settings.relations.online_picker."; private static final String I18N = "clickgui.settings.relations.online_picker.";
private final Minecraft mc = Minecraft.getInstance();
private final Consumer<String> statusSink; private final Consumer<String> statusSink;
private final List<PlayerRow> rows = new ArrayList<>(); private final List<PlayerRow> rows = new ArrayList<>();
private final List<RowHit> rowHits = new ArrayList<>(); private final List<RowHit> rowHits = new ArrayList<>();
@ -452,6 +451,7 @@ final class OnlineRelationPlayerPickerComponent {
} }
private void refreshIfNeeded() { private void refreshIfNeeded() {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.getConnection() == null) return; if (mc == null || mc.getConnection() == null) return;
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
int onlineCount = onlineCount(); int onlineCount = onlineCount();
@ -483,10 +483,12 @@ final class OnlineRelationPlayerPickerComponent {
} }
private int onlineCount() { private int onlineCount() {
Minecraft mc = Minecraft.getInstance();
return mc != null && mc.getConnection() != null ? mc.getConnection().getListedOnlinePlayers().size() : 0; return mc != null && mc.getConnection() != null ? mc.getConnection().getListedOnlinePlayers().size() : 0;
} }
private void collectRows() { private void collectRows() {
Minecraft mc = Minecraft.getInstance();
rows.clear(); rows.clear();
if (mc == null || mc.getConnection() == null) { if (mc == null || mc.getConnection() == null) {
rowsVersion++; rowsVersion++;
@ -520,6 +522,7 @@ final class OnlineRelationPlayerPickerComponent {
} }
private void refreshDynamicRows() { private void refreshDynamicRows() {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.getConnection() == null || rows.isEmpty()) return; if (mc == null || mc.getConnection() == null || rows.isEmpty()) return;
boolean changed = false; boolean changed = false;
for (int i = 0; i < rows.size(); i++) { for (int i = 0; i < rows.size(); i++) {
@ -546,6 +549,7 @@ final class OnlineRelationPlayerPickerComponent {
} }
private long structureSignature() { private long structureSignature() {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.getConnection() == null) return 0L; if (mc == null || mc.getConnection() == null) return 0L;
long h = 0x6A09E667F3BCC909L; long h = 0x6A09E667F3BCC909L;
for (PlayerInfo info : mc.getConnection().getListedOnlinePlayers()) { for (PlayerInfo info : mc.getConnection().getListedOnlinePlayers()) {
@ -567,6 +571,7 @@ final class OnlineRelationPlayerPickerComponent {
} }
private Component displayName(PlayerInfo info, String fallback) { private Component displayName(PlayerInfo info, String fallback) {
Minecraft mc = Minecraft.getInstance();
Component display = null; Component display = null;
if (mc != null && mc.gui != null && mc.gui.hud != null && mc.gui.hud.getTabList() != null) { if (mc != null && mc.gui != null && mc.gui.hud != null && mc.gui.hud.getTabList() != null) {
display = mc.gui.hud.getTabList().getNameForDisplay(info); display = mc.gui.hud.getTabList().getNameForDisplay(info);

View file

@ -31,7 +31,6 @@ public final class ThemeEditorPreviewComponent {
private static final float DESIGN_W = 320f; private static final float DESIGN_W = 320f;
private static final float DESIGN_H = 190f; private static final float DESIGN_H = 190f;
private final Minecraft mc = Minecraft.getInstance();
private final UiScriptModuleHandle previewModuleHandle = HudScriptLayouts.handle(PREVIEW_LAYOUT_ID); private final UiScriptModuleHandle previewModuleHandle = HudScriptLayouts.handle(PREVIEW_LAYOUT_ID);
private final CachedUiScriptRuntime previewRuntime = new CachedUiScriptRuntime(HudScriptLayouts.runtimeReporter()); private final CachedUiScriptRuntime previewRuntime = new CachedUiScriptRuntime(HudScriptLayouts.runtimeReporter());
private float previewAnim; private float previewAnim;
@ -93,6 +92,7 @@ public final class ThemeEditorPreviewComponent {
float panelH, float panelH,
float unit, float unit,
float alpha) { float alpha) {
Minecraft mc = Minecraft.getInstance();
if (previewModuleHandle.isRuntimeBlocked() || mc == null || mc.getResourceManager() == null) return; if (previewModuleHandle.isRuntimeBlocked() || mc == null || mc.getResourceManager() == null) return;
HudScriptLayouts.pollReloadCombo(mc); HudScriptLayouts.pollReloadCombo(mc);
if (previewModuleHandle.consumeChanged()) { if (previewModuleHandle.consumeChanged()) {
@ -131,6 +131,7 @@ public final class ThemeEditorPreviewComponent {
} }
private UiScriptModule ensurePreviewModule() { private UiScriptModule ensurePreviewModule() {
Minecraft mc = Minecraft.getInstance();
if (!previewModuleHandle.ensureLoaded(mc.getResourceManager())) { if (!previewModuleHandle.ensureLoaded(mc.getResourceManager())) {
HudScriptLayouts.reportLoadError(previewModuleHandle); HudScriptLayouts.reportLoadError(previewModuleHandle);
return null; return null;

View file

@ -72,7 +72,6 @@ public final class Admins extends DraggableHudElement {
private static final String EFFECT_NONE = "None"; private static final String EFFECT_NONE = "None";
private static final String EFFECT_BLUR = "Blur"; private static final String EFFECT_BLUR = "Blur";
private final Minecraft mc = Minecraft.getInstance();
private final HudGlobalConfig hud = HudGlobalConfig.get(); private final HudGlobalConfig hud = HudGlobalConfig.get();
private final ScriptedListHudPanel scriptedPanel = new ScriptedListHudPanel(); private final ScriptedListHudPanel scriptedPanel = new ScriptedListHudPanel();
private final NumberValue<Double> scaleValue = private final NumberValue<Double> scaleValue =
@ -176,6 +175,7 @@ public final class Admins extends DraggableHudElement {
int screenW, int screenW,
int screenH) { int screenH) {
boolean forceVisible = DraggableHudElementRegistry.isForceVisible(); boolean forceVisible = DraggableHudElementRegistry.isForceVisible();
Minecraft mc = Minecraft.getInstance();
if (mc == null || (mc.player == null && !forceVisible)) { if (mc == null || (mc.player == null && !forceVisible)) {
width = 0.0f; width = 0.0f;
height = 0.0f; height = 0.0f;
@ -450,6 +450,7 @@ public final class Admins extends DraggableHudElement {
} }
private Identifier resolvePreviewSkin() { private Identifier resolvePreviewSkin() {
Minecraft mc = Minecraft.getInstance();
if (mc != null && mc.player != null) { if (mc != null && mc.player != null) {
return PlayerSkinResolver.resolveProfileSkin(mc.player.getGameProfile()); return PlayerSkinResolver.resolveProfileSkin(mc.player.getGameProfile());
} }
@ -609,6 +610,7 @@ public final class Admins extends DraggableHudElement {
} }
private AbstractClientPlayer resolvePlayerByName(String name) { private AbstractClientPlayer resolvePlayerByName(String name) {
Minecraft mc = Minecraft.getInstance();
if (name == null || name.isEmpty() || mc == null || mc.level == null) { if (name == null || name.isEmpty() || mc == null || mc.level == null) {
return null; return null;
} }
@ -631,6 +633,7 @@ public final class Admins extends DraggableHudElement {
return PlayerSkinResolver.resolveProfileSkin(player.getGameProfile()); return PlayerSkinResolver.resolveProfileSkin(player.getGameProfile());
} }
Minecraft mc = Minecraft.getInstance();
if (mc != null && mc.getConnection() != null) { if (mc != null && mc.getConnection() != null) {
for (PlayerInfo entry : mc.getConnection().getOnlinePlayers()) { for (PlayerInfo entry : mc.getConnection().getOnlinePlayers()) {
var profile = entry.getProfile(); var profile = entry.getProfile();

View file

@ -46,7 +46,6 @@ public final class Armor extends DraggableHudElement {
private static final float DEFAULT_MARGIN = 16f; private static final float DEFAULT_MARGIN = 16f;
private static final float DIVIDER_THICKNESS = 0.5f; private static final float DIVIDER_THICKNESS = 0.5f;
private final Minecraft mc = Minecraft.getInstance();
private final HudGlobalConfig hud = HudGlobalConfig.get(); private final HudGlobalConfig hud = HudGlobalConfig.get();
private final NumberValue<Double> scaleValue = num("armor_scale", 3.68, 0.5, 5.0); private final NumberValue<Double> scaleValue = num("armor_scale", 3.68, 0.5, 5.0);
private final EnumValue<LayoutMode> layoutMode = private final EnumValue<LayoutMode> layoutMode =
@ -110,6 +109,7 @@ public final class Armor extends DraggableHudElement {
float tickDelta, float tickDelta,
int screenW, int screenW,
int screenH) { int screenH) {
Minecraft mc = Minecraft.getInstance();
layoutReady = false; layoutReady = false;
boolean preview = DraggableHudElementRegistry.isForceVisible(); boolean preview = DraggableHudElementRegistry.isForceVisible();
float scale = HudScale.scale(screenW, screenH) * scaleValue.get().floatValue(); float scale = HudScale.scale(screenW, screenH) * scaleValue.get().floatValue();
@ -187,6 +187,7 @@ public final class Armor extends DraggableHudElement {
float tickDelta, float tickDelta,
int screenW, int screenW,
int screenH) { int screenH) {
Minecraft mc = Minecraft.getInstance();
if (!layoutReady || mc == null || mc.player == null || ctx == null) return; if (!layoutReady || mc == null || mc.player == null || ctx == null) return;
ItemStack[] stacks = cachedStacks != null ? cachedStacks : getArmorStacks(); ItemStack[] stacks = cachedStacks != null ? cachedStacks : getArmorStacks();
@ -216,6 +217,7 @@ public final class Armor extends DraggableHudElement {
} }
private ItemStack[] getArmorStacks() { private ItemStack[] getArmorStacks() {
Minecraft mc = Minecraft.getInstance();
return new ItemStack[]{ return new ItemStack[]{
mc.player.getItemBySlot(EquipmentSlot.HEAD), mc.player.getItemBySlot(EquipmentSlot.HEAD),
mc.player.getItemBySlot(EquipmentSlot.CHEST), mc.player.getItemBySlot(EquipmentSlot.CHEST),
@ -261,6 +263,7 @@ public final class Armor extends DraggableHudElement {
} }
private boolean shouldShowWhenEmpty() { private boolean shouldShowWhenEmpty() {
Minecraft mc = Minecraft.getInstance();
if (hud == null) return false; if (hud == null) return false;
HudGlobalConfig.HeaderMode mode = hud.getHeaderMode(); HudGlobalConfig.HeaderMode mode = hud.getHeaderMode();
if (mode == HudGlobalConfig.HeaderMode.ALWAYS) return true; if (mode == HudGlobalConfig.HeaderMode.ALWAYS) return true;

View file

@ -39,7 +39,6 @@ public final class BetterChat extends DraggableHudElement {
defaultLayout(16.0f, 485.12f); defaultLayout(16.0f, 485.12f);
} }
private final Minecraft mc = Minecraft.getInstance();
private final NumberValue<Double> widthRatio = num("width_ratio", 0.54, 0.2, 0.8); private final NumberValue<Double> widthRatio = num("width_ratio", 0.54, 0.2, 0.8);
private final NumberValue<Double> fontScale = num("font_scale", 1.36, 0.6, 1.4); private final NumberValue<Double> fontScale = num("font_scale", 1.36, 0.6, 1.4);
private final NumberValue<Integer> maxLines = num("lines", 12, 4, 20); private final NumberValue<Integer> maxLines = num("lines", 12, 4, 20);

View file

@ -94,7 +94,6 @@ public final class Cooldowns extends DraggableHudElement {
}; };
private static ItemStack[] previewStacks; private static ItemStack[] previewStacks;
private final Minecraft mc = Minecraft.getInstance();
private final HudGlobalConfig hud = HudGlobalConfig.get(); private final HudGlobalConfig hud = HudGlobalConfig.get();
private final ScriptedListHudPanel scriptedPanel = new ScriptedListHudPanel(); private final ScriptedListHudPanel scriptedPanel = new ScriptedListHudPanel();
private final NumberValue<Double> scaleValue = private final NumberValue<Double> scaleValue =
@ -207,6 +206,7 @@ public final class Cooldowns extends DraggableHudElement {
float tickDelta, float tickDelta,
int screenW, int screenW,
int screenH) { int screenH) {
Minecraft mc = Minecraft.getInstance();
foregroundReady = false; foregroundReady = false;
pendingItemTasks.clear(); pendingItemTasks.clear();
boolean forceVisible = DraggableHudElementRegistry.isForceVisible(); boolean forceVisible = DraggableHudElementRegistry.isForceVisible();
@ -475,6 +475,7 @@ public final class Cooldowns extends DraggableHudElement {
} }
private List<Row> collectRows() { private List<Row> collectRows() {
Minecraft mc = Minecraft.getInstance();
List<Row> rows = new ArrayList<>(); List<Row> rows = new ArrayList<>();
if (mc != null && mc.player != null) { if (mc != null && mc.player != null) {
var manager = mc.player.getCooldowns(); var manager = mc.player.getCooldowns();

View file

@ -69,7 +69,6 @@ public final class HudNotifier extends DraggableHudElement implements ModuleStat
private final ModeValue soundMode = new ModeValue("sound_mode", "2", "1", "2", "3"); private final ModeValue soundMode = new ModeValue("sound_mode", "2", "1", "2", "3");
private final NumberValue<Double> soundVolume = new NumberValue<>("sound_volume", 0.47, 0.05, 1.0); private final NumberValue<Double> soundVolume = new NumberValue<>("sound_volume", 0.47, 0.05, 1.0);
private final Minecraft mc = Minecraft.getInstance();
private final List<Toast> toasts = new ArrayList<>(); private final List<Toast> toasts = new ArrayList<>();
private final RenderColor colorTmp = new RenderColor(0xFFFFFFFF); private final RenderColor colorTmp = new RenderColor(0xFFFFFFFF);
private boolean stackLayoutReady; private boolean stackLayoutReady;
@ -171,6 +170,7 @@ public final class HudNotifier extends DraggableHudElement implements ModuleStat
private void renderEngineInternal(Renderer2D renderer, private void renderEngineInternal(Renderer2D renderer,
TextRenderer textRenderer) { TextRenderer textRenderer) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.level == null || mc.player == null) return; if (mc == null || mc.level == null || mc.player == null) return;
@ -497,6 +497,7 @@ public final class HudNotifier extends DraggableHudElement implements ModuleStat
} }
private MessengerSide getSide() { private MessengerSide getSide() {
Minecraft mc = Minecraft.getInstance();
Window window = mc == null ? null : mc.getWindow(); Window window = mc == null ? null : mc.getWindow();
if (window == null) return MessengerSide.RIGHT; if (window == null) return MessengerSide.RIGHT;
int fbw = Math.max(1, window.getWidth()); int fbw = Math.max(1, window.getWidth());

View file

@ -76,7 +76,6 @@ public final class Inventory extends DraggableHudElement {
private static final String EFFECT_NONE = "None"; private static final String EFFECT_NONE = "None";
private static final String EFFECT_BLUR = "Blur"; private static final String EFFECT_BLUR = "Blur";
private final Minecraft mc = Minecraft.getInstance();
private final HudGlobalConfig hud = HudGlobalConfig.get(); private final HudGlobalConfig hud = HudGlobalConfig.get();
private final ScriptedInventoryHudPanel scriptedPanel = new ScriptedInventoryHudPanel(); private final ScriptedInventoryHudPanel scriptedPanel = new ScriptedInventoryHudPanel();
@ -170,6 +169,7 @@ public final class Inventory extends DraggableHudElement {
@Override @Override
public void onTick() { public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (!toggleInit) { if (!toggleInit) {
if (mc == null || mc.getWindow() == null) { if (mc == null || mc.getWindow() == null) {
return; return;
@ -204,6 +204,7 @@ public final class Inventory extends DraggableHudElement {
float tickDelta, float tickDelta,
int screenW, int screenW,
int screenH) { int screenH) {
Minecraft mc = Minecraft.getInstance();
layoutReady = false; layoutReady = false;
hitboxActive = false; hitboxActive = false;
pendingItemTasks.clear(); pendingItemTasks.clear();
@ -374,6 +375,7 @@ public final class Inventory extends DraggableHudElement {
} }
private List<ItemStack> collectStacks() { private List<ItemStack> collectStacks() {
Minecraft mc = Minecraft.getInstance();
List<ItemStack> stacks = new ArrayList<>(LAST_INV_SLOT - FIRST_INV_SLOT); List<ItemStack> stacks = new ArrayList<>(LAST_INV_SLOT - FIRST_INV_SLOT);
if (mc == null || mc.player == null) { if (mc == null || mc.player == null) {
return stacks; return stacks;
@ -450,6 +452,7 @@ public final class Inventory extends DraggableHudElement {
} }
private void initToggleState() { private void initToggleState() {
Minecraft mc = Minecraft.getInstance();
toggleArmed = true; toggleArmed = true;
String combo = toggleBind.get(); String combo = toggleBind.get();
if (combo != null && !combo.isBlank() && !"NONE".equalsIgnoreCase(combo) if (combo != null && !combo.isBlank() && !"NONE".equalsIgnoreCase(combo)

View file

@ -54,7 +54,6 @@ public final class Itemizer extends DraggableHudElement {
public static Itemizer INSTANCE; public static Itemizer INSTANCE;
private final Minecraft mc = Minecraft.getInstance();
private final UiScriptModuleHandle moduleHandle = HudScriptLayouts.handle(LAYOUT_ID); private final UiScriptModuleHandle moduleHandle = HudScriptLayouts.handle(LAYOUT_ID);
private final CachedUiScriptRuntime runtime = new CachedUiScriptRuntime(HudScriptLayouts.runtimeReporter()); private final CachedUiScriptRuntime runtime = new CachedUiScriptRuntime(HudScriptLayouts.runtimeReporter());
private final LinkedHashMap<ItemizerEvent, Entry> entries = new LinkedHashMap<>(); private final LinkedHashMap<ItemizerEvent, Entry> entries = new LinkedHashMap<>();
@ -334,6 +333,7 @@ public final class Itemizer extends DraggableHudElement {
} }
private UiScriptModule ensureModule() { private UiScriptModule ensureModule() {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.getResourceManager() == null) return null; if (mc == null || mc.getResourceManager() == null) return null;
HudScriptLayouts.pollReloadCombo(mc); HudScriptLayouts.pollReloadCombo(mc);
if (moduleHandle.consumeChanged()) { if (moduleHandle.consumeChanged()) {

View file

@ -72,7 +72,6 @@ public final class MediaPlayer extends DraggableHudElement {
private static final int ICON_NEXT = 0xEA05; private static final int ICON_NEXT = 0xEA05;
private static final int ICON_SHUFFLE = 0xEA06; private static final int ICON_SHUFFLE = 0xEA06;
private final Minecraft mc = Minecraft.getInstance();
private final HudGlobalConfig hud = HudGlobalConfig.get(); private final HudGlobalConfig hud = HudGlobalConfig.get();
private final MediaSessionService mediaService = MediaSessionService.get(); private final MediaSessionService mediaService = MediaSessionService.get();
private final NumberValue<Double> scale = private final NumberValue<Double> scale =
@ -209,6 +208,7 @@ public final class MediaPlayer extends DraggableHudElement {
float tickDelta, float tickDelta,
int screenW, int screenW,
int screenH) { int screenH) {
Minecraft mc = Minecraft.getInstance();
if (mc == null) return; if (mc == null) return;
boolean chatOpen = ClientScreen.current() instanceof ChatScreen; boolean chatOpen = ClientScreen.current() instanceof ChatScreen;
boolean preview = DraggableHudElementRegistry.isForceVisible(); boolean preview = DraggableHudElementRegistry.isForceVisible();
@ -539,6 +539,7 @@ public final class MediaPlayer extends DraggableHudElement {
} }
private void updateHover(boolean chatOpen, boolean showShuffle, boolean showRepeat) { private void updateHover(boolean chatOpen, boolean showShuffle, boolean showRepeat) {
Minecraft mc = Minecraft.getInstance();
if (!chatOpen) { if (!chatOpen) {
playHover = lerp(playHover, 0f, 0.2f); playHover = lerp(playHover, 0f, 0.2f);
shuffleHover = lerp(shuffleHover, 0f, 0.2f); shuffleHover = lerp(shuffleHover, 0f, 0.2f);

View file

@ -56,7 +56,6 @@ public final class Radar extends DraggableHudElement {
Field.PING, Field.PING,
Field.NONE Field.NONE
}; };
private final Minecraft mc = Minecraft.getInstance();
private final HudGlobalConfig hud = HudGlobalConfig.get(); private final HudGlobalConfig hud = HudGlobalConfig.get();
private final EnumValue<Mode> mode = private final EnumValue<Mode> mode =
new EnumValue<>("radar_mode", Mode.RECT, Mode.RECT, Mode.TEXT); new EnumValue<>("radar_mode", Mode.RECT, Mode.RECT, Mode.TEXT);
@ -135,6 +134,7 @@ public final class Radar extends DraggableHudElement {
float tickDelta, float tickDelta,
int screenW, int screenW,
int screenH) { int screenH) {
Minecraft mc = Minecraft.getInstance();
boolean preview = DraggableHudElementRegistry.isForceVisible(); boolean preview = DraggableHudElementRegistry.isForceVisible();
if (mc == null && !preview) { if (mc == null && !preview) {
width = 0f; width = 0f;
@ -195,6 +195,7 @@ public final class Radar extends DraggableHudElement {
} }
private void renderBlips(Renderer2D renderer, float tickDelta, float cx, float cy, float half, float scale) { private void renderBlips(Renderer2D renderer, float tickDelta, float cx, float cy, float half, float scale) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null || mc.level == null) return; if (mc == null || mc.player == null || mc.level == null) return;
float blip = Math.max(2.5f, BLIP_SIZE * scale); float blip = Math.max(2.5f, BLIP_SIZE * scale);
@ -225,6 +226,7 @@ public final class Radar extends DraggableHudElement {
} }
private void renderTextMode(TextRenderer textRenderer, int screenW, int screenH, boolean preview) { private void renderTextMode(TextRenderer textRenderer, int screenW, int screenH, boolean preview) {
Minecraft mc = Minecraft.getInstance();
boolean ready = mc != null && mc.player != null && mc.level != null; boolean ready = mc != null && mc.player != null && mc.level != null;
if (!ready && !preview) { if (!ready && !preview) {
width = 0f; width = 0f;
@ -290,6 +292,7 @@ public final class Radar extends DraggableHudElement {
} }
private void appendField(StringBuilder out, Field field, Player player) { private void appendField(StringBuilder out, Field field, Player player) {
Minecraft mc = Minecraft.getInstance();
if (field == null || field == Field.NONE) return; if (field == null || field == Field.NONE) return;
if (out.length() > 0) out.append(' '); if (out.length() > 0) out.append(' ');
switch (field) { switch (field) {
@ -317,6 +320,7 @@ public final class Radar extends DraggableHudElement {
} }
private int getPlayerPing(Player player) { private int getPlayerPing(Player player) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.getConnection() == null) return -1; if (mc == null || mc.getConnection() == null) return -1;
PlayerInfo entry = mc.getConnection().getPlayerInfo(player.getUUID()); PlayerInfo entry = mc.getConnection().getPlayerInfo(player.getUUID());
return entry != null ? entry.getLatency() : -1; return entry != null ? entry.getLatency() : -1;

View file

@ -77,7 +77,6 @@ public final class Scoreboard extends DraggableHudElement {
private static final String EFFECT_NONE = "None"; private static final String EFFECT_NONE = "None";
private static final String EFFECT_BLUR = "Blur"; private static final String EFFECT_BLUR = "Blur";
private final Minecraft mc = Minecraft.getInstance();
private final HudGlobalConfig hud = HudGlobalConfig.get(); private final HudGlobalConfig hud = HudGlobalConfig.get();
private final KeyBindValue toggleBind = private final KeyBindValue toggleBind =
@ -582,6 +581,7 @@ public final class Scoreboard extends DraggableHudElement {
@Override @Override
public void onTick() { public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (!toggleInit) { if (!toggleInit) {
if (mc == null || mc.getWindow() == null) { if (mc == null || mc.getWindow() == null) {
return; return;
@ -616,6 +616,7 @@ public final class Scoreboard extends DraggableHudElement {
float tickDelta, float tickDelta,
int screenW, int screenW,
int screenH) { int screenH) {
Minecraft mc = Minecraft.getInstance();
boolean preview = DraggableHudElementRegistry.isForceVisible(); boolean preview = DraggableHudElementRegistry.isForceVisible();
if (mc == null || (mc.level == null && !preview)) { if (mc == null || (mc.level == null && !preview)) {
width = 0.0f; width = 0.0f;
@ -758,6 +759,7 @@ public final class Scoreboard extends DraggableHudElement {
} }
private Objective resolveObjective() { private Objective resolveObjective() {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.level == null || mc.player == null) { if (mc == null || mc.level == null || mc.player == null) {
return null; return null;
} }
@ -824,6 +826,7 @@ public final class Scoreboard extends DraggableHudElement {
} }
private void initToggleState() { private void initToggleState() {
Minecraft mc = Minecraft.getInstance();
toggleArmed = true; toggleArmed = true;
String combo = toggleBind.get(); String combo = toggleBind.get();
if (combo != null && !combo.isBlank() && !"NONE".equalsIgnoreCase(combo) if (combo != null && !combo.isBlank() && !"NONE".equalsIgnoreCase(combo)

View file

@ -64,7 +64,6 @@ public final class SpeedBps extends DraggableHudElement implements ScriptableHud
private static final long SAMPLE_INTERVAL_MS = 75L; private static final long SAMPLE_INTERVAL_MS = 75L;
private static final int SAMPLE_COUNT = 20; private static final int SAMPLE_COUNT = 20;
private final Minecraft mc = Minecraft.getInstance();
private final HudGlobalConfig hud = HudGlobalConfig.get(); private final HudGlobalConfig hud = HudGlobalConfig.get();
private final BooleanValue useBps = bool("speed_bps_bps", true); private final BooleanValue useBps = bool("speed_bps_bps", true);
private final BooleanValue average = bool("speed_bps_average", false); private final BooleanValue average = bool("speed_bps_average", false);
@ -140,6 +139,7 @@ public final class SpeedBps extends DraggableHudElement implements ScriptableHud
float tickDelta, float tickDelta,
int screenW, int screenW,
int screenH) { int screenH) {
Minecraft mc = Minecraft.getInstance();
boolean preview = DraggableHudElementRegistry.isForceVisible(); boolean preview = DraggableHudElementRegistry.isForceVisible();
if (mc == null && !preview) { if (mc == null && !preview) {
width = 0f; width = 0f;
@ -252,6 +252,7 @@ public final class SpeedBps extends DraggableHudElement implements ScriptableHud
} }
private float currentBps() { private float currentBps() {
Minecraft mc = Minecraft.getInstance();
return PlayerSpeedUtil.getBps(mc != null ? mc.player : null); return PlayerSpeedUtil.getBps(mc != null ? mc.player : null);
} }

View file

@ -17,7 +17,6 @@ import dev.loki.lovisual.render.engine.math.HudScale;
import dev.loki.lovisual.render.engine.renderer.Renderer2D; import dev.loki.lovisual.render.engine.renderer.Renderer2D;
import dev.loki.lovisual.render.engine.text.TextRenderer; import dev.loki.lovisual.render.engine.text.TextRenderer;
import dev.loki.lovisual.util.pvp.opponents.TotemPopCounter; import dev.loki.lovisual.util.pvp.opponents.TotemPopCounter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
@ -119,7 +118,6 @@ public final class TargetHud extends DraggableHudElement {
static final ConcurrentHashMap<Integer, Long> HIT_FLASH_MS_BY_ID = new ConcurrentHashMap<>(); static final ConcurrentHashMap<Integer, Long> HIT_FLASH_MS_BY_ID = new ConcurrentHashMap<>();
final Minecraft mc = Minecraft.getInstance();
final HudGlobalConfig hud = HudGlobalConfig.get(); final HudGlobalConfig hud = HudGlobalConfig.get();
final NumberValue<Double> scale = final NumberValue<Double> scale =

View file

@ -15,6 +15,7 @@ import dev.loki.lovisual.render.engine.text.FontInfo;
import dev.loki.lovisual.render.engine.text.Fonts; import dev.loki.lovisual.render.engine.text.Fonts;
import dev.loki.lovisual.render.engine.text.TextRenderer; import dev.loki.lovisual.render.engine.text.TextRenderer;
import dev.loki.lovisual.util.player.PlayerHealthResolver; import dev.loki.lovisual.util.player.PlayerHealthResolver;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import static dev.loki.lovisual.features.gui.hud.draggable.impl.TargetHud.*; import static dev.loki.lovisual.features.gui.hud.draggable.impl.TargetHud.*;
@ -36,6 +37,7 @@ final class TargetHudContentRenderer {
float alphaFactor, float alphaFactor,
long now, long now,
float dt) { float dt) {
Minecraft mc = Minecraft.getInstance();
float contentX = x + (FACE_X + FACE_SIZE + CONTENT_GAP + CONTENT_X_OFFSET) * scaleFactor; float contentX = x + (FACE_X + FACE_SIZE + CONTENT_GAP + CONTENT_X_OFFSET) * scaleFactor;
float nameY = y + (NAME_Y + CONTENT_Y_OFFSET) * scaleFactor; float nameY = y + (NAME_Y + CONTENT_Y_OFFSET) * scaleFactor;
@ -63,9 +65,9 @@ final class TargetHudContentRenderer {
element.absorptionAnimation = AnimationUtility.approach(element.absorptionAnimation, targetAbsorptionFraction, dt, BAR_SPEED); element.absorptionAnimation = AnimationUtility.approach(element.absorptionAnimation, targetAbsorptionFraction, dt, BAR_SPEED);
element.absorptionAnimation = AnimationUtility.snap(element.absorptionAnimation, targetAbsorptionFraction, PROGRESS_SNAP_EPSILON); element.absorptionAnimation = AnimationUtility.snap(element.absorptionAnimation, targetAbsorptionFraction, PROGRESS_SNAP_EPSILON);
boolean selfTarget = target == element.mc.player; boolean selfTarget = target == mc.player;
if (!selfTarget) { if (!selfTarget) {
float targetDistance = element.mc.player != null ? element.mc.player.distanceTo(target) : 0.0f; float targetDistance = mc.player != null ? mc.player.distanceTo(target) : 0.0f;
element.displayedDistance = AnimationUtility.approach(element.displayedDistance, targetDistance, dt, DISTANCE_SPEED); element.displayedDistance = AnimationUtility.approach(element.displayedDistance, targetDistance, dt, DISTANCE_SPEED);
element.displayedDistance = AnimationUtility.snap(element.displayedDistance, targetDistance, 0.01f); element.displayedDistance = AnimationUtility.snap(element.displayedDistance, targetDistance, 0.01f);
} }

View file

@ -14,6 +14,7 @@ import dev.loki.lovisual.render.engine.text.FontInfo;
import dev.loki.lovisual.render.engine.text.Fonts; import dev.loki.lovisual.render.engine.text.Fonts;
import dev.loki.lovisual.render.engine.text.TextRenderer; import dev.loki.lovisual.render.engine.text.TextRenderer;
import dev.loki.lovisual.util.player.effect.StatusEffectView; import dev.loki.lovisual.util.player.effect.StatusEffectView;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
@ -141,14 +142,15 @@ final class TargetHudEffectsRenderer {
} }
private static List<MobEffectInstance> collectTargetEffects(TargetHud element, LivingEntity target, boolean preview) { private static List<MobEffectInstance> collectTargetEffects(TargetHud element, LivingEntity target, boolean preview) {
Minecraft mc = Minecraft.getInstance();
if (target == null) { if (target == null) {
return List.of(); return List.of();
} }
if (target == element.mc.player && !element.effectsSelf.get() && !preview) { if (target == mc.player && !element.effectsSelf.get() && !preview) {
return List.of(); return List.of();
} }
if (target instanceof Player player) { if (target instanceof Player player) {
StatusEffectView.sync(element.mc); StatusEffectView.sync(mc);
List<MobEffectInstance> effects = StatusEffectView.collectHudEffects(player); List<MobEffectInstance> effects = StatusEffectView.collectHudEffects(player);
if (!effects.isEmpty() || !preview) { if (!effects.isEmpty() || !preview) {
return effects; return effects;

View file

@ -17,6 +17,7 @@ import dev.loki.lovisual.render.engine.text.Fonts;
import dev.loki.lovisual.render.engine.text.TextRenderer; import dev.loki.lovisual.render.engine.text.TextRenderer;
import dev.loki.lovisual.util.player.PlayerSkinResolver; import dev.loki.lovisual.util.player.PlayerSkinResolver;
import dev.loki.lovisual.util.pvp.opponents.TotemPopSnapshot; import dev.loki.lovisual.util.pvp.opponents.TotemPopSnapshot;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
@ -112,6 +113,7 @@ final class TargetHudFaceRenderer {
} }
static void drawPlaceholderFace(TargetHud element, Renderer2D renderer, TextRenderer textRenderer, LivingEntity target, float x, float y, float size, float radius, float alphaFactor, float hurtPercent) { static void drawPlaceholderFace(TargetHud element, Renderer2D renderer, TextRenderer textRenderer, LivingEntity target, float x, float y, float size, float radius, float alphaFactor, float hurtPercent) {
Minecraft mc = Minecraft.getInstance();
int light = HudRenderUtil.scaleAlpha(HudRenderUtil.mixColor(element.uiBgSecondary, element.uiHealthEnd, 0.28f), alphaFactor); int light = HudRenderUtil.scaleAlpha(HudRenderUtil.mixColor(element.uiBgSecondary, element.uiHealthEnd, 0.28f), alphaFactor);
int dark = HudRenderUtil.scaleAlpha(HudRenderUtil.mixColor(element.uiBgPrimary, 0xFF000000, 0.22f), alphaFactor); int dark = HudRenderUtil.scaleAlpha(HudRenderUtil.mixColor(element.uiBgPrimary, 0xFF000000, 0.22f), alphaFactor);
renderer.roundedRectGradient(x, y, size, size, radius, 1.0f, light, dark, 90.0f); renderer.roundedRectGradient(x, y, size, size, radius, 1.0f, light, dark, 90.0f);
@ -125,8 +127,8 @@ final class TargetHudFaceRenderer {
String letter = name.isEmpty() ? "?" : name.substring(0, 1).toUpperCase(Locale.ROOT); String letter = name.isEmpty() ? "?" : name.substring(0, 1).toUpperCase(Locale.ROOT);
TextRenderer faceRenderer = Fonts.renderer("Inter", FontInfo.Type.Bold, textRenderer); TextRenderer faceRenderer = Fonts.renderer("Inter", FontInfo.Type.Bold, textRenderer);
float textScale = PLACEHOLDER_TEXT_SCALE * element.scale.get().floatValue() * HudScale.scale( float textScale = PLACEHOLDER_TEXT_SCALE * element.scale.get().floatValue() * HudScale.scale(
element.mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledWidth(),
element.mc.getWindow().getGuiScaledHeight() mc.getWindow().getGuiScaledHeight()
); );
faceRenderer.begin(textScale, false, false); faceRenderer.begin(textScale, false, false);
try { try {

View file

@ -18,6 +18,7 @@ import dev.loki.lovisual.render.engine.profiler.RenderProfiler2D;
import dev.loki.lovisual.render.engine.renderer.Renderer2D; import dev.loki.lovisual.render.engine.renderer.Renderer2D;
import dev.loki.lovisual.render.engine.text.TextRenderer; import dev.loki.lovisual.render.engine.text.TextRenderer;
import dev.loki.lovisual.util.pvp.opponents.TotemPopSnapshot; import dev.loki.lovisual.util.pvp.opponents.TotemPopSnapshot;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
@ -30,10 +31,11 @@ final class TargetHudRenderer {
} }
static void render(TargetHud element, Renderer2D renderer, TextRenderer textRenderer, GuiGraphicsExtractor ctx, float tickDelta, int screenW, int screenH) { static void render(TargetHud element, Renderer2D renderer, TextRenderer textRenderer, GuiGraphicsExtractor ctx, float tickDelta, int screenW, int screenH) {
Minecraft mc = Minecraft.getInstance();
try (ProfilerPhase.Scope ignored = ProfilerPhase.scope("2d:target_hud:main"); try (ProfilerPhase.Scope ignored = ProfilerPhase.scope("2d:target_hud:main");
RenderProfiler2D.Section ignoredSection = RenderProfiler2D.section("target_hud:main")) { RenderProfiler2D.Section ignoredSection = RenderProfiler2D.section("target_hud:main")) {
boolean preview = DraggableHudElementRegistry.isForceVisible(); boolean preview = DraggableHudElementRegistry.isForceVisible();
if (element.mc == null || (element.mc.player == null && !preview)) return; if (mc == null || (mc.player == null && !preview)) return;
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
LivingEntity target = TargetHudTargetResolver.resolveTarget(element, preview); LivingEntity target = TargetHudTargetResolver.resolveTarget(element, preview);
TargetHudTargetResolver.updateDisplayTarget(element, target, now); TargetHudTargetResolver.updateDisplayTarget(element, target, now);

View file

@ -12,6 +12,7 @@ import dev.loki.lovisual.util.pvp.opponents.TotemPopCounter;
import dev.loki.lovisual.util.pvp.opponents.TotemPopSnapshot; import dev.loki.lovisual.util.pvp.opponents.TotemPopSnapshot;
import dev.loki.lovisual.util.screen.ClientScreen; import dev.loki.lovisual.util.screen.ClientScreen;
import dev.loki.lovisual.util.target.TargetManager; import dev.loki.lovisual.util.target.TargetManager;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.ChatScreen; import net.minecraft.client.gui.screens.ChatScreen;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
@ -22,17 +23,19 @@ final class TargetHudTargetResolver {
} }
static LivingEntity resolveTarget(TargetHud element, boolean preview) { static LivingEntity resolveTarget(TargetHud element, boolean preview) {
Minecraft mc = Minecraft.getInstance();
LivingEntity target = TargetManager.getTarget(); LivingEntity target = TargetManager.getTarget();
if (target != null && element.playersOnly.get() && !(target instanceof Player)) { if (target != null && element.playersOnly.get() && !(target instanceof Player)) {
target = null; target = null;
} }
if (target == null && (preview || ClientScreen.current() instanceof ChatScreen)) { if (target == null && (preview || ClientScreen.current() instanceof ChatScreen)) {
target = element.mc.player; target = mc.player;
} }
return target; return target;
} }
static void updateDisplayTarget(TargetHud element, LivingEntity target, long now) { static void updateDisplayTarget(TargetHud element, LivingEntity target, long now) {
Minecraft mc = Minecraft.getInstance();
if (target != null) { if (target != null) {
if (element.displayTarget != target) { if (element.displayTarget != target) {
element.displayTarget = target; element.displayTarget = target;
@ -43,14 +46,14 @@ final class TargetHudTargetResolver {
element.healthAnimation = realHealth / baseMaxHealth; element.healthAnimation = realHealth / baseMaxHealth;
element.trailAnimation = element.healthAnimation; element.trailAnimation = element.healthAnimation;
element.absorptionAnimation = health.absorption() / baseMaxHealth; element.absorptionAnimation = health.absorption() / baseMaxHealth;
element.displayedDistance = element.mc.player != null ? element.mc.player.distanceTo(target) : 0.0f; element.displayedDistance = mc.player != null ? mc.player.distanceTo(target) : 0.0f;
element.waveStartMs = now; element.waveStartMs = now;
} }
element.lastSeenMs = now; element.lastSeenMs = now;
return; return;
} }
if (element.displayTarget == element.mc.player && !(ClientScreen.current() instanceof ChatScreen)) { if (element.displayTarget == mc.player && !(ClientScreen.current() instanceof ChatScreen)) {
element.lastSeenMs = 0L; element.lastSeenMs = 0L;
} }
} }

View file

@ -100,7 +100,6 @@ public final class DynamicIsland extends AbstractHudElement {
private static final int STATUS_PVP = 0xFFFF6368; private static final int STATUS_PVP = 0xFFFF6368;
private static final int PROGRESS_BG = 0x2AFFFFFF; private static final int PROGRESS_BG = 0x2AFFFFFF;
private final Minecraft mc = Minecraft.getInstance();
private final MediaSessionService mediaService = MediaSessionService.get(); private final MediaSessionService mediaService = MediaSessionService.get();
private final BooleanValue syncTheme = new BooleanValue("dynamic_island_sync_theme", true); private final BooleanValue syncTheme = new BooleanValue("dynamic_island_sync_theme", true);
private final NumberValue<Integer> themeAlpha = new NumberValue<>("dynamic_island_theme_alpha", 200, 0, 255); private final NumberValue<Integer> themeAlpha = new NumberValue<>("dynamic_island_theme_alpha", 200, 0, 255);
@ -189,11 +188,13 @@ public final class DynamicIsland extends AbstractHudElement {
@Override @Override
public boolean isMouseOverInteractive(float mx, float my) { public boolean isMouseOverInteractive(float mx, float my) {
Minecraft mc = Minecraft.getInstance();
return mc != null && isInteractiveScreen(ClientScreen.current()) && contains(mx, my); return mc != null && isInteractiveScreen(ClientScreen.current()) && contains(mx, my);
} }
@Override @Override
public boolean onMouseClicked(float mx, float my, int button) { public boolean onMouseClicked(float mx, float my, int button) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || !isInteractiveScreen(ClientScreen.current())) return false; if (mc == null || !isInteractiveScreen(ClientScreen.current())) return false;
if (!contains(mx, my)) return false; if (!contains(mx, my)) return false;
@ -241,6 +242,7 @@ public final class DynamicIsland extends AbstractHudElement {
} }
private void renderScreenOverlayInternal(GuiGraphicsExtractor ctx, float tickDelta) { private void renderScreenOverlayInternal(GuiGraphicsExtractor ctx, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!Lifecycle.canRunHud()) return; if (!Lifecycle.canRunHud()) return;
if (mc == null || mc.getWindow() == null || ctx == null) return; if (mc == null || mc.getWindow() == null || ctx == null) return;
Screen currentScreen = ClientScreen.current(); Screen currentScreen = ClientScreen.current();
@ -275,6 +277,7 @@ public final class DynamicIsland extends AbstractHudElement {
int screenW, int screenW,
int screenH, int screenH,
boolean screenOverlayPass) { boolean screenOverlayPass) {
Minecraft mc = Minecraft.getInstance();
if (mc == null) return; if (mc == null) return;
HudScriptLayouts.pollReloadCombo(mc); HudScriptLayouts.pollReloadCombo(mc);
if (moduleHandle.consumeChanged()) { if (moduleHandle.consumeChanged()) {
@ -562,6 +565,7 @@ public final class DynamicIsland extends AbstractHudElement {
} }
private UiScriptModule ensureModule() { private UiScriptModule ensureModule() {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.getResourceManager() == null) return null; if (mc == null || mc.getResourceManager() == null) return null;
if (!moduleHandle.ensureLoaded(mc.getResourceManager())) { if (!moduleHandle.ensureLoaded(mc.getResourceManager())) {
HudScriptLayouts.reportLoadError(moduleHandle); HudScriptLayouts.reportLoadError(moduleHandle);

View file

@ -0,0 +1,140 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.gui.hud.nondraggable.impl;
import dev.loki.lovisual.config.values.BooleanValue;
import dev.loki.lovisual.config.values.NumberValue;
import dev.loki.lovisual.config.values.RGBColorValue;
import dev.loki.lovisual.features.gui.hud.AbstractHudElement;
import dev.loki.lovisual.features.gui.hud.HudElementRegister;
import dev.loki.lovisual.features.gui.hud.HudRenderSpace;
import dev.loki.lovisual.features.gui.hud.HudRenderUtil;
import dev.loki.lovisual.render.engine.color.RenderColor;
import dev.loki.lovisual.render.engine.math.HudScale;
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
import dev.loki.lovisual.render.engine.text.FontInfo;
import dev.loki.lovisual.render.engine.text.Fonts;
import dev.loki.lovisual.render.engine.text.TextRenderer;
import dev.loki.lovisual.util.combat.ReachMeter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import static dev.loki.lovisual.features.theme.Theme.theme;
/**
* Shows the reach (eye -> target hitbox distance) of your last melee hit just
* below the crosshair, then fades out. Pure information widget: it never
* changes attack range.
*/
@HudElementRegister(order = 35)
public final class ReachDisplay extends AbstractHudElement {
public static final ReachDisplay INSTANCE = new ReachDisplay();
private static final float PAD_X = 6f;
private static final float PAD_Y = 4f;
private static final float RADIUS = 5f;
private static final float CROSSHAIR_GAP = 26f;
private static final float FADE_WINDOW_MS = 1800f;
private static final int OVERREACH_TINT = 0xFFFF6368;
private final NumberValue<Double> scale = num("reach_scale", "scale", 1.0, 0.5, 2.5);
private final NumberValue<Integer> keepMs = num("reach_keep_ms", "keep_ms", 2500, 500, 10000);
private final BooleanValue syncTheme = bool("reach_sync_theme", "sync_theme", true);
private final BooleanValue showMax = bool("reach_show_max", "show_max", true);
private final BooleanValue highlightOverreach = bool("reach_highlight_overreach", "highlight_overreach", true);
private final RGBColorValue valueColor =
visibleWhen(colorNoAlpha("reach_value_color", "#5CC8E7"), () -> !syncTheme.get());
private final RGBColorValue bgColor =
visibleWhen(colorNoAlpha("reach_bg_color", "#1A1D23"), () -> !syncTheme.get());
private ReachDisplay() {
super("reach_display", "Reach", true);
}
public static ReachDisplay get() {
return INSTANCE;
}
@Override
public boolean usesEngineRenderer() {
return true;
}
@Override
public HudRenderSpace getRenderSpace() {
return HudRenderSpace.UNSCALED_LOGICAL;
}
@Override
public void renderEngine(Renderer2D renderer,
TextRenderer textRenderer,
GuiGraphicsExtractor ctx,
float tickDelta,
int screenW,
int screenH) {
double distance = ReachMeter.lastDistance();
Minecraft mc = Minecraft.getInstance();
if (distance < 0.0 || mc == null || mc.player == null || mc.level == null) {
width = 0f;
height = 0f;
return;
}
long age = System.currentTimeMillis() - ReachMeter.lastAttackAtMs();
if (age > keepMs.get() + FADE_WINDOW_MS) {
width = 0f;
height = 0f;
return;
}
int alpha = clampAlpha((int) ((1.0f - Math.max(0f, age - keepMs.get()) / FADE_WINDOW_MS) * 255f));
String value = ReachMeter.format(distance);
if (showMax.get()) {
value = value + " • max " + ReachMeter.format(ReachMeter.sessionMax());
}
float drawScale = HudScale.scale(screenW, screenH) * scale.get().floatValue();
TextRenderer fallback = textRenderer != null ? textRenderer : TextRenderer.get();
TextRenderer font = Fonts.renderer("OnestMedium", FontInfo.Type.Regular, fallback);
font.begin(drawScale, true, false);
float textW = (float) font.getWidth(value, false);
float textH = (float) font.getHeight(false);
font.end();
float boxW = textW + PAD_X * 2f * drawScale;
float boxH = textH + PAD_Y * 2f * drawScale;
float boxX = (screenW - boxW) * 0.5f;
float boxY = screenH * 0.5f + CROSSHAIR_GAP * drawScale;
boolean overreach = highlightOverreach.get() && distance > ReachMeter.VANILLA_REACH;
int valueArgb = overreach ? OVERREACH_TINT
: syncTheme.get() ? theme().accent() : valueColor.getArgb() | 0xFF000000;
int bg = syncTheme.get()
? HudRenderUtil.setAlpha(theme().windowBg(), 0xCC)
: (0xCC << 24) | (bgColor.getArgb() & 0x00FFFFFF);
renderer.roundedRect(boxX, boxY, boxW, boxH, RADIUS * drawScale, 1.0f,
HudRenderUtil.setAlpha(bg, alpha));
font.begin(drawScale, true, false);
font.render(value, boxX + PAD_X * drawScale, boxY + (boxH - textH) * 0.5f,
new RenderColor(HudRenderUtil.setAlpha(valueArgb, alpha)), false);
font.end();
width = boxW;
height = boxH;
x = boxX;
y = boxY;
}
private static int clampAlpha(int a) {
return Math.max(0, Math.min(255, a));
}
}

View file

@ -0,0 +1,178 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.gui.hud.nondraggable.impl;
import dev.loki.lovisual.config.values.BooleanValue;
import dev.loki.lovisual.config.values.NumberValue;
import dev.loki.lovisual.config.values.RGBColorValue;
import dev.loki.lovisual.features.gui.hud.AbstractHudElement;
import dev.loki.lovisual.features.gui.hud.HudElementRegister;
import dev.loki.lovisual.features.gui.hud.HudRenderSpace;
import dev.loki.lovisual.features.gui.hud.HudRenderUtil;
import dev.loki.lovisual.render.engine.color.RenderColor;
import dev.loki.lovisual.render.engine.math.HudScale;
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
import dev.loki.lovisual.render.engine.text.FontInfo;
import dev.loki.lovisual.render.engine.text.Fonts;
import dev.loki.lovisual.render.engine.text.TextRenderer;
import dev.loki.lovisual.util.FastFps;
import dev.loki.lovisual.util.player.NetworkStatsUtil;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import static dev.loki.lovisual.features.theme.Theme.theme;
/**
* LoVisual watermark: a small brand pill in the top-left corner with optional
* FPS and ping segments. Static UI element (Settings -> UI), theme-synced colors,
* not a copy of the SoupVisuals component stack.
*/
@HudElementRegister(order = 30)
public final class Watermark extends AbstractHudElement {
public static final Watermark INSTANCE = new Watermark();
private static final float PAD_X = 6f;
private static final float PAD_Y = 4f;
private static final float GAP = 5f;
private static final float DOT_SIZE = 3f;
private static final float RADIUS = 5f;
private static final float ORIGIN_X = 4f;
private static final float ORIGIN_Y = 4f;
private final NumberValue<Double> scale = num("watermark_scale", "scale", 1.0, 0.5, 2.5);
private final BooleanValue syncTheme = bool("watermark_sync_theme", "sync_theme", true);
private final BooleanValue showVersion = bool("watermark_show_version", "show_version", true);
private final BooleanValue showUser = bool("watermark_show_user", "show_user", false);
private final BooleanValue showFps = bool("watermark_show_fps", "show_fps", true);
private final BooleanValue showPing = bool("watermark_show_ping", "show_ping", true);
private final RGBColorValue titleColor =
visibleWhen(colorNoAlpha("watermark_title_color", "title_color", "#5CC8E7"), () -> !syncTheme.get());
private final RGBColorValue metaColor =
visibleWhen(colorNoAlpha("watermark_meta_color", "meta_color", "#9B9B9B"), () -> !syncTheme.get());
private final RGBColorValue bgColor =
visibleWhen(colorNoAlpha("watermark_bg_color", "bg_color", "#1A1D23"), () -> !syncTheme.get());
private Watermark() {
super("watermark", "Watermark", true);
}
public static Watermark get() {
return INSTANCE;
}
static String brandLine() {
return FabricLoader.getInstance().getModContainer("lovisual")
.map(container -> container.getMetadata().getVersion().getFriendlyString())
.orElse("");
}
@Override
public boolean usesEngineRenderer() {
return true;
}
@Override
public HudRenderSpace getRenderSpace() {
return HudRenderSpace.UNSCALED_LOGICAL;
}
@Override
public void renderEngine(Renderer2D renderer,
TextRenderer textRenderer,
GuiGraphicsExtractor ctx,
float tickDelta,
int screenW,
int screenH) {
if (!isEnabled()) return;
Minecraft mc = Minecraft.getInstance();
String brand = "LoVisual";
String version = showVersion.get() ? brandLine() : "";
String fps = showFps.get() ? Math.max(0, FastFps.getFps()) + " fps" : null;
String ping = null;
if (showPing.get() && mc != null) {
int pingMs = NetworkStatsUtil.getPing(mc);
if (pingMs >= 0) ping = pingMs + " ms";
}
String meta = joinMeta(
version,
showUser.get() && mc != null && mc.getUser() != null ? mc.getUser().getName() : null,
fps,
ping);
float drawScale = HudScale.scale(screenW, screenH) * scale.get().floatValue();
TextRenderer fallback = textRenderer != null ? textRenderer : TextRenderer.get();
TextRenderer titleFont = Fonts.renderer("OnestMedium", FontInfo.Type.Bold, fallback);
TextRenderer metaFont = Fonts.renderer("OnestMedium", FontInfo.Type.Regular, fallback);
titleFont.begin(drawScale, true, false);
float brandW = (float) titleFont.getWidth(brand, false);
float textH = (float) titleFont.getHeight(false);
titleFont.end();
float metaW = 0f;
if (!meta.isEmpty()) {
metaFont.begin(drawScale, true, false);
metaW = (float) metaFont.getWidth(meta, false);
metaFont.end();
}
float dotW = meta.isEmpty() ? 0f : DOT_SIZE * drawScale + GAP * drawScale;
float boxW = (brandW + dotW + metaW) + PAD_X * 2f * drawScale;
float boxH = textH + PAD_Y * 2f * drawScale;
float radius = RADIUS * drawScale;
int bg = resolveBg();
int titleArgb = syncTheme.get() ? theme().accent() : titleColor.getArgb() | 0xFF000000;
int metaArgb = syncTheme.get() ? theme().textMuted() : metaColor.getArgb() | 0xFF000000;
renderer.roundedRect(ORIGIN_X, ORIGIN_Y, boxW, boxH, radius, 1.0f, bg);
float cursorX = ORIGIN_X + PAD_X * drawScale;
float textY = ORIGIN_Y + (boxH - textH) * 0.5f;
titleFont.begin(drawScale, true, false);
titleFont.render(brand, cursorX, textY, new RenderColor(titleArgb), false);
titleFont.end();
cursorX += brandW;
if (!meta.isEmpty()) {
float dotY = textY + textH * 0.5f - DOT_SIZE * drawScale * 0.5f;
renderer.roundedRect(cursorX + GAP * drawScale * 0.5f, dotY,
DOT_SIZE * drawScale, DOT_SIZE * drawScale,
DOT_SIZE * drawScale * 0.5f, 1.0f, HudRenderUtil.setAlpha(metaArgb, 0xB0));
cursorX += dotW;
metaFont.begin(drawScale, true, false);
metaFont.render(meta, cursorX, textY, new RenderColor(metaArgb), false);
metaFont.end();
}
width = boxW;
height = boxH;
}
static String joinMeta(String... parts) {
StringBuilder sb = new StringBuilder();
for (String part : parts) {
if (part == null || part.isBlank()) continue;
if (!sb.isEmpty()) sb.append(" ");
sb.append(part);
}
return sb.toString();
}
private int resolveBg() {
if (syncTheme.get()) {
return HudRenderUtil.setAlpha(theme().windowBg(), 0xCC);
}
return (0xCC << 24) | (bgColor.getArgb() & 0x00FFFFFF);
}
}

View file

@ -57,7 +57,6 @@ public final class CustomTabList extends AbstractHudElement {
private static final long STRUCTURE_PROBE_INTERVAL_MS = 250L; private static final long STRUCTURE_PROBE_INTERVAL_MS = 250L;
private static final long DYNAMIC_REFRESH_INTERVAL_MS = 500L; private static final long DYNAMIC_REFRESH_INTERVAL_MS = 500L;
private final Minecraft mc = Minecraft.getInstance();
private final TabListAnimator animator = new TabListAnimator(); private final TabListAnimator animator = new TabListAnimator();
private final NumberValue<Integer> maxWidth = num("tab_list_max_width", "max_width", 1680, 760, 2600); private final NumberValue<Integer> maxWidth = num("tab_list_max_width", "max_width", 1680, 760, 2600);
private final BooleanValue syncTheme = bool("tab_list_sync_theme", "sync_theme", true); private final BooleanValue syncTheme = bool("tab_list_sync_theme", "sync_theme", true);
@ -107,6 +106,7 @@ public final class CustomTabList extends AbstractHudElement {
} }
private void renderVanillaTabStratumInternal(GuiGraphicsExtractor ctx, int guiWidth) { private void renderVanillaTabStratumInternal(GuiGraphicsExtractor ctx, int guiWidth) {
Minecraft mc = Minecraft.getInstance();
if (!Lifecycle.canRunHud()) return; if (!Lifecycle.canRunHud()) return;
if (mc == null || mc.getWindow() == null || ctx == null) return; if (mc == null || mc.getWindow() == null || ctx == null) return;
int screenW = Math.max(1, Math.round(HudScale.virtualWidth( int screenW = Math.max(1, Math.round(HudScale.virtualWidth(
@ -170,6 +170,7 @@ public final class CustomTabList extends AbstractHudElement {
int screenW, int screenW,
int screenH, int screenH,
boolean show) { boolean show) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || ctx == null || renderer == null) return; if (mc == null || ctx == null || renderer == null) return;
float panelPresence = animator.updatePanel(show); float panelPresence = animator.updatePanel(show);
if (!show && panelPresence <= 0.01f && !animator.hasVisibleRows()) { if (!show && panelPresence <= 0.01f && !animator.hasVisibleRows()) {
@ -196,6 +197,7 @@ public final class CustomTabList extends AbstractHudElement {
} }
private void refreshSnapshot(int screenW, boolean force) { private void refreshSnapshot(int screenW, boolean force) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.getConnection() == null) return; if (mc == null || mc.getConnection() == null) return;
long now = Util.getMillis(); long now = Util.getMillis();
int listedPlayers = Math.min(mc.getConnection().getListedOnlinePlayers().size(), TabListModel.maxPlayers()); int listedPlayers = Math.min(mc.getConnection().getListedOnlinePlayers().size(), TabListModel.maxPlayers());
@ -239,10 +241,12 @@ public final class CustomTabList extends AbstractHudElement {
} }
private boolean canRenderReplacement() { private boolean canRenderReplacement() {
Minecraft mc = Minecraft.getInstance();
return mc != null && mc.player != null && mc.getConnection() != null; return mc != null && mc.player != null && mc.getConnection() != null;
} }
private boolean shouldShowNow() { private boolean shouldShowNow() {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null || mc.getConnection() == null || mc.options == null) return false; if (mc == null || mc.player == null || mc.getConnection() == null || mc.options == null) return false;
if (!mc.options.keyPlayerList.isDown()) return false; if (!mc.options.keyPlayerList.isDown()) return false;

View file

@ -23,7 +23,6 @@ public final class ScriptedCompactHudStatRenderer {
private static final String LAYOUT_ID = "lovisual:api/hud/draggable/compact_stat"; private static final String LAYOUT_ID = "lovisual:api/hud/draggable/compact_stat";
private final Minecraft mc = Minecraft.getInstance();
private final UiScriptModuleHandle moduleHandle = HudScriptLayouts.handle(LAYOUT_ID); private final UiScriptModuleHandle moduleHandle = HudScriptLayouts.handle(LAYOUT_ID);
private final CachedUiScriptRuntime scriptRuntime = new CachedUiScriptRuntime(HudScriptLayouts.runtimeReporter()); private final CachedUiScriptRuntime scriptRuntime = new CachedUiScriptRuntime(HudScriptLayouts.runtimeReporter());
@ -35,6 +34,7 @@ public final class ScriptedCompactHudStatRenderer {
TextRenderer textRenderer, TextRenderer textRenderer,
GuiGraphicsExtractor ctx, GuiGraphicsExtractor ctx,
float tickDelta) { float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (model == null || !model.visible()) return false; if (model == null || !model.visible()) return false;
if (moduleHandle.isRuntimeBlocked()) return false; if (moduleHandle.isRuntimeBlocked()) return false;
if (mc == null || mc.getResourceManager() == null) return false; if (mc == null || mc.getResourceManager() == null) return false;
@ -73,6 +73,7 @@ public final class ScriptedCompactHudStatRenderer {
} }
private UiScriptModule ensureModule() { private UiScriptModule ensureModule() {
Minecraft mc = Minecraft.getInstance();
if (!moduleHandle.ensureLoaded(mc.getResourceManager())) { if (!moduleHandle.ensureLoaded(mc.getResourceManager())) {
HudScriptLayouts.reportLoadError(moduleHandle); HudScriptLayouts.reportLoadError(moduleHandle);
return null; return null;

View file

@ -38,7 +38,6 @@ public class DamageTint extends Module implements PostProcessPass {
private static final String SETTING_PULSE_MAX = "pulse_max"; private static final String SETTING_PULSE_MAX = "pulse_max";
private static final String SETTING_FADE_IN = "fade_in"; private static final String SETTING_FADE_IN = "fade_in";
private static final String SETTING_FADE_OUT = "fade_out"; private static final String SETTING_FADE_OUT = "fade_out";
private final Minecraft mc = Minecraft.getInstance();
private final NumberValue<Integer> startHearts = private final NumberValue<Integer> startHearts =
num("damageTintStartHearts", SETTING_START_HP, 20, 1, 80); num("damageTintStartHearts", SETTING_START_HP, 20, 1, 80);
private final NumberValue<Integer> fullHearts = private final NumberValue<Integer> fullHearts =
@ -71,6 +70,7 @@ public class DamageTint extends Module implements PostProcessPass {
@Override @Override
public boolean isActive() { public boolean isActive() {
Minecraft mc = Minecraft.getInstance();
return isEnabled() && mc.player != null && mc.level != null; return isEnabled() && mc.player != null && mc.level != null;
} }
@ -86,6 +86,7 @@ public class DamageTint extends Module implements PostProcessPass {
@Override @Override
public boolean render(GpuTextureView src, GpuTextureView dst, float tickDelta) { public boolean render(GpuTextureView src, GpuTextureView dst, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.player == null || mc.level == null) return false; if (!isEnabled() || mc.player == null || mc.level == null) return false;
if (src == null || dst == null) return false; if (src == null || dst == null) return false;

View file

@ -133,8 +133,6 @@ public class Predictions extends Module {
private static final float IMPACT_RING_LINE_WIDTH = 1.2f; private static final float IMPACT_RING_LINE_WIDTH = 1.2f;
private static final float IMPACT_RING_ALPHA = 0.88f; private static final float IMPACT_RING_ALPHA = 0.88f;
private final Minecraft mc = Minecraft.getInstance();
private final RGBColorValue lineColorValue = colorNoAlpha("pred_line_color", SETTING_LINE_COLOR, "#11C7C7"); private final RGBColorValue lineColorValue = colorNoAlpha("pred_line_color", SETTING_LINE_COLOR, "#11C7C7");
private final BooleanValue pearlColorToggle = bool("pred_pearl_custom_color", SETTING_PEARL_CUSTOM_COLOR, false); private final BooleanValue pearlColorToggle = bool("pred_pearl_custom_color", SETTING_PEARL_CUSTOM_COLOR, false);
private final RGBColorValue pearlColorValue = private final RGBColorValue pearlColorValue =
@ -191,6 +189,7 @@ public class Predictions extends Module {
@Override @Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null) { if (!isEnabled() || mc.level == null) {
TargetManager.setPredictionTarget(null); TargetManager.setPredictionTarget(null);
return; return;
@ -215,6 +214,7 @@ public class Predictions extends Module {
@Override @Override
public void onRenderHudEngine(Renderer2D renderer, TextRenderer textRenderer, net.minecraft.client.gui.GuiGraphicsExtractor ctx, float tickDelta) { public void onRenderHudEngine(Renderer2D renderer, TextRenderer textRenderer, net.minecraft.client.gui.GuiGraphicsExtractor ctx, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null) return; if (!isEnabled() || mc.level == null) return;
boolean showPlate = showTimerPlateValue.get(); boolean showPlate = showTimerPlateValue.get();
@ -238,6 +238,7 @@ public class Predictions extends Module {
@Override @Override
public void onRenderHudEngineForeground(Renderer2D renderer, TextRenderer textRenderer, net.minecraft.client.gui.GuiGraphicsExtractor ctx, float tickDelta) { public void onRenderHudEngineForeground(Renderer2D renderer, TextRenderer textRenderer, net.minecraft.client.gui.GuiGraphicsExtractor ctx, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null) return; if (!isEnabled() || mc.level == null) return;
if (timerPlates.isEmpty()) return; if (timerPlates.isEmpty()) return;
@ -276,6 +277,7 @@ public class Predictions extends Module {
} }
private List<TrajectoryResult> simulateProjectiles(float tickDelta) { private List<TrajectoryResult> simulateProjectiles(float tickDelta) {
Minecraft mc = Minecraft.getInstance();
ClientLevel world = mc.level; ClientLevel world = mc.level;
if (world == null) return Collections.emptyList(); if (world == null) return Collections.emptyList();
@ -396,6 +398,7 @@ public class Predictions extends Module {
} }
private void drawTntTimers(Renderer2D renderer, TextRenderer textRenderer, float tickDelta) { private void drawTntTimers(Renderer2D renderer, TextRenderer textRenderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
ClientLevel world = mc.level; ClientLevel world = mc.level;
if (world == null) return; if (world == null) return;
@ -518,6 +521,7 @@ public class Predictions extends Module {
} }
private void renderAimTrajectoryGlow(Renderer3D renderer, float tickDelta, int baseColor) { private void renderAimTrajectoryGlow(Renderer3D renderer, float tickDelta, int baseColor) {
Minecraft mc = Minecraft.getInstance();
Player player = mc.player; Player player = mc.player;
if (player == null) return; if (player == null) return;
@ -574,6 +578,7 @@ public class Predictions extends Module {
} }
private void renderAimIndicatorWorld(Renderer3D renderer, float tickDelta) { private void renderAimIndicatorWorld(Renderer3D renderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
Player player = mc.player; Player player = mc.player;
if (player == null) return; if (player == null) return;
@ -600,6 +605,7 @@ public class Predictions extends Module {
} }
private void updatePredictionTarget(float tickDelta) { private void updatePredictionTarget(float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!targetHudOnAimValue.get()) { if (!targetHudOnAimValue.get()) {
TargetManager.setPredictionTarget(null); TargetManager.setPredictionTarget(null);
return; return;
@ -710,6 +716,7 @@ public class Predictions extends Module {
} }
private void renderImpactEffects(Renderer3D renderer, List<TrajectoryResult> results, int baseColor, float tickDelta) { private void renderImpactEffects(Renderer3D renderer, List<TrajectoryResult> results, int baseColor, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
boolean walls = glowWallsValue.get(); boolean walls = glowWallsValue.get();
RenderPipeline linePipeline = walls RenderPipeline linePipeline = walls
? LoVisualRenderPipelines.WORLD_COLORED_LINES_LIQUID_BLEND ? LoVisualRenderPipelines.WORLD_COLORED_LINES_LIQUID_BLEND

View file

@ -78,7 +78,6 @@ public final class ProjectilePuncher extends Module {
private static final String PROJECTILE_FIREBALL = "fireball"; private static final String PROJECTILE_FIREBALL = "fireball";
private static final String PROJECTILE_SHULKER_BULLET = "shulker_bullet"; private static final String PROJECTILE_SHULKER_BULLET = "shulker_bullet";
private static final int ROTATION_PRIORITY = 10; private static final int ROTATION_PRIORITY = 10;
private final Minecraft mc = Minecraft.getInstance();
private final NumberValue<Double> range = private final NumberValue<Double> range =
numCommon( numCommon(
"projectilePuncherRange", "projectilePuncherRange",
@ -428,6 +427,7 @@ public final class ProjectilePuncher extends Module {
@Override @Override
public void onTick() { public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (mc.player == null || mc.level == null) { if (mc.player == null || mc.level == null) {
clearTargetImmediate(); clearTargetImmediate();
} }
@ -435,6 +435,7 @@ public final class ProjectilePuncher extends Module {
@EventHandler(priority = ROTATION_PRIORITY) @EventHandler(priority = ROTATION_PRIORITY)
public void onRotationUpdate(RotationUpdateEvent event) { public void onRotationUpdate(RotationUpdateEvent event) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled()) return; if (!isEnabled()) return;
if (mc.player == null || mc.level == null) { if (mc.player == null || mc.level == null) {
clearTargetImmediate(); clearTargetImmediate();
@ -461,6 +462,7 @@ public final class ProjectilePuncher extends Module {
} }
private void tryAttackPost() { private void tryAttackPost() {
Minecraft mc = Minecraft.getInstance();
if (mc.player == null || mc.level == null || mc.gameMode == null) return; if (mc.player == null || mc.level == null || mc.gameMode == null) return;
if (mc.player.tickCount == lastAttackExecutionAge) { if (mc.player.tickCount == lastAttackExecutionAge) {
return; return;
@ -493,6 +495,7 @@ public final class ProjectilePuncher extends Module {
} }
private List<Entity> findCandidates() { private List<Entity> findCandidates() {
Minecraft mc = Minecraft.getInstance();
double searchRange = Math.max(range.get(), wallRange.get()) + 8.0; double searchRange = Math.max(range.get(), wallRange.get()) + 8.0;
List<Entity> candidates = mc.level.getEntities( List<Entity> candidates = mc.level.getEntities(
mc.player, mc.player,
@ -506,6 +509,7 @@ public final class ProjectilePuncher extends Module {
} }
private boolean processTarget(Entity target, float range, float wallsRange) { private boolean processTarget(Entity target, float range, float wallsRange) {
Minecraft mc = Minecraft.getInstance();
RotationWithVector rotation = findRotation(target, range, wallsRange); RotationWithVector rotation = findRotation(target, range, wallsRange);
if (rotation == null) return false; if (rotation == null) return false;
@ -526,6 +530,7 @@ public final class ProjectilePuncher extends Module {
} }
private Rotation resolveAttackRotation(Entity target) { private Rotation resolveAttackRotation(Entity target) {
Minecraft mc = Minecraft.getInstance();
if (mc.player == null) return null; if (mc.player == null) return null;
Rotation rotation; Rotation rotation;
@ -548,6 +553,7 @@ public final class ProjectilePuncher extends Module {
} }
private boolean passesRaycast(Entity target, float yaw, float pitch) { private boolean passesRaycast(Entity target, float yaw, float pitch) {
Minecraft mc = Minecraft.getInstance();
RaycastMode mode = raycast.get(); RaycastMode mode = raycast.get();
if (mode == RaycastMode.NONE) return true; if (mode == RaycastMode.NONE) return true;
@ -560,10 +566,12 @@ public final class ProjectilePuncher extends Module {
} }
private boolean isInRange(Entity target) { private boolean isInRange(Entity target) {
Minecraft mc = Minecraft.getInstance();
return TargetingUtil.distanceToEntityBoxSq(mc.player.getEyePosition(), target) <= range.get() * range.get(); return TargetingUtil.distanceToEntityBoxSq(mc.player.getEyePosition(), target) <= range.get() * range.get();
} }
private RotationWithVector findRotation(Entity target, float range, float wallsRange) { private RotationWithVector findRotation(Entity target, float range, float wallsRange) {
Minecraft mc = Minecraft.getInstance();
if (mc.player == null) return null; if (mc.player == null) return null;
Vec3 eyes = mc.player.getEyePosition(); Vec3 eyes = mc.player.getEyePosition();
@ -635,10 +643,12 @@ public final class ProjectilePuncher extends Module {
} }
private double predictedDistanceSq(Entity entity) { private double predictedDistanceSq(Entity entity) {
Minecraft mc = Minecraft.getInstance();
return TargetingUtil.distanceToBoxSq(mc.player.getEyePosition(), predictBox(entity)); return TargetingUtil.distanceToBoxSq(mc.player.getEyePosition(), predictBox(entity));
} }
private boolean shouldAttack(Entity entity) { private boolean shouldAttack(Entity entity) {
Minecraft mc = Minecraft.getInstance();
Vec3 velocity = entity.getDeltaMovement(); Vec3 velocity = entity.getDeltaMovement();
if (velocity.lengthSqr() < 1.0E-7) { if (velocity.lengthSqr() < 1.0E-7) {
return false; return false;

View file

@ -37,7 +37,6 @@ public class RubberHand extends Module {
private static final String ACTION_ATTACK_WHILE_SHIELD = "attack_while_shield"; private static final String ACTION_ATTACK_WHILE_SHIELD = "attack_while_shield";
private static final String ACTION_ATTACK_WHILE_EATING = "attack_while_eating"; private static final String ACTION_ATTACK_WHILE_EATING = "attack_while_eating";
private final Minecraft mc = Minecraft.getInstance();
private final BooleanMapValue actions = group( private final BooleanMapValue actions = group(
"rubberhand_actions", "rubberhand_actions",
SETTING_ACTIONS, SETTING_ACTIONS,
@ -66,6 +65,7 @@ public class RubberHand extends Module {
@Override @Override
public void onTick() { public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled()) return; if (!isEnabled()) return;
if (mc.player == null) return; if (mc.player == null) return;
@ -78,6 +78,7 @@ public class RubberHand extends Module {
@EventHandler @EventHandler
private void onSync(EventSync event) { private void onSync(EventSync event) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.player == null || mc.gameMode == null) return; if (!isEnabled() || mc.player == null || mc.gameMode == null) return;
if (didAttackThisHold) return; if (didAttackThisHold) return;
if (!mc.options.keyAttack.isDown() || !mc.options.keyUse.isDown()) return; if (!mc.options.keyAttack.isDown() || !mc.options.keyUse.isDown()) return;
@ -119,6 +120,7 @@ public class RubberHand extends Module {
} }
private void performAttack(LocalPlayer player, Entity target) { private void performAttack(LocalPlayer player, Entity target) {
Minecraft mc = Minecraft.getInstance();
mc.gameMode.attack(player, target); mc.gameMode.attack(player, target);
player.swing(InteractionHand.MAIN_HAND); player.swing(InteractionHand.MAIN_HAND);
didAttackThisHold = true; didAttackThisHold = true;

View file

@ -24,7 +24,6 @@ import dev.loki.lovisual.util.player.NetworkStatsUtil;
public class TPSSync extends Module { public class TPSSync extends Module {
private static final String SETTING_START_TPS = "start_tps"; private static final String SETTING_START_TPS = "start_tps";
private final Minecraft mc = Minecraft.getInstance();
private final BooleanValue enabled = private final BooleanValue enabled =
new BooleanValue("tpssync_enabled", true); new BooleanValue("tpssync_enabled", true);
private final NumberValue<Float> startTps = private final NumberValue<Float> startTps =
@ -32,6 +31,7 @@ public class TPSSync extends Module {
/** сколько серверных тиков прошло за клиентский */ /** сколько серверных тиков прошло за клиентский */
public float getServerTickDelta() { public float getServerTickDelta() {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || !enabled.get()) return 1.0f; if (!isEnabled() || !enabled.get()) return 1.0f;
if (mc == null || mc.level == null) return 1.0f; if (mc == null || mc.level == null) return 1.0f;

View file

@ -78,7 +78,6 @@ public class TargetESP extends Module {
private static final Identifier CRYSTAL_BLOOM = TextureStorage.BLOOM; private static final Identifier CRYSTAL_BLOOM = TextureStorage.BLOOM;
private static final float ALPHA_TICK_DT = 1.0f / 20.0f; private static final float ALPHA_TICK_DT = 1.0f / 20.0f;
private static final float ALPHA_SNAP_EPSILON = 0.02f; private static final float ALPHA_SNAP_EPSILON = 0.02f;
private final Minecraft mc = Minecraft.getInstance();
private final ModeValue espMode = modeSetting( private final ModeValue espMode = modeSetting(
"targetEspMode", "targetEspMode",
SETTING_MODE, SETTING_MODE,
@ -146,6 +145,7 @@ public class TargetESP extends Module {
@Override @Override
public void onTick() { public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null) { if (!isEnabled() || mc.level == null) {
target = null; target = null;
updateRenderTarget(null); updateRenderTarget(null);
@ -247,6 +247,7 @@ public class TargetESP extends Module {
} }
private Renderer3D.CullOptions resolveCullOptions(LivingEntity target) { private Renderer3D.CullOptions resolveCullOptions(LivingEntity target) {
Minecraft mc = Minecraft.getInstance();
if (target == null || mc.player == null) { if (target == null || mc.player == null) {
return Renderer3D.CullOptions.SMART_TARGET; return Renderer3D.CullOptions.SMART_TARGET;
} }

View file

@ -48,7 +48,6 @@ public class DefineTarget extends Module {
private static final String SETTING_BLOCK_DAMAGE_TO_BEDWARS = "block_damage_to_bedwars"; private static final String SETTING_BLOCK_DAMAGE_TO_BEDWARS = "block_damage_to_bedwars";
private static final String SETTING_IGNORED_ENTITIES = "ignored_entities"; private static final String SETTING_IGNORED_ENTITIES = "ignored_entities";
private static final String ACTION_TOGGLE_TARGET = "toggle_target"; private static final String ACTION_TOGGLE_TARGET = "toggle_target";
private final Minecraft mc = Minecraft.getInstance();
private final BooleanValue bedwarsRelations = bool("defineTargetBedwarsRelations", SETTING_BEDWARS_RELATIONS, false); private final BooleanValue bedwarsRelations = bool("defineTargetBedwarsRelations", SETTING_BEDWARS_RELATIONS, false);
private final EnumValue<BedwarsColorMode> bedwarsColorMode = private final EnumValue<BedwarsColorMode> bedwarsColorMode =
visibleWhen(enumSetting("defineTargetBedwarsColorMode", SETTING_BEDWARS_COLOR_MODE, BedwarsColorMode.RELATION_COLORS, BedwarsColorMode.values()), visibleWhen(enumSetting("defineTargetBedwarsColorMode", SETTING_BEDWARS_COLOR_MODE, BedwarsColorMode.RELATION_COLORS, BedwarsColorMode.values()),
@ -142,6 +141,7 @@ public class DefineTarget extends Module {
@Override @Override
public void onTick() { public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.player == null || mc.level == null) return; if (!isEnabled() || mc.player == null || mc.level == null) return;
if (isActionPressedOnce(ACTION_TOGGLE_TARGET)) { if (isActionPressedOnce(ACTION_TOGGLE_TARGET)) {
@ -150,6 +150,7 @@ public class DefineTarget extends Module {
} }
private void handleToggle() { private void handleToggle() {
Minecraft mc = Minecraft.getInstance();
HitResult hit = mc.hitResult; HitResult hit = mc.hitResult;
if (!(hit instanceof EntityHitResult ehr)) return; if (!(hit instanceof EntityHitResult ehr)) return;
Entity ent = ehr.getEntity(); Entity ent = ehr.getEntity();

View file

@ -55,7 +55,6 @@ public class FakePlayer extends Module {
private static final String SETTING_AUTO_TOTEM = "auto_totem"; private static final String SETTING_AUTO_TOTEM = "auto_totem";
private static final float SMASH_THRESHOLD = 1.5f; private static final float SMASH_THRESHOLD = 1.5f;
private static final float SMASH_BASE_DAMAGE_PER_BLOCK = 1.0f; private static final float SMASH_BASE_DAMAGE_PER_BLOCK = 1.0f;
private final Minecraft mc = Minecraft.getInstance();
private final BooleanValue copyInventory = private final BooleanValue copyInventory =
bool("fakePlayerCopyInventory", SETTING_COPY_INVENTORY, false); bool("fakePlayerCopyInventory", SETTING_COPY_INVENTORY, false);
private final BooleanValue autoTotem = private final BooleanValue autoTotem =
@ -65,6 +64,7 @@ public class FakePlayer extends Module {
private int deathTime; private int deathTime;
public FakePlayer() { public FakePlayer() {
Minecraft mc = Minecraft.getInstance();
AttackEntityCallback.EVENT.register((player, world, hand, target, hitResult) -> { AttackEntityCallback.EVENT.register((player, world, hand, target, hitResult) -> {
if (!isEnabled()) return InteractionResult.PASS; if (!isEnabled()) return InteractionResult.PASS;
if (mc.player == null || mc.level == null) return InteractionResult.PASS; if (mc.player == null || mc.level == null) return InteractionResult.PASS;
@ -112,6 +112,7 @@ public class FakePlayer extends Module {
@Override @Override
public void onEnable() { public void onEnable() {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null || mc.player == null) return; if (mc.level == null || mc.player == null) return;
despawnFake(); despawnFake();
@ -168,6 +169,7 @@ public class FakePlayer extends Module {
@Override @Override
public void onTick() { public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null || mc.player == null) return; if (!isEnabled() || mc.level == null || mc.player == null) return;
if (fakePlayer == null) return; if (fakePlayer == null) return;
@ -186,6 +188,7 @@ public class FakePlayer extends Module {
} }
private int allocateFakeEntityId() { private int allocateFakeEntityId() {
Minecraft mc = Minecraft.getInstance();
int id = nextFakeEntityId--; int id = nextFakeEntityId--;
while (mc.level != null && mc.level.getEntity(id) != null) { while (mc.level != null && mc.level.getEntity(id) != null) {
id = nextFakeEntityId--; id = nextFakeEntityId--;
@ -204,6 +207,7 @@ public class FakePlayer extends Module {
} }
private void handleAttack(Player attacker, Player target) { private void handleAttack(Player attacker, Player target) {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null) return; if (mc.level == null) return;
ItemStack weapon = attacker.getMainHandItem(); ItemStack weapon = attacker.getMainHandItem();
@ -270,6 +274,7 @@ public class FakePlayer extends Module {
} }
private void applyDamage(Player target, DamageSource source, float damage) { private void applyDamage(Player target, DamageSource source, float damage) {
Minecraft mc = Minecraft.getInstance();
if (damage <= 0f) return; if (damage <= 0f) return;
target.handleDamageEvent(source); target.handleDamageEvent(source);
@ -335,6 +340,7 @@ public class FakePlayer extends Module {
} }
private Holder<Enchantment> entryOf(ResourceKey<Enchantment> key) { private Holder<Enchantment> entryOf(ResourceKey<Enchantment> key) {
Minecraft mc = Minecraft.getInstance();
if (key == null || mc.level == null) return null; if (key == null || mc.level == null) return null;
Registry<Enchantment> registry = Registry<Enchantment> registry =
mc.level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT); mc.level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT);

View file

@ -0,0 +1,68 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.misc;
import dev.loki.lovisual.config.values.BooleanValue;
import dev.loki.lovisual.features.module.Module;
import dev.loki.lovisual.features.module.ModuleCategory;
import dev.loki.lovisual.features.module.ModuleInfo;
import dev.loki.lovisual.util.screen.ClientScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ContainerInput;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
/**
* Shift-moves items with the mouse wheel: scrolling over a container screen
* sends a QUICK_MOVE click for the hovered slot, or for the matching hotbar
* slot when the cursor is over no slot at all.
*/
@ModuleInfo(
id = "itemscroller",
displayName = "Item Scroller",
aliases = {"itemscroller", "scrollmove"},
category = ModuleCategory.MISC
)
public class ItemScroller extends Module {
private static final String SETTING_HOTBAR_FALLBACK = "hotbar_fallback";
private final BooleanValue hotbarFallback =
bool("itemScrollerHotbarFallback", SETTING_HOTBAR_FALLBACK, true);
/** Called from AbstractContainerScreenMixin before vanilla scroll handling. */
public boolean onMouseScrolled(double vertical, Object hoveredSlotObj, ItemStack hoveredStack) {
if (!isEnabled() || !ScrollMovePolicy.isScrollSignificant(vertical)) return false;
Minecraft mc = Minecraft.getInstance();
if (mc.player == null || mc.gameMode == null) return false;
if (!(ClientScreen.current() instanceof AbstractContainerScreen<?> screen)) return false;
AbstractContainerMenu menu = screen.getMenu();
net.minecraft.world.inventory.Slot hovered = (net.minecraft.world.inventory.Slot) hoveredSlotObj;
int target = hovered != null
? ScrollMovePolicy.resolveTargetSlot(true, hovered.index, -1, menu.slots.size())
: hotbarFallbackTarget(menu, mc.player);
if (!ScrollMovePolicy.canClick(menu, target, hoveredStack)) return false;
if (hovered == null && menu.getCarried() != null && !menu.getCarried().isEmpty()) {
// Holding an item stack: let vanilla handle the drag.
return false;
}
mc.gameMode.handleContainerInput(menu.containerId, target,
ScrollMovePolicy.quickMoveButton(), ContainerInput.QUICK_MOVE, mc.player);
return true;
}
private int hotbarFallbackTarget(AbstractContainerMenu menu, Player player) {
if (!hotbarFallback.get()) return -1;
return ScrollMovePolicy.resolveTargetSlot(false, -1,
player.getInventory().getSelectedSlot(), menu.slots.size());
}}

View file

@ -0,0 +1,116 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.misc;
import dev.loki.lovisual.config.values.BooleanValue;
import dev.loki.lovisual.config.values.StringValue;
import dev.loki.lovisual.features.module.Module;
import dev.loki.lovisual.features.module.ModuleCategory;
import dev.loki.lovisual.features.module.ModuleInfo;
import dev.loki.lovisual.features.module.Modules;
import dev.loki.lovisual.features.relations.PlayerRelations;
import dev.loki.lovisual.util.text.NameProtector;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentContents;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.contents.PlainTextContents;
/**
* Hides the local nickname and friend nicknames in rendered chat/tab text,
* replacing them with a configurable alias. Hook points are the mixins on
* ChatComponent.addMessage and PlayerInfo.getTabListDisplayName.
*/
@ModuleInfo(
id = "nameprotect",
displayName = "Name Protect",
category = ModuleCategory.MISC
)
public class NameProtect extends Module {
private static final String SETTING_ALIAS = "alias";
private static final String SETTING_PROTECT_SELF = "protect_self";
private static final String SETTING_PROTECT_FRIENDS = "protect_friends";
private static final long CACHE_TTL_MS = 100L;
private final StringValue alias = text("nameProtectAlias", SETTING_ALIAS, "Protected");
private final BooleanValue protectSelf = bool("nameProtectSelf", SETTING_PROTECT_SELF, true);
private final BooleanValue protectFriends = bool("nameProtectFriends", SETTING_PROTECT_FRIENDS, true);
private volatile NameProtector cachedProtector;
private volatile long cachedAt;
@Override
public void onDisable() {
cachedProtector = null;
}
/**
* @return null when the module is off or nothing is configured to protect
*/
public static NameProtector activeProtector() {
NameProtect module = Modules.get(NameProtect.class);
if (module == null || !module.isEnabled()) return null;
return module.protector();
}
/** Rewrites plain-text leaves of a component tree; returns input when nothing changes. */
public static Component protect(Component text) {
if (text == null) return null;
NameProtector protector = activeProtector();
if (protector == null) return text;
return rewrite(text, protector);
}
private NameProtector protector() {
long now = System.currentTimeMillis();
NameProtector cached = cachedProtector;
if (cached != null && now - cachedAt < CACHE_TTL_MS) {
return cached.isEmpty() ? null : cached;
}
NameProtector protector = build();
cachedProtector = protector;
cachedAt = now;
return protector.isEmpty() ? null : protector;
}
private NameProtector build() {
NameProtector protector = new NameProtector();
String replacement = alias.get();
if (replacement == null) replacement = "";
Minecraft mc = Minecraft.getInstance();
if (protectSelf.get() && mc != null && mc.getUser() != null) {
protector.put(mc.getUser().getName(), replacement);
}
if (protectFriends.get()) {
for (String friend : PlayerRelations.get().getFriends()) {
protector.put(friend, replacement);
}
}
return protector;
}
private static Component rewrite(Component text, NameProtector protector) {
ComponentContents contents = text.getContents();
MutableComponent result;
if (contents instanceof PlainTextContents plain) {
String replaced = protector.replace(plain.text());
result = Component.literal(replaced).withStyle(text.getStyle());
} else {
result = MutableComponent.create(contents).withStyle(text.getStyle());
}
for (Component child : text.getSiblings()) {
result.append(rewrite(child, protector));
}
return result;
}
}

View file

@ -7,6 +7,8 @@
package dev.loki.lovisual.features.module.modules.misc; package dev.loki.lovisual.features.module.modules.misc;
import dev.loki.lovisual.config.values.ModeValue;
import dev.loki.lovisual.config.values.NumberValue;
import dev.loki.lovisual.config.values.SetValue; import dev.loki.lovisual.config.values.SetValue;
import dev.loki.lovisual.features.gui.clickgui.settings.TextListSetting; import dev.loki.lovisual.features.gui.clickgui.settings.TextListSetting;
import dev.loki.lovisual.features.module.Module; import dev.loki.lovisual.features.module.Module;
@ -24,14 +26,45 @@ import java.util.Set;
) )
public class NoSound extends Module { public class NoSound extends Module {
private static final String SETTING_SOUND_IDS = "sound_ids"; private static final String SETTING_SOUND_IDS = "sound_ids";
private static final String SETTING_MODE = "mode";
private static final String SETTING_VOLUME_SCALE = "volume_scale";
private final SetValue soundIds = textList("noSoundIds", SETTING_SOUND_IDS, TextListSetting.PickerMode.SOUNDS); private final SetValue soundIds = textList("noSoundIds", SETTING_SOUND_IDS, TextListSetting.PickerMode.SOUNDS);
private final ModeValue mode = mode("noSoundMode", SETTING_MODE,
Mode.MUTE.id, Mode.REDUCE.id);
private final NumberValue<Integer> volumeScale =
visibleWhen(num("noSoundVolumeScale", SETTING_VOLUME_SCALE, 30, 1, 100),
() -> Mode.REDUCE.id.equals(mode.get()));
{ {
soundIds.set(Set.of("minecraft:entity.ender_dragon.growl")); soundIds.set(Set.of("minecraft:entity.ender_dragon.growl"));
} }
public enum Mode {
MUTE("Mute"),
REDUCE("Reduce");
public final String id;
Mode(String id) {
this.id = id;
}
}
public boolean shouldMute(String id) { public boolean shouldMute(String id) {
if (!isEnabled()) return false; return isEnabled() && Mode.MUTE.id.equals(mode.get()) && matches(id);
}
/**
* Volume multiplier for the sound engine: 1.0 unless the sound is listed
* and the module is in reduce mode.
*/
public float volumeScale(String id) {
if (!isEnabled() || !Mode.REDUCE.id.equals(mode.get())) return 1.0f;
return resolveScale(matches(id), volumeScale.get());
}
private boolean matches(String id) {
if (id == null || id.isEmpty()) return false; if (id == null || id.isEmpty()) return false;
String normalized = id.toLowerCase(Locale.ROOT); String normalized = id.toLowerCase(Locale.ROOT);
for (String mutedId : soundIds.get()) { for (String mutedId : soundIds.get()) {
@ -39,4 +72,10 @@ public class NoSound extends Module {
} }
return false; return false;
} }
/** Pure scale math shared with the sound-engine mixin. */
public static float resolveScale(boolean matches, int scalePct) {
if (!matches) return 1.0f;
return Math.max(0, Math.min(100, scalePct)) / 100.0f;
}
} }

View file

@ -0,0 +1,67 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.misc;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
/**
* Pure scroll-quick-move logic for ItemScroller: resolves which slot should
* receive a QUICK_MOVE click when the user scrolls over the screen.
*/
public final class ScrollMovePolicy {
private ScrollMovePolicy() {
}
public static boolean isScrollSignificant(double vertical) {
return vertical > 0.05 || vertical < -0.05;
}
public static int direction(double vertical) {
return vertical > 0 ? 1 : -1;
}
/**
* QUICK_MOVE button (shift-click) as understood by
* {@code MultiPlayerGameMode.handleContainerInput} for a left click.
*/
public static int quickMoveButton() {
return 0;
}
/**
* Resolves the slot index that should receive the shift-click: the hovered
* slot when it exists, otherwise the hotbar slot matching the scroll
* direction (vanilla hotbar cycling) mapped into the menu.
*/
public static int resolveTargetSlot(boolean hasHovered, int hoveredIndex,
int hotbarSlot, int menuSlotCount) {
if (hasHovered) return hoveredIndex;
return hotbarIndexInMenu(hotbarSlot, menuSlotCount);
}
public static int hotbarIndexInMenu(int hotbarSlot, int menuSlotCount) {
if (menuSlotCount <= 0) return -1;
// In the standard inventory menu the hotbar occupies the last 9 slots.
int hotbarStart = menuSlotCount - Inventory.getSelectionSize();
if (hotbarStart < 0) return -1;
return hotbarStart + Math.floorMod(hotbarSlot, Inventory.getSelectionSize());
}
/**
* Double-check before sending a packet: an empty source stack must never
* produce a click, and the slot must belong to the open menu.
*/
public static boolean canClick(AbstractContainerMenu menu, int slotIndex, ItemStack hoveredStack) {
if (menu == null || slotIndex < 0 || slotIndex >= menu.slots.size()) return false;
if (hoveredStack == null || hoveredStack.isEmpty()) return false;
return true;
}
}

View file

@ -0,0 +1,121 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.misc;
import dev.loki.lovisual.config.values.BooleanValue;
import dev.loki.lovisual.config.values.StringValue;
import dev.loki.lovisual.features.module.Module;
import dev.loki.lovisual.features.module.ModuleCategory;
import dev.loki.lovisual.features.module.ModuleInfo;
import dev.loki.lovisual.features.module.Modules;
import dev.loki.lovisual.util.text.NameMasker;
import java.util.List;
/**
* Streamer mode: masks every nickname-like token in chat/tab/scoreboard text
* (so both player names and server-brand words disappear deterministically),
* and can replace real player skins with the default Steve/Alex texture.
* Hook points reuse the NameProtect mixins plus a skin override in
* AbstractClientPlayerMixin.
*/
@ModuleInfo(
id = "streamermode",
displayName = "Streamer Mode",
aliases = {"streamermode", "streamer"},
category = ModuleCategory.MISC
)
public class StreamerMode extends Module {
private static final String SETTING_ALIAS = "alias";
private static final String SETTING_MASK_CHAT = "mask_chat";
private static final String SETTING_MASK_SERVER_WORDS = "mask_server_words";
private static final String SETTING_HIDE_SKINS = "hide_skins";
private final StringValue alias = text("streamerModeAlias", SETTING_ALIAS, "Player");
private final BooleanValue maskChat = bool("streamerModeMaskChat", SETTING_MASK_CHAT, true);
private final BooleanValue maskServerWords =
visibleWhen(bool("streamerModeMaskServerWords", SETTING_MASK_SERVER_WORDS, true),
maskChat::get);
private final BooleanValue hideSkins = bool("streamerModeHideSkins", SETTING_HIDE_SKINS, true);
private volatile NameMasker cachedMasker;
@Override
public void onDisable() {
cachedMasker = null;
}
public boolean shouldHideSkins() {
return isEnabled() && hideSkins.get();
}
public boolean shouldMaskChat() {
return isEnabled() && maskChat.get();
}
/** @return null when the module is off or chat masking is disabled */
public static NameMasker activeMasker() {
StreamerMode module = Modules.get(StreamerMode.class);
if (module == null || !module.shouldMaskChat()) return null;
return module.masker();
}
/** Rewrites chat/tab component text; mirrors NameProtect.protect usage. */
public static String maskText(String text) {
if (text == null) return null;
NameMasker masker = activeMasker();
if (masker == null) return text;
return masker.mask(text);
}
/**
* Component-tree variant for chat mixins: rewrites plain-text leaves and
* returns the input instance when nothing changed.
*/
public static net.minecraft.network.chat.Component protectComponent(net.minecraft.network.chat.Component text) {
if (text == null) return null;
NameMasker masker = activeMasker();
if (masker == null) return text;
net.minecraft.network.chat.ComponentContents contents = text.getContents();
net.minecraft.network.chat.MutableComponent result;
if (contents instanceof net.minecraft.network.chat.contents.PlainTextContents plain) {
String replaced = masker.mask(plain.text());
if (replaced.equals(plain.text())) {
result = net.minecraft.network.chat.MutableComponent.create(contents).withStyle(text.getStyle());
} else {
result = net.minecraft.network.chat.Component.literal(replaced).withStyle(text.getStyle());
}
} else {
result = net.minecraft.network.chat.MutableComponent.create(contents).withStyle(text.getStyle());
}
for (net.minecraft.network.chat.Component child : text.getSiblings()) {
result.append(protectComponent(child));
}
return result;
}
private NameMasker masker() {
NameMasker cached = cachedMasker;
if (cached != null) return cached;
List<String> words = maskServerWords.get()
? List.of("anarchy", "анархия", "hitech", "хайтек", "magic", "магия",
"vanilla", "ванил", "skyblock", "скайблок")
: List.of();
NameMasker masker = new NameMasker(alias.get(), words);
cachedMasker = masker;
return masker;
}
/** Pure helper for the skin mixin: is this UUID worth hiding (not self). */
public static boolean hideSkin(boolean enabled, boolean isSelf) {
return enabled && !isSelf;
}
}

View file

@ -0,0 +1,140 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.player;
import dev.loki.lovisual.config.values.BooleanValue;
import dev.loki.lovisual.config.values.NumberValue;
import dev.loki.lovisual.config.values.SetValue;
import dev.loki.lovisual.config.values.StringValue;
import dev.loki.lovisual.events.EventHandler;
import dev.loki.lovisual.events.impl.PvpChatEvent;
import dev.loki.lovisual.features.gui.clickgui.settings.TextListSetting;
import dev.loki.lovisual.features.module.Module;
import dev.loki.lovisual.features.module.ModuleCategory;
import dev.loki.lovisual.features.module.ModuleInfo;
import dev.loki.lovisual.features.module.Notifier;
import dev.loki.lovisual.features.relations.PlayerRelations;
import dev.loki.lovisual.util.time.GameClock;
import net.minecraft.client.Minecraft;
import java.util.Locale;
import java.util.Set;
/**
* Answers teleport/clan requests automatically by sending a configurable
* command once a chat message matches one of the configured patterns.
* Optionally restricted to friends via PlayerRelations.
*/
@ModuleInfo(
id = "autoaccept",
displayName = "Auto Accept",
aliases = {"autoaccept"},
category = ModuleCategory.PLAYER
)
public class AutoAccept extends Module {
private static final String SETTING_PATTERNS = "patterns";
private static final String SETTING_COMMAND = "command";
private static final String SETTING_ONLY_FRIENDS = "only_friends";
private static final String SETTING_DELAY = "delay";
private static final String SETTING_COOLDOWN = "cooldown";
private static final long MIN_COOLDOWN_MS = 500L;
private final SetValue patterns = textList("autoAcceptPatterns", SETTING_PATTERNS,
TextListSetting.PickerMode.TEXT,
Set.of(
"просит телепортироваться к себе",
"просит телепортироваться",
"хочет телепортироваться к вам",
"has requested to teleport to you",
"has requested to teleport",
"wants to teleport to you"
));
private final StringValue command = text("autoAcceptCommand", SETTING_COMMAND, "tpaccept");
private final BooleanValue onlyFriends = bool("autoAcceptOnlyFriends", SETTING_ONLY_FRIENDS, false);
private final NumberValue<Integer> delay = num("autoAcceptDelay", SETTING_DELAY, 0, 0, 3000);
private final NumberValue<Integer> cooldown =
num("autoAcceptCooldown", SETTING_COOLDOWN, 2000, (int) MIN_COOLDOWN_MS, 10000);
private String pendingSender;
private long firstMatchMs;
private long lastAcceptedMs = Long.MIN_VALUE;
@Override
public void onDisable() {
pendingSender = null;
}
@Override
public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null || mc.getConnection() == null) {
pendingSender = null;
return;
}
if (pendingSender == null) return;
long now = GameClock.millis();
if (now - firstMatchMs < delay.get()) return;
String sender = pendingSender;
pendingSender = null;
if (now - lastAcceptedMs < Math.max(MIN_COOLDOWN_MS, cooldown.get())) return;
lastAcceptedMs = now;
Notifier.info("Auto Accept: " + sender);
mc.getConnection().sendCommand(command.get());
}
@EventHandler
private void onChat(PvpChatEvent event) {
if (event.message == null || event.message.isBlank()) return;
String sender = TeleportRequestParser.extractSender(event.message, patterns.get());
if (sender == null) return;
if (onlyFriends.get() && !PlayerRelations.get().isFriend(sender)) return;
if (pendingSender != null) return;
pendingSender = sender;
firstMatchMs = GameClock.millis();
}
/**
* Pure chat-request parsing: finds the earliest configured pattern and
* extracts the nickname-like token immediately before it.
*/
public static final class TeleportRequestParser {
private TeleportRequestParser() {
}
public static String extractSender(String message, Set<String> patterns) {
if (message == null || patterns == null || patterns.isEmpty()) return null;
String lower = message.toLowerCase(Locale.ROOT);
int matchIndex = -1;
for (String pattern : patterns) {
if (pattern == null || pattern.isBlank()) continue;
int idx = lower.indexOf(pattern.toLowerCase(Locale.ROOT));
if (idx >= 0 && (matchIndex < 0 || idx < matchIndex)) matchIndex = idx;
}
if (matchIndex <= 0) return null;
return lastNicknameIn(message.substring(0, matchIndex));
}
static String lastNicknameIn(String prefix) {
java.util.regex.Matcher matcher =
java.util.regex.Pattern.compile("[A-Za-z0-9_]{3,16}").matcher(prefix);
String found = null;
while (matcher.find()) {
found = matcher.group();
}
return found;
}
}
}

View file

@ -0,0 +1,159 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.player;
import dev.loki.lovisual.config.values.BooleanValue;
import dev.loki.lovisual.config.values.NumberValue;
import dev.loki.lovisual.features.module.Module;
import dev.loki.lovisual.features.module.ModuleCategory;
import dev.loki.lovisual.features.module.ModuleInfo;
import dev.loki.lovisual.features.module.Notifier;
import dev.loki.lovisual.features.relations.PlayerRelations;
import dev.loki.lovisual.util.time.GameClock;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.player.Player;
/**
* Leaves the server by simple safety triggers: low health or unfriendly
* players nearby. Fires once, after a configurable delay, with a warning
* notification so the leave never comes as a surprise.
*/
@ModuleInfo(
id = "autoleave",
displayName = "Auto Leave",
aliases = {"autoleave", "autodc"},
category = ModuleCategory.PLAYER
)
public class AutoLeave extends Module {
private static final String SETTING_HP_ENABLED = "hp_enabled";
private static final String SETTING_HP_THRESHOLD = "hp_threshold";
private static final String SETTING_PLAYERS_ENABLED = "players_enabled";
private static final String SETTING_PLAYER_COUNT = "player_count";
private static final String SETTING_RADIUS = "radius";
private static final String SETTING_IGNORE_FRIENDS = "ignore_friends";
private static final String SETTING_DELAY = "delay";
private final BooleanValue hpEnabled = bool("autoLeaveHpEnabled", SETTING_HP_ENABLED, true);
private final NumberValue<Double> hpThreshold =
visibleWhen(num("autoLeaveHpThreshold", SETTING_HP_THRESHOLD, 6.0, 1.0, 19.0), hpEnabled::get);
private final BooleanValue playersEnabled = bool("autoLeavePlayersEnabled", SETTING_PLAYERS_ENABLED, false);
private final NumberValue<Integer> playerCount =
visibleWhen(num("autoLeavePlayerCount", SETTING_PLAYER_COUNT, 1, 1, 20), playersEnabled::get);
private final NumberValue<Integer> radius =
visibleWhen(num("autoLeaveRadius", SETTING_RADIUS, 32, 8, 128), playersEnabled::get);
private final BooleanValue ignoreFriends =
visibleWhen(bool("autoLeaveIgnoreFriends", SETTING_IGNORE_FRIENDS, true), playersEnabled::get);
private final NumberValue<Integer> delay = num("autoLeaveDelay", SETTING_DELAY, 1000, 0, 5000);
private final LeavePolicy policy = new LeavePolicy();
private LocalPlayer lastPlayer;
@Override
public void onDisable() {
lastPlayer = null;
policy.reset();
}
@Override
public void onFrame(float tickDelta) {
// onTick only fires while a player exists; re-arming on world change
// has to happen where the player absence is still observable.
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null) {
lastPlayer = null;
policy.reset();
return;
}
if (mc.player != lastPlayer) {
lastPlayer = mc.player;
policy.reset();
}
}
@Override
public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.player == null || mc.level == null) return;
boolean triggered = isLowHealth(mc.player) || hasUnfriendlyPlayersNearby(mc);
if (!policy.onTick(triggered, GameClock.millis(), delay.get())) return;
if (policy.consumeWarning()) {
Notifier.warning("Auto Leave: disconnecting now");
}
leave(mc);
}
private boolean isLowHealth(LocalPlayer player) {
return hpEnabled.get() && LeavePolicy.isHealthLow(player.getHealth(), hpThreshold.get().floatValue());
}
private boolean hasUnfriendlyPlayersNearby(Minecraft mc) {
if (!playersEnabled.get()) return false;
double radiusSq = (double) radius.get() * radius.get();
int unfriendly = 0;
for (Player other : mc.level.players()) {
if (other == mc.player || other.isSpectator()) continue;
if (!other.isAlive()) continue;
if (ignoreFriends.get() && PlayerRelations.get().isFriend(other.getGameProfile().name())) continue;
if (other.distanceToSqr(mc.player) > radiusSq) continue;
unfriendly++;
}
return LeavePolicy.isPlayerCountReached(unfriendly, playerCount.get());
}
private void leave(Minecraft mc) {
policy.reset();
mc.disconnect(null, false);
}
/**
* Pure trigger state machine: warn once on a rising trigger edge, then
* leave after the configured delay. Any quiet tick re-arms the gate.
*/
public static final class LeavePolicy {
private boolean triggered;
private long triggerMs;
private boolean warningShown;
public boolean onTick(boolean anyTrigger, long nowMs, long delayMs) {
if (!anyTrigger) {
reset();
return false;
}
if (!triggered) {
triggered = true;
triggerMs = nowMs;
warningShown = false;
return Math.max(0L, delayMs) <= 0L;
}
return nowMs - triggerMs >= Math.max(0L, delayMs);
}
public boolean consumeWarning() {
if (warningShown) return false;
warningShown = true;
return true;
}
public void reset() {
triggered = false;
triggerMs = 0L;
warningShown = false;
}
public static boolean isHealthLow(float health, float threshold) {
return health <= threshold;
}
public static boolean isPlayerCountReached(int count, int required) {
return count >= Math.max(1, required);
}
}
}

View file

@ -35,13 +35,12 @@ public class AutoTool extends Module {
private final NumberValue<Integer> restoreDelayMs = private final NumberValue<Integer> restoreDelayMs =
num("autoToolRestoreDelayMs", SETTING_RESTORE_DELAY, 300, 0, 1000); num("autoToolRestoreDelayMs", SETTING_RESTORE_DELAY, 300, 0, 1000);
private final Minecraft mc = Minecraft.getInstance();
private int originalSlot = -1; private int originalSlot = -1;
private long lastBreakMs = 0L; private long lastBreakMs = 0L;
@Override @Override
public void onDisable() { public void onDisable() {
Minecraft mc = Minecraft.getInstance();
if (mc != null && mc.player != null && originalSlot >= 0) { if (mc != null && mc.player != null && originalSlot >= 0) {
InventorySwap.INSTANCE.selectHotbar(originalSlot); InventorySwap.INSTANCE.selectHotbar(originalSlot);
} }
@ -51,6 +50,7 @@ public class AutoTool extends Module {
@EventHandler @EventHandler
private void onGameTick(GameTickEvent event) { private void onGameTick(GameTickEvent event) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.player == null || mc.level == null || mc.options == null || mc.gameMode == null) { if (!isEnabled() || mc.player == null || mc.level == null || mc.options == null || mc.gameMode == null) {
resetState(); resetState();
return; return;
@ -83,6 +83,7 @@ public class AutoTool extends Module {
} }
private int findBestHotbarTool(BlockPos pos) { private int findBestHotbarTool(BlockPos pos) {
Minecraft mc = Minecraft.getInstance();
if (mc.player == null || mc.level == null) return -1; if (mc.player == null || mc.level == null) return -1;
BlockState state = mc.level.getBlockState(pos); BlockState state = mc.level.getBlockState(pos);
if (state.isAir()) return -1; if (state.isAir()) return -1;

View file

@ -29,7 +29,6 @@ import dev.loki.lovisual.util.player.inventory.InventorySwap;
public class ClickPearl extends Module { public class ClickPearl extends Module {
private static final String ACTION_CLICKPEARL = "clickpearl"; private static final String ACTION_CLICKPEARL = "clickpearl";
private final Minecraft client = Minecraft.getInstance();
private ItemStack previousHeld = ItemStack.EMPTY; private ItemStack previousHeld = ItemStack.EMPTY;
public ClickPearl() { public ClickPearl() {
@ -43,6 +42,7 @@ public class ClickPearl extends Module {
@Override @Override
public void onTick() { public void onTick() {
if (!isEnabled()) return; if (!isEnabled()) return;
Minecraft client = Minecraft.getInstance();
if (client.player == null || client.gameMode == null) return; if (client.player == null || client.gameMode == null) return;
if (isActionPressedOnce(ACTION_CLICKPEARL)) { if (isActionPressedOnce(ACTION_CLICKPEARL)) {
@ -54,6 +54,7 @@ public class ClickPearl extends Module {
// ТВОЯ ЛОГИКА — НЕ ТРОНУТА // ТВОЯ ЛОГИКА — НЕ ТРОНУТА
// =========================== // ===========================
private void tryThrowPearl() { private void tryThrowPearl() {
Minecraft client = Minecraft.getInstance();
LocalPlayer player = client.player; LocalPlayer player = client.player;
MultiPlayerGameMode manager = client.gameMode; MultiPlayerGameMode manager = client.gameMode;
if (player == null || manager == null) return; if (player == null || manager == null) return;

View file

@ -99,8 +99,6 @@ public final class NoDelay extends Module {
visibleWhen(num("noDelayFastBreakProgress", SETTING_FAST_BREAK_PROGRESS, 0.5f, 0.1f, 1.0f), visibleWhen(num("noDelayFastBreakProgress", SETTING_FAST_BREAK_PROGRESS, 0.5f, 0.1f, 1.0f),
() -> types.get(TYPE_FAST_BREAK)); () -> types.get(TYPE_FAST_BREAK));
private final Minecraft mc = Minecraft.getInstance();
private long pressStartThrow = 0L; private long pressStartThrow = 0L;
private long lastThrow = 0L; private long lastThrow = 0L;
private long pressStartInteract = 0L; private long pressStartInteract = 0L;
@ -122,6 +120,7 @@ public final class NoDelay extends Module {
@Override @Override
public void onTick() { public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc == null || mc.player == null) { if (!isEnabled() || mc == null || mc.player == null) {
resetBreakBoost(); resetBreakBoost();
return; return;
@ -162,6 +161,7 @@ public final class NoDelay extends Module {
} }
public boolean handleFastUse() { public boolean handleFastUse() {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled()) return false; if (!isEnabled()) return false;
if (mc == null || mc.player == null || mc.gameMode == null) return false; if (mc == null || mc.player == null || mc.gameMode == null) return false;
if (!types.get(TYPE_FAST_THROW) && !types.get(TYPE_FAST_INTERACT)) return false; if (!types.get(TYPE_FAST_THROW) && !types.get(TYPE_FAST_INTERACT)) return false;
@ -214,6 +214,7 @@ public final class NoDelay extends Module {
} }
private void applyBlockPlaceCooldown() { private void applyBlockPlaceCooldown() {
Minecraft mc = Minecraft.getInstance();
if (mc.player == null || !mc.options.keyUse.isDown()) return; if (mc.player == null || !mc.options.keyUse.isDown()) return;
if (!isBlockPlaceCandidate(mc.player, mc.hitResult)) return; if (!isBlockPlaceCandidate(mc.player, mc.hitResult)) return;
@ -233,6 +234,7 @@ public final class NoDelay extends Module {
} }
private void applyFastBreakBoost() { private void applyFastBreakBoost() {
Minecraft mc = Minecraft.getInstance();
if (mc.gameMode == null || mc.getConnection() == null) { if (mc.gameMode == null || mc.getConnection() == null) {
resetBreakBoost(); resetBreakBoost();
return; return;
@ -282,6 +284,7 @@ public final class NoDelay extends Module {
} }
private Direction resolveBreakingDirection(BlockPos breakingPos) { private Direction resolveBreakingDirection(BlockPos breakingPos) {
Minecraft mc = Minecraft.getInstance();
HitResult hit = mc.hitResult; HitResult hit = mc.hitResult;
if (hit instanceof BlockHitResult blockHit && breakingPos.equals(blockHit.getBlockPos())) { if (hit instanceof BlockHitResult blockHit && breakingPos.equals(blockHit.getBlockPos())) {
return blockHit.getDirection(); return blockHit.getDirection();
@ -319,6 +322,7 @@ public final class NoDelay extends Module {
} }
private InteractionResult performFastUse(LocalPlayer player, UseContext ctx) { private InteractionResult performFastUse(LocalPlayer player, UseContext ctx) {
Minecraft mc = Minecraft.getInstance();
if (ctx.target == UseTarget.THROWABLE) { if (ctx.target == UseTarget.THROWABLE) {
return mc.gameMode.useItem(player, ctx.hand); return mc.gameMode.useItem(player, ctx.hand);
} }

View file

@ -0,0 +1,56 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.player;
import dev.loki.lovisual.config.values.BindMode;
import dev.loki.lovisual.config.values.NumberValue;
import dev.loki.lovisual.features.module.Module;
import dev.loki.lovisual.features.module.ModuleCategory;
import dev.loki.lovisual.features.module.ModuleInfo;
/**
* Free third-person look: while the bind is held the camera orbits freely and
* the player's movement direction stays untouched. No camera entity is
* spawned — only the vanilla third-person camera is extended.
*/
@ModuleInfo(
id = "thirdperson",
displayName = "Third Person",
aliases = {"thirdperson", "3p"},
category = ModuleCategory.PLAYER
)
public class ThirdPerson extends Module {
private static final String SETTING_LOOK = "look";
private static final String SETTING_DISTANCE = "distance";
private final NumberValue<Double> distance = num("thirdPersonDistance", SETTING_DISTANCE, 4.0, 1.0, 16.0);
public float orbitYaw;
public float orbitPitch;
{
action(SETTING_LOOK, "L", BindMode.HOLD);
}
/**
* Camera input is captured only while the action bind is held; otherwise
* the vanilla turnPlayer path stays in charge.
*/
public boolean isLooking() {
return isEnabled() && isActionHeld(SETTING_LOOK);
}
public double distance() {
return distance.get();
}
/** Pure pitch clamp helper shared with the mouse mixin. */
public static float clampPitch(float pitch) {
return Math.max(-90.0f, Math.min(90.0f, pitch));
}
}

View file

@ -0,0 +1,144 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.player;
import dev.loki.lovisual.config.values.BooleanMapValue;
import dev.loki.lovisual.config.values.BooleanValue;
import dev.loki.lovisual.events.EventHandler;
import dev.loki.lovisual.events.impl.PacketEvent;
import dev.loki.lovisual.features.module.Module;
import dev.loki.lovisual.features.module.ModuleCategory;
import dev.loki.lovisual.features.module.ModuleInfo;
import dev.loki.lovisual.features.module.Notifier;
import dev.loki.lovisual.util.protection.NotifyThrottle;
import net.minecraft.util.Util;
import net.minecraft.client.Minecraft;import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.Holder;
import net.minecraft.network.protocol.game.ClientboundEntityEventPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Items;
import java.util.Map;
/**
* Announces notable item/effect uses in the HUD: totem pops (self and
* opponents) and selected combat effects applied to opponents. All decisions
* are packet-driven, so nothing is missed by render-tick sampling.
*/
@ModuleInfo(
id = "usetracker",
displayName = "Use Tracker",
aliases = {"usetracker"},
category = ModuleCategory.PLAYER
)
public class UseTracker extends Module {
static final byte TOTEM_POP_EVENT_ID = 35;
private static final String SETTING_SELF_TOTEMS = "self_totems";
private static final String SETTING_OTHER_TOTEMS = "other_totems";
private static final String SETTING_OTHER_EFFECTS = "other_effects";
private final BooleanValue selfTotems = bool("useTrackerSelfTotems", SETTING_SELF_TOTEMS, true);
private final BooleanValue otherTotems = bool("useTrackerOtherTotems", SETTING_OTHER_TOTEMS, true);
private final BooleanMapValue otherEffects = group("useTrackerOtherEffects", SETTING_OTHER_EFFECTS,
UseEffects.defaultOptions());
private final NotifyThrottle throttle = new NotifyThrottle(300L);
@EventHandler
private void onPacket(PacketEvent.Receive event) {
if (event.getPacket() instanceof ClientboundEntityEventPacket entityEvent) {
handleEntityEvent(entityEvent);
} else if (event.getPacket() instanceof ClientboundUpdateMobEffectPacket effectPacket) {
handleEffectPacket(effectPacket);
}
}
private void handleEntityEvent(ClientboundEntityEventPacket packet) {
if (packet.getEventId() != TOTEM_POP_EVENT_ID) return;
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.level == null) return;
Entity entity = packet.getEntity(mc.level);
if (!(entity instanceof Player player)) return;
if (player instanceof LocalPlayer) {
if (!selfTotems.get() || !throttle.tryAcquire(Util.getMillis())) return;
Notifier.warning("Totem popped: " + countTotems(mc.player) + " left");
} else if (otherTotems.get()) {
Notifier.info(player.getGameProfile().name() + " popped a totem");
}
}
private void handleEffectPacket(ClientboundUpdateMobEffectPacket packet) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.level == null) return;
Entity entity = mc.level.getEntity(packet.getEntityId());
if (!(entity instanceof Player player) || player instanceof LocalPlayer) return;
String key = UseEffects.optionKey(packet.getEffect());
if (key == null || !otherEffects.get(key)) return;
Notifier.info(player.getGameProfile().name() + " used " + UseEffects.displayName(key));
}
private static int countTotems(Player player) {
int count = 0;
var inventory = player.getInventory();
for (int i = 0; i < inventory.getContainerSize(); i++) {
if (inventory.getItem(i).is(Items.TOTEM_OF_UNDYING)) count++;
}
return count;
}
/**
* Pure effect bookkeeping: which combat effects are trackable and how they
* are displayed. Holder-to-key mapping happens in the module.
*/
public static final class UseEffects {
private UseEffects() {
}
public static Map<String, Boolean> defaultOptions() {
return Map.of(
"invisibility", true,
"speed", false,
"strength", false,
"regeneration", false,
"fire_resistance", false,
"weakness", false
);
}
static String optionKey(Holder<MobEffect> effect) {
if (effect == MobEffects.INVISIBILITY) return "invisibility";
if (effect == MobEffects.SPEED) return "speed";
if (effect == MobEffects.STRENGTH) return "strength";
if (effect == MobEffects.REGENERATION) return "regeneration";
if (effect == MobEffects.FIRE_RESISTANCE) return "fire_resistance";
if (effect == MobEffects.WEAKNESS) return "weakness";
return null;
}
static String displayName(String key) {
return switch (key) {
case "invisibility" -> "Invisibility";
case "speed" -> "Speed";
case "strength" -> "Strength";
case "regeneration" -> "Regeneration";
case "fire_resistance" -> "Fire Resistance";
case "weakness" -> "Weakness";
default -> "an effect";
};
}
}
}

View file

@ -35,8 +35,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
) )
public final class BlockESP extends Module { public final class BlockESP extends Module {
private final Minecraft mc = Minecraft.getInstance();
private final ItemIdSetValue blockList = private final ItemIdSetValue blockList =
itemList("block_list", TextListSetting.PickerMode.BLOCKS); itemList("block_list", TextListSetting.PickerMode.BLOCKS);
@ -85,6 +83,7 @@ public final class BlockESP extends Module {
@Override @Override
public void onTick() { public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null || mc.player == null) return; if (mc.level == null || mc.player == null) return;
if (scanTimer.passedS(scanInterval.get())) { if (scanTimer.passedS(scanInterval.get())) {
@ -100,6 +99,7 @@ public final class BlockESP extends Module {
@Override @Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null || mc.player == null) return; if (!isEnabled() || mc.level == null || mc.player == null) return;
if (blockList.get().isEmpty()) return; if (blockList.get().isEmpty()) return;
@ -115,6 +115,7 @@ public final class BlockESP extends Module {
} }
private void requestScan() { private void requestScan() {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null || mc.player == null) return; if (mc.level == null || mc.player == null) return;
if (!scanning.get()) return; if (!scanning.get()) return;
if (executor.isShutdown()) return; if (executor.isShutdown()) return;
@ -130,6 +131,7 @@ public final class BlockESP extends Module {
} }
private void scan() { private void scan() {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null || mc.player == null) return; if (mc.level == null || mc.player == null) return;
List<BlockPos> result = new ArrayList<>(); List<BlockPos> result = new ArrayList<>();

View file

@ -45,7 +45,6 @@ public class BlockHighlight extends Module {
private static final double RAINBOW_SPATIAL_SCALE = 12.0; private static final double RAINBOW_SPATIAL_SCALE = 12.0;
private static final double GEOMETRY_EXPAND = 0.0025; private static final double GEOMETRY_EXPAND = 0.0025;
private final Minecraft mc = Minecraft.getInstance();
private final BooleanValue outlineEnabled = private final BooleanValue outlineEnabled =
bool( bool(
"outline_enabled", "outline_enabled",
@ -240,6 +239,7 @@ public class BlockHighlight extends Module {
@Override @Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null || mc.player == null) return; if (!isEnabled() || mc.level == null || mc.player == null) return;
if (outlineEnabled.get()) { if (outlineEnabled.get()) {
@ -252,6 +252,7 @@ public class BlockHighlight extends Module {
} }
private void renderTargetOutline(Renderer3D renderer) { private void renderTargetOutline(Renderer3D renderer) {
Minecraft mc = Minecraft.getInstance();
HitResult hit = mc.hitResult; HitResult hit = mc.hitResult;
if (!(hit instanceof BlockHitResult blockHit)) return; if (!(hit instanceof BlockHitResult blockHit)) return;
if (hit.getType() == HitResult.Type.MISS) return; if (hit.getType() == HitResult.Type.MISS) return;
@ -417,6 +418,7 @@ public class BlockHighlight extends Module {
} }
private void renderBreakingHighlight(Renderer3D renderer) { private void renderBreakingHighlight(Renderer3D renderer) {
Minecraft mc = Minecraft.getInstance();
if (breakingSelf.get()) { if (breakingSelf.get()) {
renderSelfBreaking(renderer); renderSelfBreaking(renderer);
} }
@ -441,6 +443,7 @@ public class BlockHighlight extends Module {
} }
private void renderSelfBreaking(Renderer3D renderer) { private void renderSelfBreaking(Renderer3D renderer) {
Minecraft mc = Minecraft.getInstance();
MultiPlayerGameMode interactionManager = mc.gameMode; MultiPlayerGameMode interactionManager = mc.gameMode;
if (interactionManager == null || !interactionManager.isDestroying()) return; if (interactionManager == null || !interactionManager.isDestroying()) return;
if (!(interactionManager instanceof MultiPlayerGameModeAccessor accessor)) return; if (!(interactionManager instanceof MultiPlayerGameModeAccessor accessor)) return;
@ -453,6 +456,7 @@ public class BlockHighlight extends Module {
} }
private void renderBreakingInfo(Renderer3D renderer, BlockPos pos, float progress) { private void renderBreakingInfo(Renderer3D renderer, BlockPos pos, float progress) {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null || pos == null) return; if (mc.level == null || pos == null) return;
BlockState state = mc.level.getBlockState(pos); BlockState state = mc.level.getBlockState(pos);

View file

@ -123,7 +123,6 @@ public class Chams extends Module {
private static final float NOISE_CONTRAST = 1.15f; private static final float NOISE_CONTRAST = 1.15f;
private static final float NOISE_BREATHE = 0.55f; private static final float NOISE_BREATHE = 0.55f;
private static final float NOISE_DENSITY = 1.0f; private static final float NOISE_DENSITY = 1.0f;
private final Minecraft mc = Minecraft.getInstance();
private final ModeValue mode = private final ModeValue mode =
modeSetting("handChamsMode", SETTING_MODE, "Smoke", "Smoke", "Metallic", "Glass", "Noise", "Snow", "Chroma", "Solid", "Stripes", "Glow", "Balatro"); modeSetting("handChamsMode", SETTING_MODE, "Smoke", "Smoke", "Metallic", "Glass", "Noise", "Snow", "Chroma", "Solid", "Stripes", "Glow", "Balatro");
@ -297,6 +296,7 @@ public class Chams extends Module {
} }
public boolean isActive() { public boolean isActive() {
Minecraft mc = Minecraft.getInstance();
return isEnabled() && mc.player != null && mc.level != null; return isEnabled() && mc.player != null && mc.level != null;
} }
@ -307,6 +307,7 @@ public class Chams extends Module {
} }
private FeatureRenderDispatcher getStandaloneHandFeatureDispatcher() { private FeatureRenderDispatcher getStandaloneHandFeatureDispatcher() {
Minecraft mc = Minecraft.getInstance();
if (irisHandFeatureDispatcher != null) { if (irisHandFeatureDispatcher != null) {
return irisHandFeatureDispatcher; return irisHandFeatureDispatcher;
} }
@ -342,6 +343,7 @@ public class Chams extends Module {
} }
public boolean renderHandMask(GameRenderer renderer, CameraRenderState cameraRenderState, float tickDelta, Matrix4fc positionMatrix) { public boolean renderHandMask(GameRenderer renderer, CameraRenderState cameraRenderState, float tickDelta, Matrix4fc positionMatrix) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.player == null || mc.level == null) return false; if (!isEnabled() || mc.player == null || mc.level == null) return false;
if (mc.gameMode == null) return false; if (mc.gameMode == null) return false;
if (!hands.get() || !shouldRenderHand()) return false; if (!hands.get() || !shouldRenderHand()) return false;
@ -385,6 +387,7 @@ public class Chams extends Module {
} }
public boolean renderPreparedHandScene(FeatureRenderDispatcher dispatcher, SubmitNodeStorage storage) { public boolean renderPreparedHandScene(FeatureRenderDispatcher dispatcher, SubmitNodeStorage storage) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.player == null || mc.level == null) return false; if (!isEnabled() || mc.player == null || mc.level == null) return false;
if (dispatcher == null || storage == null) return false; if (dispatcher == null || storage == null) return false;
if (!hands.get() || !shouldRenderHand()) return false; if (!hands.get() || !shouldRenderHand()) return false;
@ -433,6 +436,7 @@ public class Chams extends Module {
CameraRenderState cameraRenderState, CameraRenderState cameraRenderState,
Matrix4fc positionMatrix, Matrix4fc positionMatrix,
float tickDelta) { float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!IrisRuntime.isShaderpackRendererActive() || !isEnabled() || mc.player == null || mc.level == null) { if (!IrisRuntime.isShaderpackRendererActive() || !isEnabled() || mc.player == null || mc.level == null) {
return false; return false;
} }
@ -488,6 +492,7 @@ public class Chams extends Module {
FeatureRenderDispatcher dispatcher, FeatureRenderDispatcher dispatcher,
float tickDelta, float tickDelta,
boolean solidPhase) { boolean solidPhase) {
Minecraft mc = Minecraft.getInstance();
IrisHandRendererAccessor irisHand = (IrisHandRendererAccessor) HandRenderer.INSTANCE; IrisHandRendererAccessor irisHand = (IrisHandRendererAccessor) HandRenderer.INSTANCE;
irisHand.lovisual$setRenderingSolid(solidPhase); irisHand.lovisual$setRenderingSolid(solidPhase);
@ -523,6 +528,7 @@ public class Chams extends Module {
} }
private void ensureBuffers() { private void ensureBuffers() {
Minecraft mc = Minecraft.getInstance();
int w = mc.getWindow().getWidth(); int w = mc.getWindow().getWidth();
int h = mc.getWindow().getHeight(); int h = mc.getWindow().getHeight();
if (w <= 0 || h <= 0) return; if (w <= 0 || h <= 0) return;
@ -612,6 +618,7 @@ public class Chams extends Module {
} }
private void ensureDualBuffers() { private void ensureDualBuffers() {
Minecraft mc = Minecraft.getInstance();
int w = mc.getWindow().getWidth(); int w = mc.getWindow().getWidth();
int h = mc.getWindow().getHeight(); int h = mc.getWindow().getHeight();
if (w <= 0 || h <= 0) return; if (w <= 0 || h <= 0) return;
@ -665,6 +672,7 @@ public class Chams extends Module {
} }
private boolean renderHands(GpuTextureView src, GpuTextureView dst, float tickDelta) { private boolean renderHands(GpuTextureView src, GpuTextureView dst, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!maskReady) return false; if (!maskReady) return false;
if (handMask == null) { if (handMask == null) {
maskReady = false; maskReady = false;
@ -792,6 +800,7 @@ public class Chams extends Module {
} }
private void renderChamsEffect(GpuTextureView src, GpuTextureView dst, GpuTextureView maskView, String chamsMode, float rawTime) { private void renderChamsEffect(GpuTextureView src, GpuTextureView dst, GpuTextureView maskView, String chamsMode, float rawTime) {
Minecraft mc = Minecraft.getInstance();
int w = mc.getWindow().getWidth(); int w = mc.getWindow().getWidth();
int h = mc.getWindow().getHeight(); int h = mc.getWindow().getHeight();
float time = rawTime; float time = rawTime;
@ -862,6 +871,7 @@ public class Chams extends Module {
} }
private void renderNoise(GpuTextureView src, GpuTextureView dst, GpuTextureView maskView, float rawTime) { private void renderNoise(GpuTextureView src, GpuTextureView dst, GpuTextureView maskView, float rawTime) {
Minecraft mc = Minecraft.getInstance();
int fillRgb = materialFillRgb(); int fillRgb = materialFillRgb();
int glowRgb = glowRgb(fillRgb); int glowRgb = glowRgb(fillRgb);
@ -941,6 +951,7 @@ public class Chams extends Module {
private final class HandsPass implements PostProcessPass { private final class HandsPass implements PostProcessPass {
@Override @Override
public boolean isActive() { public boolean isActive() {
Minecraft mc = Minecraft.getInstance();
return Chams.this.isEnabled() return Chams.this.isEnabled()
&& mc.player != null && mc.player != null
&& mc.level != null && mc.level != null
@ -955,6 +966,7 @@ public class Chams extends Module {
@Override @Override
public boolean render(GpuTextureView src, GpuTextureView dst, float tickDelta) { public boolean render(GpuTextureView src, GpuTextureView dst, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!Chams.this.isEnabled() || mc.player == null || mc.level == null) return false; if (!Chams.this.isEnabled() || mc.player == null || mc.level == null) return false;
if (!hands.get() || !mc.options.getCameraType().isFirstPerson()) return false; if (!hands.get() || !mc.options.getCameraType().isFirstPerson()) return false;
return Chams.this.renderHands(src, dst, tickDelta); return Chams.this.renderHands(src, dst, tickDelta);

View file

@ -25,8 +25,9 @@ import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
/** /**
* Classic conical Chinese hat (rice hat / coolie hat): a wide flat brim annulus * Classic conical Chinese hat (rice hat / coolie hat): a wide shallow cone —
* topped by a cone dome. Geometry lives in {@link ChinaHatModel}, colors in * apex on top, rim circle around the crown — matching the SoupVisuals profile
* (radius:height ≈ 2:1). Geometry lives in {@link ChinaHatModel}, colors in
* {@link ChinaHatPalette}; this class only wires settings to rendering. * {@link ChinaHatPalette}; this class only wires settings to rendering.
*/ */
@ModuleInfo( @ModuleInfo(
@ -36,8 +37,6 @@ import net.minecraft.world.entity.player.Player;
) )
public final class ChinaHat extends Module { public final class ChinaHat extends Module {
private final Minecraft mc = Minecraft.getInstance();
private final EnumValue<RenderTarget> renderTarget = private final EnumValue<RenderTarget> renderTarget =
enumMode( enumMode(
"render_target", "render_target",
@ -115,30 +114,16 @@ public final class ChinaHat extends Module {
private final NumberValue<Float> brimRadius = private final NumberValue<Float> brimRadius =
num( num(
"brim_radius", "brim_radius",
0.45f, 0.5f,
0.2f, 0.2f,
0.7f 0.9f
); );
private final NumberValue<Float> domeRadius = private final NumberValue<Float> coneHeight =
num( num(
"dome_radius", "cone_height",
0.16f, 0.22f,
0.06f, 0.05f,
0.35f 0.6f
);
private final NumberValue<Float> domeHeight =
num(
"dome_height",
0.3f,
0.1f,
0.55f
);
private final NumberValue<Float> brimThickness =
num(
"brim_thickness",
0.025f,
0.01f,
0.08f
); );
private final NumberValue<Float> headTopOffset = private final NumberValue<Float> headTopOffset =
num( num(
@ -169,6 +154,7 @@ public final class ChinaHat extends Module {
@Override @Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null || mc.player == null) return; if (!isEnabled() || mc.level == null || mc.player == null) return;
MeshBuilder tris = renderer.batch( MeshBuilder tris = renderer.batch(
@ -245,14 +231,11 @@ public final class ChinaHat extends Module {
anchor, anchor,
segments.get(), segments.get(),
brimRadius.get() * size, brimRadius.get() * size,
domeRadius.get() * size, coneHeight.get() * size,
domeHeight.get() * size,
brimThickness.get() * size,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
(index, light, alphaMul) -> ChinaHatPalette.colorAt(palette, mode, speed, index, light, alphaMul), (index, light, alphaMul) -> ChinaHatPalette.colorAt(palette, mode, speed, index, light, alphaMul),
(index, light, alphaMul) -> ChinaHatPalette.colorAt(palette, mode, speed, index, light, alphaMul),
(index, light, alphaMul) -> ChinaHatPalette.rimColorAt(palette, mode, speed, index, alphaMul), (index, light, alphaMul) -> ChinaHatPalette.rimColorAt(palette, mode, speed, index, alphaMul),
1.0f 1.0f
); );

View file

@ -13,10 +13,14 @@ import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
/** /**
* Geometry for the classic ChinaHat: a wide flat brim (annulus) plus a conical dome. * Classic coolie/rice hat: a single smooth cone from the apex resting on the
* Pure mesh assembly, no Minecraft state. Per-vertex colors come from the caller. * head up to a raised rim circle, like the SoupVisuals reference profile
* (radius 0.65 vs height 0.3). Rendered double-sided via opposite windings.
* {@link #layout} is pure geometry (unit-testable); {@link #build} feeds the
* result into MeshBuilder. Per-vertex colors come from the caller.
*/ */
final class ChinaHatModel { final class ChinaHatModel {
private ChinaHatModel() { private ChinaHatModel() {
} }
@ -24,76 +28,59 @@ final class ChinaHatModel {
int color(int index, float verticalLight, float alphaMul); int color(int index, float verticalLight, float alphaMul);
} }
/** Apex plus rim circle vertices in anchor space; rim[i+1 wraps to rim[0]. */
record Layout(Vec3 apex, Vec3[] rim) {
}
static Layout layout(float brimRadius,
float coneHeight,
int segments,
float yawDeg,
float pitchDeg,
float rollDeg,
HeadAnchor anchor) {
Vec3 apex = anchor.localRotated(0.0f, coneHeight, 0.0f, yawDeg, pitchDeg, rollDeg);
Vec3[] rim = new Vec3[Math.max(0, segments)];
for (int i = 0; i < rim.length; i++) {
double angle = (Math.PI * 2.0 * i) / segments;
float s = Mth.sin((float) angle);
float c = Mth.cos((float) angle);
rim[i] = anchor.localRotated(s * brimRadius, 0.0f, c * brimRadius,
yawDeg, pitchDeg, rollDeg);
}
return new Layout(apex, rim);
}
static void build(MeshBuilder tris, static void build(MeshBuilder tris,
MeshBuilder lines, MeshBuilder lines,
HeadAnchor anchor, HeadAnchor anchor,
int segments, int segments,
float brimRadius, float brimRadius,
float domeRadius, float coneHeight,
float domeHeight,
float brimThickness,
float yawDeg, float yawDeg,
float pitchDeg, float pitchDeg,
float rollDeg, float rollDeg,
Shade domeShade, Shade coneShade,
Shade brimShade,
Shade rimShade, Shade rimShade,
float alphaMul) { float alphaMul) {
if (tris == null || lines == null || segments < 3) return; if (tris == null || segments < 3) return;
float outerY = brimThickness; Layout layout = layout(brimRadius, coneHeight, segments, yawDeg, pitchDeg, rollDeg, anchor);
float innerY = 0.0f; Vec3 apex = layout.apex();
Vec3[] outerTop = new Vec3[segments]; Vec3[] rim = layout.rim();
Vec3[] outerBottom = new Vec3[segments];
Vec3[] innerTop = new Vec3[segments];
Vec3[] innerBottom = new Vec3[segments];
for (int i = 0; i < segments; i++) {
double angle = (Math.PI * 2.0 * i) / segments;
float s = Mth.sin((float) angle);
float c = Mth.cos((float) angle);
outerTop[i] = anchor.localRotated(s * brimRadius, outerY, c * brimRadius, yawDeg, pitchDeg, rollDeg);
outerBottom[i] = anchor.localRotated(s * brimRadius, innerY, c * brimRadius, yawDeg, pitchDeg, rollDeg);
innerTop[i] = anchor.localRotated(s * domeRadius, outerY, c * domeRadius, yawDeg, pitchDeg, rollDeg);
innerBottom[i] = anchor.localRotated(s * domeRadius, innerY, c * domeRadius, yawDeg, pitchDeg, rollDeg);
}
Vec3 tip = anchor.localRotated(0.0f, outerY + domeHeight, 0.0f, yawDeg, pitchDeg, rollDeg);
for (int i = 0; i < segments; i++) { for (int i = 0; i < segments; i++) {
int next = (i + 1) % segments; int next = (i + 1) % segments;
int domeA = domeShade.color(i, 1.06f, alphaMul); int upperA = coneShade.color(i, 1.06f, alphaMul);
int domeB = domeShade.color(next, 1.02f, alphaMul); int upperB = coneShade.color(next, 1.02f, alphaMul);
triangle(tris, tip, innerTop[i], domeA, innerTop[next], domeB); triangle(tris, apex, rim[i], upperA, rim[next], upperB);
triangle(tris, apex, rim[next], upperA, rim[i], upperB);
int brimTopA = brimShade.color(i, 0.94f, alphaMul); if (lines == null) continue;
int brimTopB = brimShade.color(next, 0.90f, alphaMul); int rimA = rimShade.color(i, 1.0f, alphaMul);
quad(tris, int rimB = rimShade.color(next, 1.0f, alphaMul);
innerTop[i], brimTopA, lineGradient(lines, rim[i], rim[next], rimA, rimB);
innerTop[next], brimTopB,
outerTop[next], brimTopB,
outerTop[i], brimTopA);
int brimBotA = brimShade.color(i, 0.42f, alphaMul);
int brimBotB = brimShade.color(next, 0.40f, alphaMul);
quad(tris,
outerBottom[i], brimBotA,
outerBottom[next], brimBotB,
innerBottom[next], brimBotA,
innerBottom[i], brimBotB);
int edgeA = brimShade.color(i, 0.72f, alphaMul);
int edgeB = brimShade.color(next, 0.70f, alphaMul);
quad(tris,
outerBottom[i], edgeA,
outerBottom[next], edgeB,
outerTop[next], edgeB,
outerTop[i], edgeA);
int rim1 = rimShade.color(i, 1.0f, alphaMul);
int rim2 = rimShade.color(next, 1.0f, alphaMul);
lineGradient(lines, outerTop[i], outerTop[next], rim1, rim2);
} }
} }
@ -105,19 +92,6 @@ final class ChinaHatModel {
mesh.triangle(i1, i2, i3); mesh.triangle(i1, i2, i3);
} }
private static void quad(MeshBuilder mesh,
Vec3 p1, int c1,
Vec3 p2, int c2,
Vec3 p3, int c3,
Vec3 p4, int c4) {
mesh.ensureQuadCapacity();
int i1 = putVertex(mesh, p1, c1);
int i2 = putVertex(mesh, p2, c2);
int i3 = putVertex(mesh, p3, c3);
int i4 = putVertex(mesh, p4, c4);
mesh.quad(i1, i2, i3, i4);
}
private static void lineGradient(MeshBuilder mesh, Vec3 a, Vec3 b, int cA, int cB) { private static void lineGradient(MeshBuilder mesh, Vec3 a, Vec3 b, int cA, int cB) {
mesh.ensureLineCapacity(); mesh.ensureLineCapacity();
int i1 = putVertex(mesh, a, cA); int i1 = putVertex(mesh, a, cA);

View file

@ -27,8 +27,6 @@ import java.util.List;
) )
public final class CosmeticDisplay extends Module { public final class CosmeticDisplay extends Module {
private final Minecraft mc = Minecraft.getInstance();
{ {
action("Next Cosmetic", "NONE"); action("Next Cosmetic", "NONE");
action("Prev Cosmetic", "NONE"); action("Prev Cosmetic", "NONE");
@ -68,6 +66,7 @@ public final class CosmeticDisplay extends Module {
@Override @Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null || mc.player == null) return; if (!isEnabled() || mc.level == null || mc.player == null) return;
CosmeticRenderer.renderOnPlayer(renderer, mc.player, CosmeticType.MODEL, tickDelta); CosmeticRenderer.renderOnPlayer(renderer, mc.player, CosmeticType.MODEL, tickDelta);

View file

@ -68,7 +68,6 @@ public class Crosshair extends Module {
private static final float ORBIZ_PROGRESS_SMOOTH_SPEED = 22.0f; private static final float ORBIZ_PROGRESS_SMOOTH_SPEED = 22.0f;
private static final float COOLDOWN_LEAD = 0.06f; private static final float COOLDOWN_LEAD = 0.06f;
private static final float RIPTIDE_CROSS_LENGTH_FACTOR = 0.65f; private static final float RIPTIDE_CROSS_LENGTH_FACTOR = 0.65f;
private final Minecraft mc = Minecraft.getInstance();
private final EnumValue<CrosshairType> crosshairType = private final EnumValue<CrosshairType> crosshairType =
enumSetting("crosshairType", "type", CrosshairType.CLASSIC); enumSetting("crosshairType", "type", CrosshairType.CLASSIC);
private final EnumValue<OrbizMotionMode> orbizMotion = private final EnumValue<OrbizMotionMode> orbizMotion =
@ -241,6 +240,7 @@ public class Crosshair extends Module {
} }
public boolean shouldHideVanilla() { public boolean shouldHideVanilla() {
Minecraft mc = Minecraft.getInstance();
return isEnabled() return isEnabled()
&& mc.player != null && mc.player != null
&& mc.level != null && mc.level != null
@ -263,6 +263,7 @@ public class Crosshair extends Module {
@Override @Override
public void onRenderHudEngine(Renderer2D renderer, TextRenderer textRenderer, GuiGraphicsExtractor ctx, float tickDelta) { public void onRenderHudEngine(Renderer2D renderer, TextRenderer textRenderer, GuiGraphicsExtractor ctx, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.player == null || mc.level == null) return; if (!isEnabled() || mc.player == null || mc.level == null) return;
if (!mc.options.getCameraType().isFirstPerson()) return; if (!mc.options.getCameraType().isFirstPerson()) return;
@ -336,6 +337,7 @@ public class Crosshair extends Module {
} }
private float getAttackCooldownProgress() { private float getAttackCooldownProgress() {
Minecraft mc = Minecraft.getInstance();
if (mc.player instanceof IPlayerAttackCooldown cooldown) { if (mc.player instanceof IPlayerAttackCooldown cooldown) {
return cooldown.lovisual$getAttackCooldownProgress(0.0f); return cooldown.lovisual$getAttackCooldownProgress(0.0f);
} }
@ -366,6 +368,7 @@ public class Crosshair extends Module {
} }
private void renderOrbiz(Renderer2D renderer, float centerX, float centerY, float scale) { private void renderOrbiz(Renderer2D renderer, float centerX, float centerY, float scale) {
Minecraft mc = Minecraft.getInstance();
boolean danger = shouldShowRiptideBlocked(mc.player) || shouldShowCooldownCross(mc.player); boolean danger = shouldShowRiptideBlocked(mc.player) || shouldShowCooldownCross(mc.player);
float progress = danger ? 1.0f : resolveOrbizProgress(); float progress = danger ? 1.0f : resolveOrbizProgress();
float targetProgress = Mth.clamp(progress * orbizCooldownMultiplier.get().floatValue(), 0.0f, 1.0f); float targetProgress = Mth.clamp(progress * orbizCooldownMultiplier.get().floatValue(), 0.0f, 1.0f);
@ -414,6 +417,7 @@ public class Crosshair extends Module {
} }
private float resolveOrbizProgress() { private float resolveOrbizProgress() {
Minecraft mc = Minecraft.getInstance();
float charge = getChargeProgress(mc.player); float charge = getChargeProgress(mc.player);
if (charge >= 0.0f) { if (charge >= 0.0f) {
return Mth.clamp(charge, 0.0f, 1.0f); return Mth.clamp(charge, 0.0f, 1.0f);
@ -422,6 +426,7 @@ public class Crosshair extends Module {
} }
private void updateOrbizMotion() { private void updateOrbizMotion() {
Minecraft mc = Minecraft.getInstance();
if (mc.player == null) { if (mc.player == null) {
orbizOffsetX = AnimationUtility.fast(orbizOffsetX, 0.0f, 10.0f); orbizOffsetX = AnimationUtility.fast(orbizOffsetX, 0.0f, 10.0f);
orbizOffsetY = AnimationUtility.fast(orbizOffsetY, 0.0f, 10.0f); orbizOffsetY = AnimationUtility.fast(orbizOffsetY, 0.0f, 10.0f);
@ -490,6 +495,7 @@ public class Crosshair extends Module {
} }
private int resolveCrosshairTargetColor() { private int resolveCrosshairTargetColor() {
Minecraft mc = Minecraft.getInstance();
if (!highlightTarget.get()) return DEFAULT_COLOR; if (!highlightTarget.get()) return DEFAULT_COLOR;
HitResult hit = mc.hitResult; HitResult hit = mc.hitResult;
if (!(hit instanceof EntityHitResult ehr)) return DEFAULT_COLOR; if (!(hit instanceof EntityHitResult ehr)) return DEFAULT_COLOR;

View file

@ -58,7 +58,6 @@ public class DropESP extends Module {
private static final double MATTE_ICON_SIZE = 14.0; private static final double MATTE_ICON_SIZE = 14.0;
private static final double MATTE_ICON_GAP = 4.0; private static final double MATTE_ICON_GAP = 4.0;
private static final float MATTE_ICON_SCALE = (float) (MATTE_ICON_SIZE / 16.0); private static final float MATTE_ICON_SCALE = (float) (MATTE_ICON_SIZE / 16.0);
private final Minecraft mc = Minecraft.getInstance();
private final ModeValue modeValue = modeSetting("dropEspMode", SETTING_MODE, "Matte", "Vanilla", "New", "Matte"); private final ModeValue modeValue = modeSetting("dropEspMode", SETTING_MODE, "Matte", "Vanilla", "New", "Matte");
private final BooleanValue limitCommonDistanceValue = bool("dropEspLimitCommonDistance", SETTING_LIMIT_COMMON_DISTANCE, true); private final BooleanValue limitCommonDistanceValue = bool("dropEspLimitCommonDistance", SETTING_LIMIT_COMMON_DISTANCE, true);
private final NumberValue<Integer> commonMaxDistanceValue = private final NumberValue<Integer> commonMaxDistanceValue =
@ -98,6 +97,7 @@ public class DropESP extends Module {
@Override @Override
public void onRenderHudEngine(Renderer2D renderer, TextRenderer textRenderer, GuiGraphicsExtractor ctx, float tickDelta) { public void onRenderHudEngine(Renderer2D renderer, TextRenderer textRenderer, GuiGraphicsExtractor ctx, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null || mc.player == null) return; if (!isEnabled() || mc.level == null || mc.player == null) return;
boolean matteMode = isMatteMode(); boolean matteMode = isMatteMode();

View file

@ -62,7 +62,6 @@ public class ESP extends Module {
static final double SHADER_MAX_DISTANCE = 128.0; static final double SHADER_MAX_DISTANCE = 128.0;
static final float BOX_3D_GRADIENT_SPEED = 0.00115f; static final float BOX_3D_GRADIENT_SPEED = 0.00115f;
final Minecraft mc = Minecraft.getInstance();
final BooleanValue renderEntities = final BooleanValue renderEntities =
bool("espRenderEntities", "render_entities", false); bool("espRenderEntities", "render_entities", false);
final RGBColorValue entityColor = final RGBColorValue entityColor =
@ -185,6 +184,7 @@ public class ESP extends Module {
@Override @Override
public void onRenderHudEngine(Renderer2D renderer, TextRenderer textRenderer, float tickDelta) { public void onRenderHudEngine(Renderer2D renderer, TextRenderer textRenderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null || mc.player == null) return; if (!isEnabled() || mc.level == null || mc.player == null) return;
Vec3 camPos = mc.gameRenderer.mainCamera().position(); Vec3 camPos = mc.gameRenderer.mainCamera().position();
@ -249,6 +249,7 @@ public class ESP extends Module {
private final class ShaderEspPass implements PostProcessPass { private final class ShaderEspPass implements PostProcessPass {
@Override @Override
public boolean isActive() { public boolean isActive() {
Minecraft mc = Minecraft.getInstance();
return ESP.this.isEnabled() return ESP.this.isEnabled()
&& ESP.this.isShaderBox() && ESP.this.isShaderBox()
&& mc.player != null && mc.player != null

View file

@ -11,6 +11,7 @@ import dev.loki.lovisual.render.engine.RenderState;
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines; import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
import dev.loki.lovisual.render.engine.renderer.Renderer3D; import dev.loki.lovisual.render.engine.renderer.Renderer3D;
import dev.loki.lovisual.render.engine.uniform.MeshBuilder; import dev.loki.lovisual.render.engine.uniform.MeshBuilder;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
@ -24,9 +25,10 @@ final class Esp3DRenderer {
} }
static void render(ESP esp, Renderer3D renderer, float tickDelta) { static void render(ESP esp, Renderer3D renderer, float tickDelta) {
if (!esp.isEnabled() || !esp.is3DBox() || esp.mc.level == null || esp.mc.player == null) return; Minecraft mc = Minecraft.getInstance();
if (!esp.isEnabled() || !esp.is3DBox() || mc.level == null || mc.player == null) return;
Vec3 camPos = esp.mc.gameRenderer.mainCamera().position(); Vec3 camPos = mc.gameRenderer.mainCamera().position();
List<EspModel.RenderEntry> entries = EspEntityCollector.collectPlayerEntries(esp, camPos); List<EspModel.RenderEntry> entries = EspEntityCollector.collectPlayerEntries(esp, camPos);
if (entries.isEmpty()) return; if (entries.isEmpty()) return;

View file

@ -8,6 +8,7 @@
package dev.loki.lovisual.features.module.modules.visuals; package dev.loki.lovisual.features.module.modules.visuals;
import dev.loki.lovisual.features.relations.CategoryService; import dev.loki.lovisual.features.relations.CategoryService;
import net.minecraft.client.Minecraft;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
@ -45,9 +46,10 @@ final class EspEntityCollector {
} }
static List<EspModel.RenderEntry> collectPlayerEntries(ESP esp, Vec3 camPos) { static List<EspModel.RenderEntry> collectPlayerEntries(ESP esp, Vec3 camPos) {
Minecraft mc = Minecraft.getInstance();
List<EspModel.RenderEntry> entries = new ArrayList<>(); List<EspModel.RenderEntry> entries = new ArrayList<>();
for (Player player : esp.mc.level.players()) { for (Player player : mc.level.players()) {
if (!shouldRenderPlayer(esp, player)) continue; if (!shouldRenderPlayer(esp, player)) continue;
int color = resolvePlayerColor(player); int color = resolvePlayerColor(player);
double distSq = player.distanceToSqr(camPos); double distSq = player.distanceToSqr(camPos);
@ -58,11 +60,12 @@ final class EspEntityCollector {
} }
static List<EspModel.RenderEntry> collectSearchEntityEntries(ESP esp, Vec3 camPos) { static List<EspModel.RenderEntry> collectSearchEntityEntries(ESP esp, Vec3 camPos) {
Minecraft mc = Minecraft.getInstance();
List<EspModel.RenderEntry> entries = new ArrayList<>(); List<EspModel.RenderEntry> entries = new ArrayList<>();
if (!esp.renderEntities.get()) return entries; if (!esp.renderEntities.get()) return entries;
int color = esp.entityColor.getArgb(); int color = esp.entityColor.getArgb();
for (Entity entity : esp.mc.level.entitiesForRendering()) { for (Entity entity : mc.level.entitiesForRendering()) {
if (!shouldRenderEntity(esp, entity)) continue; if (!shouldRenderEntity(esp, entity)) continue;
double distSq = entity.distanceToSqr(camPos); double distSq = entity.distanceToSqr(camPos);
entries.add(new EspModel.RenderEntry(entity, color, distSq)); entries.add(new EspModel.RenderEntry(entity, color, distSq));
@ -91,6 +94,7 @@ final class EspEntityCollector {
} }
static List<EspModel.RenderEntry> collectShaderChamsEntityEntries(ESP esp, Vec3 camPos, double maxDistSq) { static List<EspModel.RenderEntry> collectShaderChamsEntityEntries(ESP esp, Vec3 camPos, double maxDistSq) {
Minecraft mc = Minecraft.getInstance();
List<EspModel.RenderEntry> entries = new ArrayList<>(); List<EspModel.RenderEntry> entries = new ArrayList<>();
Set<String> selected = esp.shaderChamsEntities.get(); Set<String> selected = esp.shaderChamsEntities.get();
if (selected == null || selected.isEmpty()) { if (selected == null || selected.isEmpty()) {
@ -98,7 +102,7 @@ final class EspEntityCollector {
} }
int color = esp.shaderChamsEntityColor.getArgb(); int color = esp.shaderChamsEntityColor.getArgb();
for (Entity entity : esp.mc.level.entitiesForRendering()) { for (Entity entity : mc.level.entitiesForRendering()) {
if (!shouldRenderShaderChamsEntity(esp, entity, selected)) { if (!shouldRenderShaderChamsEntity(esp, entity, selected)) {
continue; continue;
} }
@ -112,9 +116,10 @@ final class EspEntityCollector {
} }
static boolean shouldRenderPlayer(ESP esp, Player p) { static boolean shouldRenderPlayer(ESP esp, Player p) {
Minecraft mc = Minecraft.getInstance();
if (p == null) return false; if (p == null) return false;
if (p == esp.mc.player) { if (p == mc.player) {
if (esp.mc.options.getCameraType().isFirstPerson()) return false; if (mc.options.getCameraType().isFirstPerson()) return false;
return esp.renderSelf.get(); return esp.renderSelf.get();
} }
if (esp.skipInvisible.get() && (p.isInvisible() || p.hasEffect(net.minecraft.world.effect.MobEffects.INVISIBILITY))) { if (esp.skipInvisible.get() && (p.isInvisible() || p.hasEffect(net.minecraft.world.effect.MobEffects.INVISIBILITY))) {
@ -137,32 +142,35 @@ final class EspEntityCollector {
} }
static boolean hasLineOfSight(ESP esp, Player p) { static boolean hasLineOfSight(ESP esp, Player p) {
if (esp.mc.level == null) return false; Minecraft mc = Minecraft.getInstance();
Vec3 start = esp.mc.gameRenderer.mainCamera().position(); if (mc.level == null) return false;
Vec3 start = mc.gameRenderer.mainCamera().position();
Vec3 end = p.getBoundingBox().getCenter(); Vec3 end = p.getBoundingBox().getCenter();
HitResult result = esp.mc.level.clip(new ClipContext( HitResult result = mc.level.clip(new ClipContext(
start, start,
end, end,
ClipContext.Block.COLLIDER, ClipContext.Block.COLLIDER,
ClipContext.Fluid.NONE, ClipContext.Fluid.NONE,
esp.mc.player mc.player
)); ));
return result.getType() == HitResult.Type.MISS; return result.getType() == HitResult.Type.MISS;
} }
static boolean shouldRenderEntity(ESP esp, Entity e) { static boolean shouldRenderEntity(ESP esp, Entity e) {
Minecraft mc = Minecraft.getInstance();
if (e == null) return false; if (e == null) return false;
if (e instanceof Player) return false; if (e instanceof Player) return false;
if (e == esp.mc.player) return false; if (e == mc.player) return false;
Identifier id = BuiltInRegistries.ENTITY_TYPE.getKey(e.getType()); Identifier id = BuiltInRegistries.ENTITY_TYPE.getKey(e.getType());
if (id == null) return false; if (id == null) return false;
return esp.searchEntities.get().contains(id.toString()); return esp.searchEntities.get().contains(id.toString());
} }
static boolean shouldRenderShaderChamsEntity(ESP esp, Entity entity, Set<String> selected) { static boolean shouldRenderShaderChamsEntity(ESP esp, Entity entity, Set<String> selected) {
Minecraft mc = Minecraft.getInstance();
if (entity == null) return false; if (entity == null) return false;
if (entity instanceof Player) return false; if (entity instanceof Player) return false;
if (entity == esp.mc.player) return false; if (entity == mc.player) return false;
Identifier id = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()); Identifier id = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType());
if (id == null) return false; if (id == null) return false;

View file

@ -24,6 +24,7 @@ import dev.loki.lovisual.render.engine.core.LoVisualRenderSystem;
import dev.loki.lovisual.render.engine.msaa.MsaaFramebuffer; import dev.loki.lovisual.render.engine.msaa.MsaaFramebuffer;
import dev.loki.lovisual.render.engine.msaa.MsaaWorldTarget; import dev.loki.lovisual.render.engine.msaa.MsaaWorldTarget;
import dev.loki.lovisual.render.engine.renderer.MeshRenderer; import dev.loki.lovisual.render.engine.renderer.MeshRenderer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.feature.FeatureRenderDispatcher; import net.minecraft.client.renderer.feature.FeatureRenderDispatcher;
import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.ProjectionMatrixBuffer; import net.minecraft.client.renderer.ProjectionMatrixBuffer;
@ -46,11 +47,12 @@ final class EspShaderMask {
} }
static boolean renderEntityMask(ESP esp, List<EspModel.RenderEntry> entries, float tickDelta) { static boolean renderEntityMask(ESP esp, List<EspModel.RenderEntry> entries, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (entries.isEmpty() || esp.shaderMask == null) { if (entries.isEmpty() || esp.shaderMask == null) {
return false; return false;
} }
LevelRenderer worldRenderer = esp.mc.levelRenderer; LevelRenderer worldRenderer = mc.levelRenderer;
if (worldRenderer == null) { if (worldRenderer == null) {
return false; return false;
} }
@ -103,8 +105,8 @@ final class EspShaderMask {
Matrix4f capturedPosition = LoVisualWorldMatrices.positionMatrix(); Matrix4f capturedPosition = LoVisualWorldMatrices.positionMatrix();
if (capturedPosition != null) { if (capturedPosition != null) {
modelView.mul(capturedPosition); modelView.mul(capturedPosition);
} else if (esp.mc.gameRenderer != null && esp.mc.gameRenderer.mainCamera() != null) { } else if (mc.gameRenderer != null && mc.gameRenderer.mainCamera() != null) {
modelView.mul(new Matrix4f().rotation(esp.mc.gameRenderer.mainCamera().rotation().conjugate(new Quaternionf()))); modelView.mul(new Matrix4f().rotation(mc.gameRenderer.mainCamera().rotation().conjugate(new Quaternionf())));
} }
PoseStack matrices = new PoseStack(); PoseStack matrices = new PoseStack();
@ -122,7 +124,7 @@ final class EspShaderMask {
esp.shaderEspCommandQueue.getSubmitsPerOrder().clear(); esp.shaderEspCommandQueue.getSubmitsPerOrder().clear();
EntityRenderDispatcher entityRenderManager = esp.mc.getEntityRenderDispatcher(); EntityRenderDispatcher entityRenderManager = mc.getEntityRenderDispatcher();
for (EspModel.RenderEntry entry : entries) { for (EspModel.RenderEntry entry : entries) {
Entity entity = entry.entity(); Entity entity = entry.entity();
EntityCullingCompat.forceVisibleForShaderEsp(entity); EntityCullingCompat.forceVisibleForShaderEsp(entity);

View file

@ -14,6 +14,7 @@ import dev.loki.lovisual.render.engine.msaa.MsaaFramebuffer;
import dev.loki.lovisual.render.engine.msaa.MsaaWorldTarget; import dev.loki.lovisual.render.engine.msaa.MsaaWorldTarget;
import dev.loki.lovisual.render.engine.postprocess.PostProcessManager; import dev.loki.lovisual.render.engine.postprocess.PostProcessManager;
import dev.loki.lovisual.render.iris.IrisLoVisualFrameHooks; import dev.loki.lovisual.render.iris.IrisLoVisualFrameHooks;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderBuffers; import net.minecraft.client.renderer.RenderBuffers;
import net.minecraft.client.renderer.feature.FeatureRenderDispatcher; import net.minecraft.client.renderer.feature.FeatureRenderDispatcher;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
@ -26,14 +27,15 @@ final class EspShaderRenderer {
} }
static boolean render(ESP esp, GpuTextureView src, GpuTextureView dst, float tickDelta) { static boolean render(ESP esp, GpuTextureView src, GpuTextureView dst, float tickDelta) {
if (!esp.isEnabled() || !esp.isShaderBox() || esp.mc.level == null || esp.mc.player == null) { Minecraft mc = Minecraft.getInstance();
if (!esp.isEnabled() || !esp.isShaderBox() || mc.level == null || mc.player == null) {
return false; return false;
} }
if (!esp.shaderGlow.get() && !esp.shaderOutline.get() && !esp.shaderFill.get()) { if (!esp.shaderGlow.get() && !esp.shaderOutline.get() && !esp.shaderFill.get()) {
return false; return false;
} }
Vec3 camPos = esp.mc.gameRenderer.mainCamera().position(); Vec3 camPos = mc.gameRenderer.mainCamera().position();
List<EspModel.RenderEntry> entries = EspEntityCollector.collectShaderEntries(esp, camPos); List<EspModel.RenderEntry> entries = EspEntityCollector.collectShaderEntries(esp, camPos);
if (entries.isEmpty()) { if (entries.isEmpty()) {
return false; return false;
@ -50,14 +52,15 @@ final class EspShaderRenderer {
PostProcessManager.copy(src, dst); PostProcessManager.copy(src, dst);
int width = esp.mc.getWindow().getWidth(); int width = mc.getWindow().getWidth();
int height = esp.mc.getWindow().getHeight(); int height = mc.getWindow().getHeight();
return EspShaderColor.renderColorPass(esp, dst, width, height); return EspShaderColor.renderColorPass(esp, dst, width, height);
} }
static void ensureShaderBuffers(ESP esp) { static void ensureShaderBuffers(ESP esp) {
int width = esp.mc.getWindow().getWidth(); Minecraft mc = Minecraft.getInstance();
int height = esp.mc.getWindow().getHeight(); int width = mc.getWindow().getWidth();
int height = mc.getWindow().getHeight();
if (width <= 0 || height <= 0) { if (width <= 0 || height <= 0) {
return; return;
} }
@ -96,10 +99,11 @@ final class EspShaderRenderer {
} }
static FeatureRenderDispatcher getShaderEspRenderDispatcher(ESP esp) { static FeatureRenderDispatcher getShaderEspRenderDispatcher(ESP esp) {
Minecraft mc = Minecraft.getInstance();
if (IrisLoVisualFrameHooks.isRenderingAfterIrisFinalization()) { if (IrisLoVisualFrameHooks.isRenderingAfterIrisFinalization()) {
return getStandaloneShaderEspRenderDispatcher(esp); return getStandaloneShaderEspRenderDispatcher(esp);
} }
if (esp.mc != null && esp.mc.levelRenderer instanceof dev.loki.lovisual.mixins.accessors.LevelRendererAccessor accessor) { if (mc != null && mc.levelRenderer instanceof dev.loki.lovisual.mixins.accessors.LevelRendererAccessor accessor) {
FeatureRenderDispatcher dispatcher = accessor.lovisual$getEntityRenderDispatcher(); FeatureRenderDispatcher dispatcher = accessor.lovisual$getEntityRenderDispatcher();
if (dispatcher != null) { if (dispatcher != null) {
return dispatcher; return dispatcher;
@ -109,19 +113,20 @@ final class EspShaderRenderer {
} }
static FeatureRenderDispatcher getStandaloneShaderEspRenderDispatcher(ESP esp) { static FeatureRenderDispatcher getStandaloneShaderEspRenderDispatcher(ESP esp) {
Minecraft mc = Minecraft.getInstance();
if (esp.shaderEspRenderDispatcher != null) { if (esp.shaderEspRenderDispatcher != null) {
return esp.shaderEspRenderDispatcher; return esp.shaderEspRenderDispatcher;
} }
if (esp.mc.gameRenderer == null || esp.mc.getModelManager() == null || esp.mc.getAtlasManager() == null || esp.mc.font == null) { if (mc.gameRenderer == null || mc.getModelManager() == null || mc.getAtlasManager() == null || mc.font == null) {
return null; return null;
} }
esp.shaderEspRenderBuffers = new RenderBuffers(1); esp.shaderEspRenderBuffers = new RenderBuffers(1);
esp.shaderEspRenderDispatcher = new FeatureRenderDispatcher( esp.shaderEspRenderDispatcher = new FeatureRenderDispatcher(
esp.shaderEspRenderBuffers, esp.shaderEspRenderBuffers,
esp.mc.getModelManager(), mc.getModelManager(),
esp.mc.getAtlasManager(), mc.getAtlasManager(),
esp.mc.font, mc.font,
esp.mc.gameRenderer.gameRenderState() mc.gameRenderer.gameRenderState()
); );
return esp.shaderEspRenderDispatcher; return esp.shaderEspRenderDispatcher;
} }

View file

@ -87,7 +87,6 @@ public class HitEffect extends Module {
private static final float HIT_PARTICLE_DRAG = 0.94f; private static final float HIT_PARTICLE_DRAG = 0.94f;
private static final int HIT_PARTICLE_MAX = 320; private static final int HIT_PARTICLE_MAX = 320;
private static final int HIT_PARTICLE_SPAWN_CAP_PER_TICK = 32; private static final int HIT_PARTICLE_SPAWN_CAP_PER_TICK = 32;
private final Minecraft mc = Minecraft.getInstance();
private final Random random = new Random(); private final Random random = new Random();
private final EnumValue<EffectMode> effectMode = private final EnumValue<EffectMode> effectMode =
enumSetting("hitBubblesEffectMode", SETTING_EFFECT_MODE, EffectMode.BUBBLES, EffectMode.values()); enumSetting("hitBubblesEffectMode", SETTING_EFFECT_MODE, EffectMode.BUBBLES, EffectMode.values());
@ -277,6 +276,7 @@ public class HitEffect extends Module {
} }
public void handleHit(Entity target) { public void handleHit(Entity target) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.player == null || mc.level == null) return; if (!isEnabled() || mc.player == null || mc.level == null) return;
if (!(target instanceof LivingEntity)) return; if (!(target instanceof LivingEntity)) return;
if (onlyPlayers.get() && !(target instanceof Player)) return; if (onlyPlayers.get() && !(target instanceof Player)) return;
@ -312,6 +312,7 @@ public class HitEffect extends Module {
@Override @Override
public void onTick() { public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.player == null || mc.level == null) return; if (!isEnabled() || mc.player == null || mc.level == null) return;
if (effectMode.get() != EffectMode.BUBBLES || !starsEnabled.get()) { if (effectMode.get() != EffectMode.BUBBLES || !starsEnabled.get()) {
@ -327,6 +328,7 @@ public class HitEffect extends Module {
@Override @Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.player == null || mc.level == null) return; if (!isEnabled() || mc.player == null || mc.level == null) return;
if (hitParticlesEnabled.get() && !hitParticles.isEmpty()) { if (hitParticlesEnabled.get() && !hitParticles.isEmpty()) {
@ -405,6 +407,7 @@ public class HitEffect extends Module {
} }
private void addWave(BlockPos pos) { private void addWave(BlockPos pos) {
Minecraft mc = Minecraft.getInstance();
if (mc.level != null) { if (mc.level != null) {
waves.add(new WaveEffect(pos, GameClock.millis())); waves.add(new WaveEffect(pos, GameClock.millis()));
} }
@ -457,6 +460,7 @@ public class HitEffect extends Module {
} }
private int reserveHitParticleBudget(int requested) { private int reserveHitParticleBudget(int requested) {
Minecraft mc = Minecraft.getInstance();
if (requested <= 0 || mc.player == null) { if (requested <= 0 || mc.player == null) {
return 0; return 0;
} }
@ -599,6 +603,7 @@ public class HitEffect extends Module {
} }
private Vec3 getRtxPoint(Entity target, float yaw, float pitch, double distance) { private Vec3 getRtxPoint(Entity target, float yaw, float pitch, double distance) {
Minecraft mc = Minecraft.getInstance();
if (mc.player == null) return null; if (mc.player == null) return null;
Vec3 start = mc.player.position().add(0, mc.player.getEyeHeight(mc.player.getPose()), 0); Vec3 start = mc.player.position().add(0, mc.player.getEyeHeight(mc.player.getPose()), 0);
@ -690,6 +695,7 @@ public class HitEffect extends Module {
} }
private void render(Renderer3D renderer) { private void render(Renderer3D renderer) {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null) return; if (mc.level == null) return;
if (renderer == null) return; if (renderer == null) return;
@ -751,6 +757,7 @@ public class HitEffect extends Module {
} }
private BlockPos findSurface(BlockPos pos) { private BlockPos findSurface(BlockPos pos) {
Minecraft mc = Minecraft.getInstance();
for (int y = 2; y >= -4; y--) { for (int y = 2; y >= -4; y--) {
BlockPos p = pos.above(y); BlockPos p = pos.above(y);
if (!mc.level.getBlockState(p).isAir() && mc.level.getBlockState(p.above()).isAir()) { if (!mc.level.getBlockState(p).isAir() && mc.level.getBlockState(p.above()).isAir()) {

View file

@ -0,0 +1,161 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.visuals;
import dev.loki.lovisual.config.values.BooleanValue;
import dev.loki.lovisual.config.values.ModeValue;
import dev.loki.lovisual.config.values.NumberValue;
import dev.loki.lovisual.config.values.RGBColorValue;
import dev.loki.lovisual.features.module.Module;
import dev.loki.lovisual.features.module.ModuleCategory;
import dev.loki.lovisual.features.module.ModuleInfo;
import dev.loki.lovisual.features.module.WorldPhase;
import dev.loki.lovisual.features.relations.CategoryService;
import dev.loki.lovisual.features.relations.CategoryType;
import dev.loki.lovisual.render.engine.RenderState;
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
import dev.loki.lovisual.render.engine.renderer.Renderer3D;
import dev.loki.lovisual.render.engine.uniform.MeshBuilder;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import java.util.ArrayList;
import java.util.List;
/**
* Colored wireframe/fill rendering of entity hitboxes.
* Render-only: it never changes collision boxes (that would be a cheat).
*/
@ModuleInfo(
id = "hitboxes",
displayName = "Hitboxes",
category = ModuleCategory.VISUALS
)
public class Hitboxes extends Module {
private static final String MODE_OUTLINE = "Outline";
private static final String MODE_FILL = "Fill";
private static final String MODE_BOTH = "Both";
private static final String COLOR_SYNC = "Sync";
private static final String COLOR_CUSTOM = "Custom";
private final ModeValue renderMode =
mode("hitboxesRenderMode", "render_mode", MODE_BOTH, MODE_OUTLINE, MODE_FILL, MODE_BOTH);
private final ModeValue colorMode =
mode("hitboxesColorMode", "color_mode", COLOR_SYNC, COLOR_SYNC, COLOR_CUSTOM);
private final BooleanValue eyesLine = bool("hitboxesEyesLine", "eyes_line", false);
private final BooleanValue playersOnly = bool("hitboxesPlayersOnly", "players_only", true);
private final NumberValue<Integer> lineWidth = num("hitboxesLineWidth", "line_width", 2, 1, 10);
private final NumberValue<Integer> fillAlpha = num("hitboxesFillAlpha", "fill_alpha", 40, 0, 255);
private final RGBColorValue hitboxColor =
visibleWhen(colorNoAlpha("hitboxesColor", "#FFFFFF"), () -> COLOR_CUSTOM.equals(colorMode.get()));
private final RGBColorValue friendHitboxColor =
visibleWhen(colorNoAlpha("hitboxesFriendColor", "#00FF00"), () -> COLOR_CUSTOM.equals(colorMode.get()));
@Override
public WorldPhase getWorldPhase() {
return WorldPhase.END_MAIN;
}
@Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || renderer == null || mc.level == null) return;
List<Entity> targets = collectTargets(mc, tickDelta);
if (targets.isEmpty()) return;
boolean outline = !MODE_FILL.equals(renderMode.get());
boolean fill = !MODE_OUTLINE.equals(renderMode.get());
float previousLineWidth = RenderState.lineWidth;
RenderState.lineWidth = Math.max(0.01f, lineWidth.get());
MeshBuilder lines = null;
MeshBuilder tris = null;
try {
if (outline) {
lines = renderer.batch(LoVisualRenderPipelines.WORLD_COLORED_LINES, Renderer3D.DepthMode.NONE);
}
if (fill) {
tris = renderer.batch(LoVisualRenderPipelines.WORLD_COLORED, Renderer3D.DepthMode.NONE);
}
for (Entity entity : targets) {
AABB box = interpolatedBox(entity, tickDelta);
int rgb = resolveColor(entity) & 0x00FFFFFF;
drawBox(lines, tris, box, rgb);
if (eyesLine.get() && entity instanceof LivingEntity living) {
drawEyesLine(lines, living, box, rgb);
}
}
} finally {
RenderState.lineWidth = previousLineWidth;
}
}
private List<Entity> collectTargets(Minecraft mc, float tickDelta) {
List<Entity> out = new ArrayList<>();
Entity camera = mc.getCameraEntity();
for (Entity entity : mc.level.entitiesForRendering()) {
if (entity == camera) continue;
if (playersOnly.get() && !(entity instanceof Player)) continue;
if (!Renderer3D.Culling.isInFrustum(interpolatedBox(entity, tickDelta))) continue;
out.add(entity);
}
return out;
}
static AABB interpolatedBox(Entity entity, float tickDelta) {
Vec3 pos = entity.getPosition(tickDelta);
return entity.getBoundingBox().move(pos.x - entity.getX(), pos.y - entity.getY(), pos.z - entity.getZ());
}
private int resolveColor(Entity entity) {
if (COLOR_SYNC.equals(colorMode.get()) && entity instanceof Player player) {
return CategoryService.getColor(player) | 0xFF000000;
}
if (entity instanceof Player player
&& CategoryService.get(player) == CategoryType.FRIEND) {
return friendHitboxColor.getArgb() | 0xFF000000;
}
return hitboxColor.getArgb() | 0xFF000000;
}
private void drawBox(MeshBuilder lines, MeshBuilder tris, AABB box, int rgb) {
if (tris != null) {
int fill = withAlpha(rgb, fillAlpha.get());
EspMeshBuilder.addFilledBox(tris, box, fill, fill, fill, fill, fill, fill, fill, fill);
}
if (lines != null) {
int outline = 0xFF000000 | rgb;
EspMeshBuilder.addOutlineBox(lines, box,
outline, outline, outline, outline, outline, outline, outline, outline);
}
}
private static int withAlpha(int rgb, int alpha) {
return ((alpha & 0xFF) << 24) | (rgb & 0x00FFFFFF);
}
private void drawEyesLine(MeshBuilder lines, LivingEntity living, AABB box, int rgb) {
if (lines == null) return;
double eyeY = box.minY + living.getEyeHeight();
double cx = (box.minX + box.maxX) / 2.0;
double cz = (box.minZ + box.maxZ) / 2.0;
double len = 0.3;
float yawDeg = living.getYRot();
double rad = Math.toRadians(yawDeg);
double dx = -Math.sin(rad) * len;
double dz = Math.cos(rad) * len;
int argb = 0xFF000000 | rgb;
EspMeshBuilder.line(lines, cx, eyeY, cz, argb, cx + dx, eyeY + 0.05, cz + dz, argb);
}
}

View file

@ -52,8 +52,6 @@ public final class HoleESP extends Module {
private static final int RESCAN_TICKS = 10; private static final int RESCAN_TICKS = 10;
private static final int BASE_FILL_ALPHA = 60; private static final int BASE_FILL_ALPHA = 60;
private final Minecraft mc = Minecraft.getInstance();
private final NumberValue<Integer> rangeXZ = private final NumberValue<Integer> rangeXZ =
num("holeesp_range_xz", "range_xz", 10, 1, 128); num("holeesp_range_xz", "range_xz", 10, 1, 128);
private final NumberValue<Integer> rangeY = private final NumberValue<Integer> rangeY =
@ -80,6 +78,7 @@ public final class HoleESP extends Module {
@Override @Override
public void onTick() { public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null || mc.player == null) { if (!isEnabled() || mc.level == null || mc.player == null) {
holes = List.of(); holes = List.of();
return; return;
@ -97,6 +96,7 @@ public final class HoleESP extends Module {
@Override @Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || renderer == null || mc.level == null || mc.player == null || holes.isEmpty()) { if (!isEnabled() || renderer == null || mc.level == null || mc.player == null || holes.isEmpty()) {
return; return;
} }
@ -192,6 +192,7 @@ public final class HoleESP extends Module {
} }
private int colorForDistance(AABB box, int argb, int alpha) { private int colorForDistance(AABB box, int argb, int alpha) {
Minecraft mc = Minecraft.getInstance();
Vec3 center = box.getCenter(); Vec3 center = box.getCenter();
double dx = center.x - mc.player.getX(); double dx = center.x - mc.player.getX();
double dz = center.z - mc.player.getZ(); double dz = center.z - mc.player.getZ();

View file

@ -40,7 +40,6 @@ import java.util.List;
category = ModuleCategory.VISUALS category = ModuleCategory.VISUALS
) )
public class JumpCircles extends Module { public class JumpCircles extends Module {
private final Minecraft mc = Minecraft.getInstance();
private final EnumValue<CircleMode> mode = private final EnumValue<CircleMode> mode =
enumSetting("jumpCirclesMode", "mode", CircleMode.DEFAULT); enumSetting("jumpCirclesMode", "mode", CircleMode.DEFAULT);
private final BooleanValue easeOut = bool("jumpCirclesEaseOut", "ease_out", true); private final BooleanValue easeOut = bool("jumpCirclesEaseOut", "ease_out", true);
@ -85,6 +84,7 @@ public class JumpCircles extends Module {
@Override @Override
public void onTick() { public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null || mc.player == null) return; if (!isEnabled() || mc.level == null || mc.player == null) return;
boolean selfOnly = onlySelf.get(); boolean selfOnly = onlySelf.get();
@ -118,6 +118,7 @@ public class JumpCircles extends Module {
@Override @Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null || mc.player == null || circles.isEmpty()) return; if (!isEnabled() || mc.level == null || mc.player == null || circles.isEmpty()) return;
boolean useDepth = depthTest.get(); boolean useDepth = depthTest.get();

View file

@ -64,7 +64,6 @@ public class KillEffect extends Module {
private static final Identifier ORTHODOX_SOUND = private static final Identifier ORTHODOX_SOUND =
Identifier.fromNamespaceAndPath("lovisual", "sounds/misc/orthodox.wav"); Identifier.fromNamespaceAndPath("lovisual", "sounds/misc/orthodox.wav");
private static final long EMBER_GROUND_EXTRA_MS = 0; private static final long EMBER_GROUND_EXTRA_MS = 0;
private final Minecraft mc = Minecraft.getInstance();
private final Random random = new Random(); private final Random random = new Random();
private final ModeValue mode = private final ModeValue mode =
modeSetting("killEffectMode", SETTING_MODE, "Orthodox", "Orthodox", "FallingLava", "EmbersArc", "Lightning"); modeSetting("killEffectMode", SETTING_MODE, "Orthodox", "Orthodox", "FallingLava", "EmbersArc", "Lightning");
@ -149,6 +148,7 @@ public class KillEffect extends Module {
@Override @Override
public void onTick() { public void onTick() {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null || mc.player == null) return; if (!isEnabled() || mc.level == null || mc.player == null) return;
long now = GameClock.millis(); long now = GameClock.millis();
@ -213,6 +213,7 @@ public class KillEffect extends Module {
@Override @Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (!isEnabled() || mc.level == null || mc.player == null) return; if (!isEnabled() || mc.level == null || mc.player == null) return;
if ("Orthodox".equals(mode.get())) { if ("Orthodox".equals(mode.get())) {
@ -348,6 +349,7 @@ public class KillEffect extends Module {
} }
private void spawnFallingLava(LivingEntity entity) { private void spawnFallingLava(LivingEntity entity) {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null) return; if (mc.level == null) return;
int height = (int) (entity.getBbHeight() * 10); int height = (int) (entity.getBbHeight() * 10);
int width = (int) (entity.getBbWidth() * 10); int width = (int) (entity.getBbWidth() * 10);

View file

@ -0,0 +1,89 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 loki5512344.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.visuals;
import dev.loki.lovisual.config.values.BooleanValue;
import dev.loki.lovisual.config.values.NumberValue;
import dev.loki.lovisual.config.values.RGBColorValue;
import dev.loki.lovisual.events.EventHandler;
import dev.loki.lovisual.events.impl.LightmapModifyEvent;
import dev.loki.lovisual.features.module.Module;
import dev.loki.lovisual.features.module.ModuleCategory;
import dev.loki.lovisual.features.module.ModuleInfo;
import org.joml.Vector3f;
import org.joml.Vector3fc;
/**
* Tints the world ambient light with a custom color. Pure math lives in
* {@link #tintAmbient} so it is unit-testable without Minecraft.
*/
@ModuleInfo(
id = "light",
displayName = "Light",
category = ModuleCategory.VISUALS
)
public class Light extends Module {
private static final String SETTING_TINT_AMBIENT = "tint_ambient";
private static final String SETTING_TINT_SKY = "tint_sky";
private static final String SETTING_COLOR = "color";
private static final String SETTING_STRENGTH = "strength";
private final BooleanValue tintAmbientSetting = bool("lightTintAmbient", SETTING_TINT_AMBIENT, true);
private final BooleanValue tintSkySetting = bool("lightTintSky", SETTING_TINT_SKY, false);
private final RGBColorValue color = colorNoAlpha("lightColor", SETTING_COLOR, "#C7D9FF");
private final NumberValue<Integer> strength = num("lightStrength", SETTING_STRENGTH, 50, 0, 100);
@EventHandler
private void onLightmapState(LightmapModifyEvent event) {
if (!isEnabled() || event.state() == null) return;
float mix = clamp01(strength.get() / 100.0f);
if (mix <= 0.0f) return;
int argb = color.getArgb();
Vector3f target = toLinear(argb);
var state = event.state();
if (tintAmbientSetting.get()) {
state.ambientColor = tintAmbient(state.ambientColor, target, mix);
}
if (tintSkySetting.get()) {
state.skyLightColor = tintAmbient(state.skyLightColor, target, mix);
}
state.needsUpdate = true;
}
/**
* @param current existing channel (may be null)
* @param target replacement linear RGB
* @param mix 0..1 blend factor; 0 keeps current, 1 is fully target
*/
public static Vector3f tintAmbient(Vector3fc current, Vector3f target, float mix) {
float k = clamp01(mix);
Vector3f base = current == null ? new Vector3f(1.0f, 1.0f, 1.0f)
: new Vector3f(current.x(), current.y(), current.z());
base.x = base.x * (1.0f - k) + target.x * k;
base.y = base.y * (1.0f - k) + target.y * k;
base.z = base.z * (1.0f - k) + target.z * k;
return base;
}
/** Converts 0xRRGGBB (alpha ignored) into a linear-ish 0..1 tint vector. */
public static Vector3f toLinear(int argb) {
return new Vector3f(
((argb >> 16) & 0xFF) / 255.0f,
((argb >> 8) & 0xFF) / 255.0f,
(argb & 0xFF) / 255.0f
);
}
private static float clamp01(float v) {
return v < 0.0f ? 0.0f : Math.min(v, 1.0f);
}
}

View file

@ -34,8 +34,6 @@ import java.util.concurrent.ThreadLocalRandom;
) )
public class LightningTargetESP extends Module { public class LightningTargetESP extends Module {
private final Minecraft mc = Minecraft.getInstance();
private final NumberValue<Float> lightningCount = num("lightningCount", "count", 4.0f, 1.0f, 10.0f); private final NumberValue<Float> lightningCount = num("lightningCount", "count", 4.0f, 1.0f, 10.0f);
private final NumberValue<Float> lightningWidth = num("lightningWidth", "width", 1.0f, 0.1f, 3.0f); private final NumberValue<Float> lightningWidth = num("lightningWidth", "width", 1.0f, 0.1f, 3.0f);
private final BooleanValue hurtColor = bool("hurtColor", "hurt_color", true); private final BooleanValue hurtColor = bool("hurtColor", "hurt_color", true);
@ -58,6 +56,7 @@ public class LightningTargetESP extends Module {
@Override @Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) { public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
Minecraft mc = Minecraft.getInstance();
if (mc.player == null || mc.level == null) return; if (mc.player == null || mc.level == null) return;
LivingEntity currentTarget = TargetManager.getTarget(); LivingEntity currentTarget = TargetManager.getTarget();
@ -87,6 +86,7 @@ public class LightningTargetESP extends Module {
} }
private void renderLightning(Renderer3D renderer, float partialTicks, boolean hasActiveTarget, LivingEntity activeTarget) { private void renderLightning(Renderer3D renderer, float partialTicks, boolean hasActiveTarget, LivingEntity activeTarget) {
Minecraft mc = Minecraft.getInstance();
if (lastTargetPos == null) return; if (lastTargetPos == null) return;
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();

View file

@ -36,7 +36,6 @@ public final class MotionBlur extends Module implements PostProcessPass {
private static final float VERTICAL_DEGREES_PER_SCREEN = 70.0f; private static final float VERTICAL_DEGREES_PER_SCREEN = 70.0f;
private static final int SHADER_TAPS = 11; private static final int SHADER_TAPS = 11;
private final Minecraft mc = Minecraft.getInstance();
private final MotionBlurResources resources = new MotionBlurResources(); private final MotionBlurResources resources = new MotionBlurResources();
private final NumberValue<Float> strength = private final NumberValue<Float> strength =
@ -79,6 +78,7 @@ public final class MotionBlur extends Module implements PostProcessPass {
@Override @Override
public boolean isActive() { public boolean isActive() {
Minecraft mc = Minecraft.getInstance();
return isEnabled() && mc != null && mc.player != null && mc.level != null; return isEnabled() && mc != null && mc.player != null && mc.level != null;
} }
@ -258,6 +258,7 @@ public final class MotionBlur extends Module implements PostProcessPass {
} }
private boolean shouldReset(long nowNs) { private boolean shouldReset(long nowNs) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.level == null || mc.options == null) return true; if (mc == null || mc.level == null || mc.options == null) return true;
if (!previousFrameValid || previousFrameNs == 0L) return true; if (!previousFrameValid || previousFrameNs == 0L) return true;
if (previousWorld != mc.level) return true; if (previousWorld != mc.level) return true;
@ -270,10 +271,12 @@ public final class MotionBlur extends Module implements PostProcessPass {
} }
private CameraType currentPerspective() { private CameraType currentPerspective() {
Minecraft mc = Minecraft.getInstance();
return mc != null && mc.options != null ? mc.options.getCameraType() : CameraType.FIRST_PERSON; return mc != null && mc.options != null ? mc.options.getCameraType() : CameraType.FIRST_PERSON;
} }
private void capturePose(float yaw, float pitch, long nowNs) { private void capturePose(float yaw, float pitch, long nowNs) {
Minecraft mc = Minecraft.getInstance();
previousYaw = yaw; previousYaw = yaw;
previousPitch = pitch; previousPitch = pitch;
previousWorld = mc != null ? mc.level : null; previousWorld = mc != null ? mc.level : null;

View file

@ -11,6 +11,7 @@ import dev.loki.lovisual.features.relations.PlayerRelations;
import dev.loki.lovisual.util.player.PlayerHealthResolver; import dev.loki.lovisual.util.player.PlayerHealthResolver;
import dev.loki.lovisual.util.pvp.PvpTargetState; import dev.loki.lovisual.util.pvp.PvpTargetState;
import dev.loki.lovisual.util.text.LegacyTextUtil; import dev.loki.lovisual.util.text.LegacyTextUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.PlayerInfo; import net.minecraft.client.multiplayer.PlayerInfo;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
@ -45,11 +46,12 @@ final class NameTagLabelBuilder {
} }
static Component resolveNameplateText(NameTags self, Player player) { static Component resolveNameplateText(NameTags self, Player player) {
Minecraft mc = Minecraft.getInstance();
if (player == null) return Component.empty(); if (player == null) return Component.empty();
String fallback = player.getGameProfile().name(); String fallback = player.getGameProfile().name();
if (!shouldUseTabNames(self)) return Component.literal(fallback); if (!shouldUseTabNames(self)) return Component.literal(fallback);
var handler = self.mc.getConnection(); var handler = mc.getConnection();
if (handler == null) return Component.literal(fallback); if (handler == null) return Component.literal(fallback);
PlayerInfo entry = handler.getPlayerInfo(player.getUUID()); PlayerInfo entry = handler.getPlayerInfo(player.getUUID());
if (entry == null) return Component.literal(fallback); if (entry == null) return Component.literal(fallback);
@ -105,8 +107,9 @@ final class NameTagLabelBuilder {
} }
static boolean isCreative(NameTags self, Player player) { static boolean isCreative(NameTags self, Player player) {
Minecraft mc = Minecraft.getInstance();
if (player.isCreative()) return true; if (player.isCreative()) return true;
var handler = self.mc.getConnection(); var handler = mc.getConnection();
if (handler == null) return false; if (handler == null) return false;
PlayerInfo entry = handler.getPlayerInfo(player.getUUID()); PlayerInfo entry = handler.getPlayerInfo(player.getUUID());
if (entry == null) return false; if (entry == null) return false;
@ -123,8 +126,9 @@ final class NameTagLabelBuilder {
} }
static int getPlayerPing(NameTags self, Player player) { static int getPlayerPing(NameTags self, Player player) {
if (player == null || self.mc == null || self.mc.getConnection() == null) return -1; Minecraft mc = Minecraft.getInstance();
PlayerInfo entry = self.mc.getConnection().getPlayerInfo(player.getUUID()); if (player == null || mc == null || mc.getConnection() == null) return -1;
PlayerInfo entry = mc.getConnection().getPlayerInfo(player.getUUID());
return entry != null ? entry.getLatency() : -1; return entry != null ? entry.getLatency() : -1;
} }

View file

@ -11,6 +11,7 @@ import dev.loki.lovisual.features.module.Modules;
import dev.loki.lovisual.features.relations.CategoryService; import dev.loki.lovisual.features.relations.CategoryService;
import dev.loki.lovisual.render.engine.text.TextRenderer; import dev.loki.lovisual.render.engine.text.TextRenderer;
import dev.loki.lovisual.util.text.TextRenderUtil; import dev.loki.lovisual.util.text.TextRenderUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
@ -150,9 +151,10 @@ final class NameTagLayoutEngine {
} }
static boolean shouldShowDistanceText(NameTags self, Player player) { static boolean shouldShowDistanceText(NameTags self, Player player) {
Minecraft mc = Minecraft.getInstance();
if (!self.toggles.get("Show distance")) return false; if (!self.toggles.get("Show distance")) return false;
if (player == null || self.mc.player == null) return false; if (player == null || mc.player == null) return false;
if (player != self.mc.player) return true; if (player != mc.player) return true;
Freecam freecam = Modules.get(Freecam.class); Freecam freecam = Modules.get(Freecam.class);
return freecam != null && freecam.isEnabled(); return freecam != null && freecam.isEnabled();
} }

Some files were not shown because too many files have changed in this diff Show more