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:
parent
e1d7d19a83
commit
707820e418
167 changed files with 5034 additions and 345 deletions
43
TODO.md
43
TODO.md
|
|
@ -27,4 +27,45 @@
|
|||
- [x] Фаза 3.5: Удаление читерских модулей (легит-направление)
|
||||
- [x] Фаза 4: Security (BackdoorProtection, StaffTracker)
|
||||
- [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`).
|
||||
|
|
|
|||
200
combatant-client-26.2/DEVELOPMENT.md
Normal file
200
combatant-client-26.2/DEVELOPMENT.md
Normal 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/`) не заглядывался копипаст
|
||||
|
|
@ -474,11 +474,16 @@ BetterTooltips, BetterButtons, DynamicIsland). Наследуют `BaseHudElemen
|
|||
|
||||
Интерфейс `ClientCommand` + аннотация `@CommandInfo`, регистрация — вручную в `CommandManager`.
|
||||
|
||||
Префикс команд в чате — **`%`** (старый `@` тоже принимается). Источник истины —
|
||||
`CommandPrefix` (`CHAR`/`STRING`/`isCommandLike`/`strip`). Автокомплит: имена команд +
|
||||
аргументы из `suggest()`, а если команда не дала вариантов — подсказка-usage через
|
||||
`UsageHints` (например `%toggle <module>`).
|
||||
|
||||
```java
|
||||
@CommandInfo(
|
||||
id = "toggle",
|
||||
aliases = "t",
|
||||
usage = "@toggle <module> [on|off|toggle]",
|
||||
usage = "%toggle <module> [on|off|toggle]",
|
||||
descriptionKey = "command.toggle.description"
|
||||
)
|
||||
public final class ToggleCommand implements ClientCommand {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Developed and maintained by loki5512344.
|
|||
|
||||
### 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.
|
||||
|
||||
|
|
@ -34,14 +34,13 @@ LoVisual is developed and maintained by [loki5512344](https://github.com/loki551
|
|||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| Combat | KillAura, AutoCrystal, AutoAnchor, AutoBed, AutoBow, Criticals, Reach, Velocity, Backtrack, and more |
|
||||
| Movement | Speed, Flight, ElytraFly, ElytraHelper, Sprint, Timer, Strafe, TargetStrafe, Scaffold, Parkour |
|
||||
| Visuals | ESP, NameTags, Chams, Tracers, Crosshair, Zoom, Freecam, FullBright, MotionBlur, NoRender |
|
||||
| Player | AutoTool, AutoEat, AutoArmor, AutoTotem, Scaffold, ClickPearl, FakeLag, XCarry |
|
||||
| Misc | BetterMinecraft, FakePlayer, HitSounds, Panic, NoSound |
|
||||
| HUD | Draggable elements, TargetHUD, ModuleList, Watermark, Armor, Potions, Keystrokes, Coords, Notifications, Scoreboard |
|
||||
| Combat | ProjectilePuncher, Reach, RubberHand, SpearAssist, TPSSync, PvpCooldowns, NoFriendDamage, TargetESP, Predictions, DamageTint |
|
||||
| Visuals | ESP, NameTags, Chams, Crosshair, FullBright, MotionBlur, NoRender, Freecam, Hitboxes, Light, ChinaHat, Trinkets, and more |
|
||||
| Player | AutoTool, ClickPearl, FakeLag, NoDelay, LockSlot, AutoRespawn, DeathCoords, NoInteract |
|
||||
| Misc | BetterMinecraft, FakePlayer, HitSounds, NameProtect, MessageFilter, NoSound, DefineTarget |
|
||||
| HUD | Draggable & static elements: TargetHUD, ModuleList, Watermark, Reach, Armor, Potions, Keystrokes, Coords, Notifications, Scoreboard |
|
||||
| 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 |
|
||||
| Custom renderer | Custom UI rendering with MSDF fonts, SVG icons, and shader support |
|
||||
| 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.
|
||||
|
||||
|
|
@ -90,14 +89,13 @@ LoVisual разрабатывается и поддерживается [loki551
|
|||
|
||||
| Возможность | Описание |
|
||||
|-------------|----------|
|
||||
| Combat | KillAura, AutoCrystal, AutoAnchor, AutoBed, AutoBow, Criticals, Reach, Velocity, Backtrack и другие |
|
||||
| Movement | Speed, Flight, ElytraFly, ElytraHelper, Sprint, Timer, Strafe, TargetStrafe, Scaffold, Parkour |
|
||||
| Visuals | ESP, NameTags, Chams, Tracers, Crosshair, Zoom, Freecam, FullBright, MotionBlur, NoRender |
|
||||
| Player | AutoTool, AutoEat, AutoArmor, AutoTotem, Scaffold, ClickPearl, FakeLag, XCarry |
|
||||
| Misc | BetterMinecraft, FakePlayer, HitSounds, Panic, NoSound |
|
||||
| HUD | Перетаскиваемые элементы: TargetHUD, ModuleList, Watermark, Armor, Potions, Keystrokes, Coords, Notifications, Scoreboard |
|
||||
| Combat | ProjectilePuncher, Reach, RubberHand, SpearAssist, TPSSync, PvpCooldowns, NoFriendDamage, TargetESP, Predictions, DamageTint |
|
||||
| Visuals | ESP, NameTags, Chams, Crosshair, FullBright, MotionBlur, NoRender, Freecam, Hitboxes, Light, ChinaHat, Trinkets и другие |
|
||||
| Player | AutoTool, ClickPearl, FakeLag, NoDelay, LockSlot, AutoRespawn, DeathCoords, NoInteract |
|
||||
| Misc | BetterMinecraft, FakePlayer, HitSounds, NameProtect, MessageFilter, NoSound, DefineTarget |
|
||||
| HUD | Перетаскиваемые и статические элементы: TargetHUD, ModuleList, Watermark, Reach, Armor, Potions, Keystrokes, Coords, Notifications, Scoreboard |
|
||||
| ClickGUI | Полноценный GUI с браузером модулей, редактором настроек, тем и профилей конфигов |
|
||||
| Профили `.lvisual` | Профили для модулей, HUD и тем — можно шарить между пользователями |
|
||||
| Профили `.lvcfg` | Профили для модулей, HUD и тем — можно шарить между пользователями |
|
||||
| Addon API | Версионированное API для аддонов |
|
||||
| Кастомный рендер | UI-рендеринг с MSDF-шрифтами, SVG-иконками и шейдерами |
|
||||
| Iris/Sodium/Xaero | Опциональная совместимость с популярными модами |
|
||||
|
|
@ -137,7 +135,7 @@ LoVisual разрабатывается и поддерживается [loki551
|
|||
|
||||
### Docs
|
||||
|
||||
- [Architecture](docs/ARCHITECTURE.md)
|
||||
- [Development guide](DEVELOPMENT.md) — project rules, how to add modules/HUD/events/config
|
||||
|
||||
### License
|
||||
|
||||
|
|
|
|||
|
|
@ -148,8 +148,10 @@
|
|||
чистый import-churn без функциональной ценности; делать только отдельным скриптовым проходом
|
||||
|
||||
## Фаза 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 (легит) — ПЛАН, обсуждаем
|
||||
> Референс: `ref/delta-26.2-main` (чит-клиент, код обфусцирован — берём только идеи,
|
||||
|
|
@ -167,41 +169,64 @@
|
|||
- [x] Правило папок: максимум 4 файла (было 3)
|
||||
|
||||
### Перенос (список пользователя)
|
||||
- [ ] **StreamerMode** (misc, ~80 у них) — при записи/стриме скрывает: ники игроков,
|
||||
имена друзей, названия серверов («Анархия-N») в чате/таб-листе/скорборде; скрытие скинов.
|
||||
Интеграция: наши BetterChat + CustomTabList + Scoreboard-виджет + рендер скинов.
|
||||
- [x] **StreamerMode** (misc) — маскирует ники-подобные токены и названия серверов в чате
|
||||
(общая точка NameProtect: ChatComponentMixin.addMessage), скины заменяются на
|
||||
стандартные (AbstractClientPlayerMixin.getSkin). Чистая логика — `util/text/NameMasker`
|
||||
(тест NameMaskerTest).
|
||||
- [x] **LockSlot** (player, ~36) — запрет выброса из выбранных слотов 1–9 (анти-случайный Q),
|
||||
опция «только в PVP», предупреждение в чат. Точка: перехват drop-пакета.
|
||||
- [x] **AutoRespawn** (player, ~28) — авто-возрождение после смерти (tick на death screen).
|
||||
- [ ] **AutoLeave** (player, ~61) — выход в хаб по триггерам: малое ХП, игроки рядом (N, радиус), задержка.
|
||||
- [ ] **AutoAccept** (player, ~67) — авто-принятие запросов: клан/ТП; опция «только от друзей»
|
||||
через наш PlayerRelations. Точка: перехват chat-пакетов.
|
||||
- [ ] **SoundReducer** (player, ~42) — приглушение выбранных звуков/категорий (громкость 0..N),
|
||||
в отличие от нашего NoSound (он только вкл/выкл всего). Вопрос: новый модуль или развитие NoSound.
|
||||
- [ ] **Wasted** (misc, ~100) — ОТМЕНЕНО (решение пользователя)
|
||||
- [ ] **ThirdPerson** (player, ~77) — свободный обзор от 3-го лица без поворота движения
|
||||
(по нажатию/зажатию бинда). Не дублирует Freecam (он про отдельную камеру-сущность).
|
||||
Точка: наш CameraMixin.
|
||||
- [ ] **UseTracker** (player, ~86) — уведомления об использованиях: тотем, зелья, предметы.
|
||||
Вывод через наш HudNotifier. Точка: пакеты + инвентарь.
|
||||
- [x] **NoServerPack** (misc) — у Delta ПУСТЫШКА (пустой класс). Сделаем по-настоящему:
|
||||
авто-отклонение принудительного серверного ресурспака (спрашивать/отклонять всегда).
|
||||
Точка: наш ClientCommonPacketListenerImplMixin.
|
||||
- [x] **AutoLeave** (player) — выход по триггерам: низкое ХП, N игроков в радиусе
|
||||
(друзья игнорируются опцией), задержка + предупреждение. Выход через
|
||||
`mc.disconnect(null, false)`; чистая state-machine `LeavePolicy` (AutoLeaveTest).
|
||||
- [x] **AutoAccept** (player) — авто-ответ на ТП/клан-запросы: паттерны фраз (SetValue),
|
||||
команда ответа, «только друзья» (PlayerRelations), задержка, кулдаун ≥500мс.
|
||||
Пакетный путь: PvpChatEvent (handleSystemChat/handlePlayerChat), отправка
|
||||
`connection.sendCommand(...)`. Чистый парсер `TeleportRequestParser` (AutoAcceptTest).
|
||||
- [x] **SoundReducer** (player, ~42) — РЕШЕНО: развитие NoSound, а не новый модуль.
|
||||
Режимы Mute (старое поведение через ClientPacketListenerMixin) и Reduce —
|
||||
новая громкость 1–100% через SoundEngineMixin (@Redirect getVolume в
|
||||
SoundEngine.play). Чистая математика `NoSound.resolveScale` (NoSoundTest).
|
||||
- [x] **Wasted** (misc, ~100) — ОТМЕНЕНО (решение пользователя)
|
||||
- [x] **ThirdPerson** (player) — свободный обзор от 3-го лица по удержанию бинда (L,
|
||||
настраивается), движение игрока не поворачивается. 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).
|
||||
Точка: перехват атаки (AttackEvent у них; у нас миксин на attack/interact-пакет).
|
||||
- [x] **TapeMouse** (combat, ~66) — автоклик по таймеру. ⚠️ ПРОТИВОРЕЧИТ легит-политике Фазы 3.5
|
||||
(тот же класс, что удалённые AutoAttack/TriggerBot). По умолчанию НЕ делаем / делаем
|
||||
легит-вариант «напоминание о клике» без автоматического клика — РЕШИТЬ.
|
||||
- [x] **TapeMouse** — ОСТАВЛЕН (решение владельца 2026-09-08): автоклик по таймеру
|
||||
(Attack/Use) для AFK-ферм, интервал, опция «только при зажатии». Считаем
|
||||
допустимым исключением из Фазы 3.5 по явному решению владельца.
|
||||
|
||||
### Кандидаты из отчёта (не в списке пользователя — обсудить)
|
||||
- [x] **DeathCoords** (~27) — координаты последней смерти в чат. Дёшево, полезно.
|
||||
- [ ] **ItemScroller** — перекладывание предметов прокруткой в инвентарях. Крупный QoL (~200+).
|
||||
- [ ] **ColorPlus** — ⚠️ это по сути наш удалённый PostFX (экспозиция/гамма/контраст/насыщенность/
|
||||
виньетка/резкость/LUT). Восстанавливать под новым именем или забыть — РЕШИТЬ.
|
||||
- [ ] **Arrows / off-screen индикаторы** — дополнение к Pointers (у нас их нет вообще).
|
||||
- [x] **ItemScroller** (misc) — shift-перекладка предметов колёсиком в контейнерах:
|
||||
QUICK_MOVE клик по слоту под курсором, иначе слот хотбара (опция).
|
||||
Точка: AbstractContainerScreenMixin.mouseScrolled; чистая логика
|
||||
`ScrollMovePolicy` (ScrollMovePolicyTest).
|
||||
- [x] **Arrows / off-screen индикаторы** — ОТМЕНЕНО (решение владельца 2026-09-08).
|
||||
|
||||
### Проверка после каждого модуля: `./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» (маленькие файлы) — ПЛАН
|
||||
> Правило ≤200 уже есть (Фаза 1–3), но долг: ~240 файлов >200 строк.
|
||||
> У Delta файлы компактные и слои разделены: api / client(features, ui, utils, processors) /
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -55,10 +55,10 @@ public enum CommandManager {
|
|||
public static boolean handle(String raw) {
|
||||
if (raw == null) return false;
|
||||
String trimmed = raw.trim();
|
||||
if (!isCommandLike(trimmed)) return false;
|
||||
if (!CommandPrefix.isCommandLike(trimmed)) return false;
|
||||
if (!initialized) init();
|
||||
|
||||
String body = trimmed.substring(1).trim();
|
||||
String body = CommandPrefix.strip(trimmed);
|
||||
if (body.isEmpty()) return false;
|
||||
String[] parts = body.split("\\s+");
|
||||
if (parts.length == 0) return false;
|
||||
|
|
@ -66,7 +66,8 @@ public enum CommandManager {
|
|||
|
||||
ClientCommand cmd = find(name);
|
||||
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;
|
||||
}
|
||||
if (!cmd.isAvailable()) {
|
||||
|
|
@ -82,7 +83,9 @@ public enum CommandManager {
|
|||
public static List<Suggestion> suggest(String input, int cursor) {
|
||||
if (input == null) return List.of();
|
||||
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();
|
||||
|
||||
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));
|
||||
CommandContext ctx = new CommandContext(Minecraft.getInstance(), input, cmdName, args);
|
||||
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();
|
||||
|
|
@ -138,15 +147,6 @@ public enum CommandManager {
|
|||
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) {
|
||||
String lower = token == null ? "" : token.toLowerCase();
|
||||
List<String> out = new ArrayList<>();
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public final class CommandMetadata {
|
|||
boolean localizedDescription) {
|
||||
this.id = normalizeId(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()) {
|
||||
this.description = this.usage;
|
||||
this.localizedDescription = false;
|
||||
|
|
@ -94,15 +94,34 @@ public final class CommandMetadata {
|
|||
private static CommandMetadata fromLegacy(ClientCommand command) {
|
||||
String id = command.name();
|
||||
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 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) {
|
||||
if (id == null || id.isBlank()) throw new IllegalArgumentException("Command id cannot be blank");
|
||||
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)) {
|
||||
throw new IllegalArgumentException("Invalid command id: " + id);
|
||||
}
|
||||
|
|
@ -115,7 +134,9 @@ public final class CommandMetadata {
|
|||
for (String alias : aliases) {
|
||||
if (alias == null || alias.isBlank()) continue;
|
||||
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 (!normalized.contains(value)) normalized.add(value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <module> [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: "<module>" becomes "%toggle <module>"
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ import java.util.Locale;
|
|||
|
||||
@CommandInfo(
|
||||
id = "addons",
|
||||
usage = "@addons [list|scan|enable|disable] [id]",
|
||||
usage = "%addons [list|scan|enable|disable] [id]",
|
||||
descriptionKey = "command.addons.description"
|
||||
)
|
||||
public final class AddonsCommand implements ClientCommand {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import java.util.Locale;
|
|||
@CommandInfo(
|
||||
id = "bind",
|
||||
aliases = {"binds", "keybind"},
|
||||
usage = "@bind <module> <key|combo|none> | @bind list",
|
||||
usage = "%bind <module> <key|combo|none> | @bind list",
|
||||
descriptionKey = "command.bind.description"
|
||||
)
|
||||
public final class BindCommand implements ClientCommand {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import java.util.Locale;
|
|||
@CommandInfo(
|
||||
id = "config",
|
||||
aliases = {"cfg", "settings"},
|
||||
usage = "@config [save|load|path]",
|
||||
usage = "%config [save|load|path]",
|
||||
descriptionKey = "command.config.description"
|
||||
)
|
||||
public final class ConfigCommand implements ClientCommand {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import java.util.Locale;
|
|||
@CommandInfo(
|
||||
id = "coordinates",
|
||||
aliases = {"coords", "position", "pos"},
|
||||
usage = "@coordinates",
|
||||
usage = "%coordinates",
|
||||
descriptionKey = "command.coordinates.description"
|
||||
)
|
||||
public final class CoordinatesCommand implements ClientCommand {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ package dev.loki.lovisual.features.command.impl;
|
|||
import dev.loki.lovisual.features.command.ClientCommand;
|
||||
import dev.loki.lovisual.features.command.CommandContext;
|
||||
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.CommandMetadata;
|
||||
import dev.loki.lovisual.features.command.CommandOutput;
|
||||
|
|
@ -25,7 +26,7 @@ import java.util.Locale;
|
|||
@CommandInfo(
|
||||
id = "help",
|
||||
aliases = {"commands", "cmds"},
|
||||
usage = "@help [page|command]",
|
||||
usage = "%help [page|command]",
|
||||
descriptionKey = "command.help.description"
|
||||
)
|
||||
public final class HelpCommand implements ClientCommand {
|
||||
|
|
@ -101,13 +102,13 @@ public final class HelpCommand implements ClientCommand {
|
|||
|
||||
private static MutableComponent commandLink(ClientCommand command) {
|
||||
CommandMetadata metadata = command.metadata();
|
||||
String insert = "@" + metadata.id() + " ";
|
||||
String insert = CommandPrefix.STRING + metadata.id() + " ";
|
||||
String aliases = metadata.aliases().isEmpty() ? "none" : String.join(", ", metadata.aliases());
|
||||
Component hover = Component.literal(metadata.description()
|
||||
+ "\nUsage: " + metadata.usage()
|
||||
+ "\nAliases: " + aliases
|
||||
+ "\nClick to insert");
|
||||
return Component.literal("@" + metadata.id())
|
||||
return Component.literal(CommandPrefix.STRING + metadata.id())
|
||||
.withStyle(style -> style
|
||||
.withColor(COMMAND_COLOR)
|
||||
.withUnderlined(true)
|
||||
|
|
@ -120,13 +121,13 @@ public final class HelpCommand implements ClientCommand {
|
|||
.withStyle(style -> style
|
||||
.withBold(true)
|
||||
.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))));
|
||||
}
|
||||
|
||||
private static void showDetails(ClientCommand command) {
|
||||
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));
|
||||
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)));
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import java.util.Locale;
|
|||
@CommandInfo(
|
||||
id = "hide",
|
||||
aliases = {"modulevisibility", "showmodule"},
|
||||
usage = "@hide <module> [on|off|toggle]",
|
||||
usage = "%hide <module> [on|off|toggle]",
|
||||
descriptionKey = "command.hide.description"
|
||||
)
|
||||
public final class HideCommand implements ClientCommand {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import java.util.stream.Collectors;
|
|||
@CommandInfo(
|
||||
id = "iris",
|
||||
aliases = "shaderpack",
|
||||
usage = "@iris",
|
||||
usage = "%iris",
|
||||
descriptionKey = "command.iris.description"
|
||||
)
|
||||
public final class IrisCommand implements ClientCommand {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import java.util.Locale;
|
|||
@CommandInfo(
|
||||
id = "modules",
|
||||
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"
|
||||
)
|
||||
public final class ModulesCommand implements ClientCommand {
|
||||
|
|
@ -54,7 +54,7 @@ public final class ModulesCommand implements ClientCommand {
|
|||
message.append(Component.literal(module.getDisplayName())
|
||||
.withStyle(style -> style
|
||||
.withColor(color)
|
||||
.withClickEvent(new ClickEvent.RunCommand("@toggle " + module.name()))
|
||||
.withClickEvent(new ClickEvent.RunCommand("%toggle " + module.name()))
|
||||
.withHoverEvent(new HoverEvent.ShowText(Component.literal(
|
||||
module.getDescription() + "\nClick to toggle\nID: " + module.name())))));
|
||||
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
|
||||
.withBold(true)
|
||||
.withColor(0xFF70E0A0)
|
||||
.withClickEvent(new ClickEvent.RunCommand("@modules " + filter + " " + page)));
|
||||
.withClickEvent(new ClickEvent.RunCommand("%modules " + filter + " " + page)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import dev.loki.lovisual.util.player.NetworkStatsUtil;
|
|||
@CommandInfo(
|
||||
id = "ping",
|
||||
aliases = "latency",
|
||||
usage = "@ping",
|
||||
usage = "%ping",
|
||||
descriptionKey = "command.ping.description"
|
||||
)
|
||||
public final class PingCommand implements ClientCommand {
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ public abstract class RelationCommand implements ClientCommand {
|
|||
@CommandInfo(
|
||||
id = "friend",
|
||||
aliases = {"friends", "f"},
|
||||
usage = "@friend [list|add|remove|toggle|clear] [player]",
|
||||
usage = "%friend [list|add|remove|toggle|clear] [player]",
|
||||
descriptionKey = "command.friend.description"
|
||||
)
|
||||
public static final class Friends extends RelationCommand {
|
||||
|
|
@ -135,7 +135,7 @@ public abstract class RelationCommand implements ClientCommand {
|
|||
@CommandInfo(
|
||||
id = "enemy",
|
||||
aliases = {"enemies", "e"},
|
||||
usage = "@enemy [list|add|remove|toggle|clear] [player]",
|
||||
usage = "%enemy [list|add|remove|toggle|clear] [player]",
|
||||
descriptionKey = "command.enemy.description"
|
||||
)
|
||||
public static final class Enemies extends RelationCommand {
|
||||
|
|
@ -148,7 +148,7 @@ public abstract class RelationCommand implements ClientCommand {
|
|||
@CommandInfo(
|
||||
id = "staff",
|
||||
aliases = {"staffs", "admin"},
|
||||
usage = "@staff [list|add|remove|toggle|clear] [player]",
|
||||
usage = "%staff [list|add|remove|toggle|clear] [player]",
|
||||
descriptionKey = "command.staff.description"
|
||||
)
|
||||
public static final class Staff extends RelationCommand {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import java.util.Locale;
|
|||
@CommandInfo(
|
||||
id = "serverinfo",
|
||||
aliases = {"server", "sinfo"},
|
||||
usage = "@serverinfo",
|
||||
usage = "%serverinfo",
|
||||
descriptionKey = "command.serverinfo.description"
|
||||
)
|
||||
public final class ServerInfoCommand implements ClientCommand {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import java.util.Locale;
|
|||
@CommandInfo(
|
||||
id = "toggle",
|
||||
aliases = "t",
|
||||
usage = "@toggle <module> [on|off|toggle]",
|
||||
usage = "%toggle <module> [on|off|toggle]",
|
||||
descriptionKey = "command.toggle.description"
|
||||
)
|
||||
public final class ToggleCommand implements ClientCommand {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import java.util.Locale;
|
|||
@CommandInfo(
|
||||
id = "tps",
|
||||
aliases = "servertps",
|
||||
usage = "@tps",
|
||||
usage = "%tps",
|
||||
descriptionKey = "command.tps.description"
|
||||
)
|
||||
public final class TpsCommand implements ClientCommand {
|
||||
|
|
|
|||
|
|
@ -12,10 +12,12 @@ import dev.loki.lovisual.features.command.CommandContext;
|
|||
import dev.loki.lovisual.features.command.CommandInfo;
|
||||
import dev.loki.lovisual.features.command.CommandOutput;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@CommandInfo(
|
||||
id = "username",
|
||||
aliases = {"name", "ign", "whoami"},
|
||||
usage = "@username",
|
||||
usage = "%username",
|
||||
descriptionKey = "command.username.description"
|
||||
)
|
||||
public final class UsernameCommand implements ClientCommand {
|
||||
|
|
@ -29,4 +31,9 @@ public final class UsernameCommand implements ClientCommand {
|
|||
CommandOutput.send("Username: " + profile.name() + " | UUID: " + profile.id());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggest(CommandContext ctx, int argIndex, String token) {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import java.util.Locale;
|
|||
@CommandInfo(
|
||||
id = "xaero",
|
||||
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"
|
||||
)
|
||||
public final class XaeroWaypointCommand implements ClientCommand {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package dev.loki.lovisual.features.gui.chat;
|
||||
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.mixins.accessors.ChatScreenAccessor;
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
|
|
@ -128,7 +129,7 @@ final class BetterChatContextMenuHandler {
|
|||
} else if (evt instanceof ClickEvent.CopyToClipboard) {
|
||||
ClipboardUtil.copy(val);
|
||||
return true;
|
||||
} else if (evt instanceof ClickEvent.RunCommand && val.startsWith("@")) {
|
||||
} else if (evt instanceof ClickEvent.RunCommand && CommandPrefix.isCommandLike(val)) {
|
||||
return CommandManager.handle(val);
|
||||
} else if (evt instanceof ClickEvent.SuggestCommand || evt instanceof ClickEvent.RunCommand) {
|
||||
if (!ctrlDown) return false;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
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.render.helpers.SystemCursor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
@ -44,7 +45,7 @@ final class BetterChatSuggestionRenderer {
|
|||
windowChanged = CommandSuggestorBridge.peekWindow(BetterChatRenderer.storedScreen) != CommandSuggestorBridge.lastWindow();
|
||||
}
|
||||
if (inputChanged || windowChanged || BetterChatRenderer.lastSnapshot == null) {
|
||||
if (prefix == '@') {
|
||||
if (prefix == CommandPrefix.CHAR || prefix == CommandPrefix.LEGACY) {
|
||||
BetterChatRenderer.lastSnapshot = BetterChatSuggestionState.buildCustomSnapshot(text, cursor);
|
||||
} else {
|
||||
BetterChatRenderer.lastSnapshot = CommandSuggestorBridge.snapshot(BetterChatRenderer.storedScreen);
|
||||
|
|
@ -56,7 +57,7 @@ final class BetterChatSuggestionRenderer {
|
|||
BetterChatSuggestionState.resetSuggestionTracking();
|
||||
return;
|
||||
}
|
||||
if (prefix == '@') {
|
||||
if (prefix == CommandPrefix.CHAR || prefix == CommandPrefix.LEGACY) {
|
||||
BetterChatRenderer.suggestWindow = null;
|
||||
}
|
||||
int hash = snap.texts().hashCode() ^ snap.selection() ^ snap.startIndex();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package dev.loki.lovisual.features.gui.chat;
|
||||
|
||||
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.SuggestionWindowAccessor;
|
||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||
|
|
@ -80,7 +81,7 @@ final class BetterChatSuggestionState {
|
|||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
if (Character.isWhitespace(c)) continue;
|
||||
if (c == '/' || c == '@') return c;
|
||||
if (c == '/' || c == CommandPrefix.CHAR || c == CommandPrefix.LEGACY) return c;
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package dev.loki.lovisual.features.gui.chat;
|
|||
|
||||
import com.mojang.brigadier.suggestion.Suggestion;
|
||||
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.util.screen.ClientScreen;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
|
@ -43,7 +44,7 @@ public final class BetterChatTabHandler {
|
|||
if (field == null) return false;
|
||||
String text = field.getValue();
|
||||
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();
|
||||
List<Suggestion> suggestions = CommandManager.suggest(text, cursor);
|
||||
|
|
|
|||
|
|
@ -50,7 +50,9 @@ public enum MenuSettingsResolver {
|
|||
new StaticHudCard("vanilla_bar", "Bar"),
|
||||
new StaticHudCard("vanilla_health", "Health"),
|
||||
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 =
|
||||
new StaticOwner("relations_color", () -> PlayerRelations.get().save());
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ public final class SettingsPanelComponent {
|
|||
private final List<SettingRow> rows = new ArrayList<>();
|
||||
private final List<SettingHit> hits = new ArrayList<>();
|
||||
private final SettingRenderSurface renderSurface;
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private String targetId = "";
|
||||
private String title = "Settings";
|
||||
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) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
MainConfig config = MainConfig.get();
|
||||
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) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
MainConfig config = MainConfig.get();
|
||||
if (hudContext || !isActive() || !config.isClickGuiHintsEnabled()) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ final class OnlineRelationPlayerPickerComponent {
|
|||
private static final long DYNAMIC_REFRESH_MS = 250L;
|
||||
private static final String I18N = "clickgui.settings.relations.online_picker.";
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final Consumer<String> statusSink;
|
||||
private final List<PlayerRow> rows = new ArrayList<>();
|
||||
private final List<RowHit> rowHits = new ArrayList<>();
|
||||
|
|
@ -452,6 +451,7 @@ final class OnlineRelationPlayerPickerComponent {
|
|||
}
|
||||
|
||||
private void refreshIfNeeded() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.getConnection() == null) return;
|
||||
long now = System.currentTimeMillis();
|
||||
int onlineCount = onlineCount();
|
||||
|
|
@ -483,10 +483,12 @@ final class OnlineRelationPlayerPickerComponent {
|
|||
}
|
||||
|
||||
private int onlineCount() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return mc != null && mc.getConnection() != null ? mc.getConnection().getListedOnlinePlayers().size() : 0;
|
||||
}
|
||||
|
||||
private void collectRows() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
rows.clear();
|
||||
if (mc == null || mc.getConnection() == null) {
|
||||
rowsVersion++;
|
||||
|
|
@ -520,6 +522,7 @@ final class OnlineRelationPlayerPickerComponent {
|
|||
}
|
||||
|
||||
private void refreshDynamicRows() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.getConnection() == null || rows.isEmpty()) return;
|
||||
boolean changed = false;
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
|
|
@ -546,6 +549,7 @@ final class OnlineRelationPlayerPickerComponent {
|
|||
}
|
||||
|
||||
private long structureSignature() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.getConnection() == null) return 0L;
|
||||
long h = 0x6A09E667F3BCC909L;
|
||||
for (PlayerInfo info : mc.getConnection().getListedOnlinePlayers()) {
|
||||
|
|
@ -567,6 +571,7 @@ final class OnlineRelationPlayerPickerComponent {
|
|||
}
|
||||
|
||||
private Component displayName(PlayerInfo info, String fallback) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
Component display = null;
|
||||
if (mc != null && mc.gui != null && mc.gui.hud != null && mc.gui.hud.getTabList() != null) {
|
||||
display = mc.gui.hud.getTabList().getNameForDisplay(info);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ public final class ThemeEditorPreviewComponent {
|
|||
private static final float DESIGN_W = 320f;
|
||||
private static final float DESIGN_H = 190f;
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final UiScriptModuleHandle previewModuleHandle = HudScriptLayouts.handle(PREVIEW_LAYOUT_ID);
|
||||
private final CachedUiScriptRuntime previewRuntime = new CachedUiScriptRuntime(HudScriptLayouts.runtimeReporter());
|
||||
private float previewAnim;
|
||||
|
|
@ -93,6 +92,7 @@ public final class ThemeEditorPreviewComponent {
|
|||
float panelH,
|
||||
float unit,
|
||||
float alpha) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (previewModuleHandle.isRuntimeBlocked() || mc == null || mc.getResourceManager() == null) return;
|
||||
HudScriptLayouts.pollReloadCombo(mc);
|
||||
if (previewModuleHandle.consumeChanged()) {
|
||||
|
|
@ -131,6 +131,7 @@ public final class ThemeEditorPreviewComponent {
|
|||
}
|
||||
|
||||
private UiScriptModule ensurePreviewModule() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!previewModuleHandle.ensureLoaded(mc.getResourceManager())) {
|
||||
HudScriptLayouts.reportLoadError(previewModuleHandle);
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ public final class Admins extends DraggableHudElement {
|
|||
private static final String EFFECT_NONE = "None";
|
||||
private static final String EFFECT_BLUR = "Blur";
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final HudGlobalConfig hud = HudGlobalConfig.get();
|
||||
private final ScriptedListHudPanel scriptedPanel = new ScriptedListHudPanel();
|
||||
private final NumberValue<Double> scaleValue =
|
||||
|
|
@ -176,6 +175,7 @@ public final class Admins extends DraggableHudElement {
|
|||
int screenW,
|
||||
int screenH) {
|
||||
boolean forceVisible = DraggableHudElementRegistry.isForceVisible();
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || (mc.player == null && !forceVisible)) {
|
||||
width = 0.0f;
|
||||
height = 0.0f;
|
||||
|
|
@ -450,6 +450,7 @@ public final class Admins extends DraggableHudElement {
|
|||
}
|
||||
|
||||
private Identifier resolvePreviewSkin() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc != null && mc.player != null) {
|
||||
return PlayerSkinResolver.resolveProfileSkin(mc.player.getGameProfile());
|
||||
}
|
||||
|
|
@ -609,6 +610,7 @@ public final class Admins extends DraggableHudElement {
|
|||
}
|
||||
|
||||
private AbstractClientPlayer resolvePlayerByName(String name) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (name == null || name.isEmpty() || mc == null || mc.level == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -631,6 +633,7 @@ public final class Admins extends DraggableHudElement {
|
|||
return PlayerSkinResolver.resolveProfileSkin(player.getGameProfile());
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc != null && mc.getConnection() != null) {
|
||||
for (PlayerInfo entry : mc.getConnection().getOnlinePlayers()) {
|
||||
var profile = entry.getProfile();
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ public final class Armor extends DraggableHudElement {
|
|||
private static final float DEFAULT_MARGIN = 16f;
|
||||
private static final float DIVIDER_THICKNESS = 0.5f;
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final HudGlobalConfig hud = HudGlobalConfig.get();
|
||||
private final NumberValue<Double> scaleValue = num("armor_scale", 3.68, 0.5, 5.0);
|
||||
private final EnumValue<LayoutMode> layoutMode =
|
||||
|
|
@ -110,6 +109,7 @@ public final class Armor extends DraggableHudElement {
|
|||
float tickDelta,
|
||||
int screenW,
|
||||
int screenH) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
layoutReady = false;
|
||||
boolean preview = DraggableHudElementRegistry.isForceVisible();
|
||||
float scale = HudScale.scale(screenW, screenH) * scaleValue.get().floatValue();
|
||||
|
|
@ -187,6 +187,7 @@ public final class Armor extends DraggableHudElement {
|
|||
float tickDelta,
|
||||
int screenW,
|
||||
int screenH) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!layoutReady || mc == null || mc.player == null || ctx == null) return;
|
||||
|
||||
ItemStack[] stacks = cachedStacks != null ? cachedStacks : getArmorStacks();
|
||||
|
|
@ -216,6 +217,7 @@ public final class Armor extends DraggableHudElement {
|
|||
}
|
||||
|
||||
private ItemStack[] getArmorStacks() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return new ItemStack[]{
|
||||
mc.player.getItemBySlot(EquipmentSlot.HEAD),
|
||||
mc.player.getItemBySlot(EquipmentSlot.CHEST),
|
||||
|
|
@ -261,6 +263,7 @@ public final class Armor extends DraggableHudElement {
|
|||
}
|
||||
|
||||
private boolean shouldShowWhenEmpty() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (hud == null) return false;
|
||||
HudGlobalConfig.HeaderMode mode = hud.getHeaderMode();
|
||||
if (mode == HudGlobalConfig.HeaderMode.ALWAYS) return true;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ public final class BetterChat extends DraggableHudElement {
|
|||
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> fontScale = num("font_scale", 1.36, 0.6, 1.4);
|
||||
private final NumberValue<Integer> maxLines = num("lines", 12, 4, 20);
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ public final class Cooldowns extends DraggableHudElement {
|
|||
};
|
||||
private static ItemStack[] previewStacks;
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final HudGlobalConfig hud = HudGlobalConfig.get();
|
||||
private final ScriptedListHudPanel scriptedPanel = new ScriptedListHudPanel();
|
||||
private final NumberValue<Double> scaleValue =
|
||||
|
|
@ -207,6 +206,7 @@ public final class Cooldowns extends DraggableHudElement {
|
|||
float tickDelta,
|
||||
int screenW,
|
||||
int screenH) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
foregroundReady = false;
|
||||
pendingItemTasks.clear();
|
||||
boolean forceVisible = DraggableHudElementRegistry.isForceVisible();
|
||||
|
|
@ -475,6 +475,7 @@ public final class Cooldowns extends DraggableHudElement {
|
|||
}
|
||||
|
||||
private List<Row> collectRows() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
List<Row> rows = new ArrayList<>();
|
||||
if (mc != null && mc.player != null) {
|
||||
var manager = mc.player.getCooldowns();
|
||||
|
|
|
|||
|
|
@ -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 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 RenderColor colorTmp = new RenderColor(0xFFFFFFFF);
|
||||
private boolean stackLayoutReady;
|
||||
|
|
@ -171,6 +170,7 @@ public final class HudNotifier extends DraggableHudElement implements ModuleStat
|
|||
|
||||
private void renderEngineInternal(Renderer2D renderer,
|
||||
TextRenderer textRenderer) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
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() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
Window window = mc == null ? null : mc.getWindow();
|
||||
if (window == null) return MessengerSide.RIGHT;
|
||||
int fbw = Math.max(1, window.getWidth());
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ public final class Inventory extends DraggableHudElement {
|
|||
private static final String EFFECT_NONE = "None";
|
||||
private static final String EFFECT_BLUR = "Blur";
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final HudGlobalConfig hud = HudGlobalConfig.get();
|
||||
private final ScriptedInventoryHudPanel scriptedPanel = new ScriptedInventoryHudPanel();
|
||||
|
||||
|
|
@ -170,6 +169,7 @@ public final class Inventory extends DraggableHudElement {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!toggleInit) {
|
||||
if (mc == null || mc.getWindow() == null) {
|
||||
return;
|
||||
|
|
@ -204,6 +204,7 @@ public final class Inventory extends DraggableHudElement {
|
|||
float tickDelta,
|
||||
int screenW,
|
||||
int screenH) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
layoutReady = false;
|
||||
hitboxActive = false;
|
||||
pendingItemTasks.clear();
|
||||
|
|
@ -374,6 +375,7 @@ public final class Inventory extends DraggableHudElement {
|
|||
}
|
||||
|
||||
private List<ItemStack> collectStacks() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
List<ItemStack> stacks = new ArrayList<>(LAST_INV_SLOT - FIRST_INV_SLOT);
|
||||
if (mc == null || mc.player == null) {
|
||||
return stacks;
|
||||
|
|
@ -450,6 +452,7 @@ public final class Inventory extends DraggableHudElement {
|
|||
}
|
||||
|
||||
private void initToggleState() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
toggleArmed = true;
|
||||
String combo = toggleBind.get();
|
||||
if (combo != null && !combo.isBlank() && !"NONE".equalsIgnoreCase(combo)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ public final class Itemizer extends DraggableHudElement {
|
|||
|
||||
public static Itemizer INSTANCE;
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final UiScriptModuleHandle moduleHandle = HudScriptLayouts.handle(LAYOUT_ID);
|
||||
private final CachedUiScriptRuntime runtime = new CachedUiScriptRuntime(HudScriptLayouts.runtimeReporter());
|
||||
private final LinkedHashMap<ItemizerEvent, Entry> entries = new LinkedHashMap<>();
|
||||
|
|
@ -334,6 +333,7 @@ public final class Itemizer extends DraggableHudElement {
|
|||
}
|
||||
|
||||
private UiScriptModule ensureModule() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.getResourceManager() == null) return null;
|
||||
HudScriptLayouts.pollReloadCombo(mc);
|
||||
if (moduleHandle.consumeChanged()) {
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ public final class MediaPlayer extends DraggableHudElement {
|
|||
private static final int ICON_NEXT = 0xEA05;
|
||||
private static final int ICON_SHUFFLE = 0xEA06;
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final HudGlobalConfig hud = HudGlobalConfig.get();
|
||||
private final MediaSessionService mediaService = MediaSessionService.get();
|
||||
private final NumberValue<Double> scale =
|
||||
|
|
@ -209,6 +208,7 @@ public final class MediaPlayer extends DraggableHudElement {
|
|||
float tickDelta,
|
||||
int screenW,
|
||||
int screenH) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null) return;
|
||||
boolean chatOpen = ClientScreen.current() instanceof ChatScreen;
|
||||
boolean preview = DraggableHudElementRegistry.isForceVisible();
|
||||
|
|
@ -539,6 +539,7 @@ public final class MediaPlayer extends DraggableHudElement {
|
|||
}
|
||||
|
||||
private void updateHover(boolean chatOpen, boolean showShuffle, boolean showRepeat) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!chatOpen) {
|
||||
playHover = lerp(playHover, 0f, 0.2f);
|
||||
shuffleHover = lerp(shuffleHover, 0f, 0.2f);
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ public final class Radar extends DraggableHudElement {
|
|||
Field.PING,
|
||||
Field.NONE
|
||||
};
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final HudGlobalConfig hud = HudGlobalConfig.get();
|
||||
private final EnumValue<Mode> mode =
|
||||
new EnumValue<>("radar_mode", Mode.RECT, Mode.RECT, Mode.TEXT);
|
||||
|
|
@ -135,6 +134,7 @@ public final class Radar extends DraggableHudElement {
|
|||
float tickDelta,
|
||||
int screenW,
|
||||
int screenH) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
boolean preview = DraggableHudElementRegistry.isForceVisible();
|
||||
if (mc == null && !preview) {
|
||||
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) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.player == null || mc.level == null) return;
|
||||
|
||||
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) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
boolean ready = mc != null && mc.player != null && mc.level != null;
|
||||
if (!ready && !preview) {
|
||||
width = 0f;
|
||||
|
|
@ -290,6 +292,7 @@ public final class Radar extends DraggableHudElement {
|
|||
}
|
||||
|
||||
private void appendField(StringBuilder out, Field field, Player player) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (field == null || field == Field.NONE) return;
|
||||
if (out.length() > 0) out.append(' ');
|
||||
switch (field) {
|
||||
|
|
@ -317,6 +320,7 @@ public final class Radar extends DraggableHudElement {
|
|||
}
|
||||
|
||||
private int getPlayerPing(Player player) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.getConnection() == null) return -1;
|
||||
PlayerInfo entry = mc.getConnection().getPlayerInfo(player.getUUID());
|
||||
return entry != null ? entry.getLatency() : -1;
|
||||
|
|
|
|||
|
|
@ -77,7 +77,6 @@ public final class Scoreboard extends DraggableHudElement {
|
|||
private static final String EFFECT_NONE = "None";
|
||||
private static final String EFFECT_BLUR = "Blur";
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final HudGlobalConfig hud = HudGlobalConfig.get();
|
||||
|
||||
private final KeyBindValue toggleBind =
|
||||
|
|
@ -582,6 +581,7 @@ public final class Scoreboard extends DraggableHudElement {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!toggleInit) {
|
||||
if (mc == null || mc.getWindow() == null) {
|
||||
return;
|
||||
|
|
@ -616,6 +616,7 @@ public final class Scoreboard extends DraggableHudElement {
|
|||
float tickDelta,
|
||||
int screenW,
|
||||
int screenH) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
boolean preview = DraggableHudElementRegistry.isForceVisible();
|
||||
if (mc == null || (mc.level == null && !preview)) {
|
||||
width = 0.0f;
|
||||
|
|
@ -758,6 +759,7 @@ public final class Scoreboard extends DraggableHudElement {
|
|||
}
|
||||
|
||||
private Objective resolveObjective() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.level == null || mc.player == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -824,6 +826,7 @@ public final class Scoreboard extends DraggableHudElement {
|
|||
}
|
||||
|
||||
private void initToggleState() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
toggleArmed = true;
|
||||
String combo = toggleBind.get();
|
||||
if (combo != null && !combo.isBlank() && !"NONE".equalsIgnoreCase(combo)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ public final class SpeedBps extends DraggableHudElement implements ScriptableHud
|
|||
private static final long SAMPLE_INTERVAL_MS = 75L;
|
||||
private static final int SAMPLE_COUNT = 20;
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final HudGlobalConfig hud = HudGlobalConfig.get();
|
||||
private final BooleanValue useBps = bool("speed_bps_bps", true);
|
||||
private final BooleanValue average = bool("speed_bps_average", false);
|
||||
|
|
@ -140,6 +139,7 @@ public final class SpeedBps extends DraggableHudElement implements ScriptableHud
|
|||
float tickDelta,
|
||||
int screenW,
|
||||
int screenH) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
boolean preview = DraggableHudElementRegistry.isForceVisible();
|
||||
if (mc == null && !preview) {
|
||||
width = 0f;
|
||||
|
|
@ -252,6 +252,7 @@ public final class SpeedBps extends DraggableHudElement implements ScriptableHud
|
|||
}
|
||||
|
||||
private float currentBps() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return PlayerSpeedUtil.getBps(mc != null ? mc.player : null);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.text.TextRenderer;
|
||||
import dev.loki.lovisual.util.pvp.opponents.TotemPopCounter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
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<>();
|
||||
|
||||
final Minecraft mc = Minecraft.getInstance();
|
||||
final HudGlobalConfig hud = HudGlobalConfig.get();
|
||||
|
||||
final NumberValue<Double> scale =
|
||||
|
|
|
|||
|
|
@ -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.TextRenderer;
|
||||
import dev.loki.lovisual.util.player.PlayerHealthResolver;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
|
||||
import static dev.loki.lovisual.features.gui.hud.draggable.impl.TargetHud.*;
|
||||
|
|
@ -36,6 +37,7 @@ final class TargetHudContentRenderer {
|
|||
float alphaFactor,
|
||||
long now,
|
||||
float dt) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
float contentX = x + (FACE_X + FACE_SIZE + CONTENT_GAP + CONTENT_X_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.snap(element.absorptionAnimation, targetAbsorptionFraction, PROGRESS_SNAP_EPSILON);
|
||||
|
||||
boolean selfTarget = target == element.mc.player;
|
||||
boolean selfTarget = target == mc.player;
|
||||
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.snap(element.displayedDistance, targetDistance, 0.01f);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.TextRenderer;
|
||||
import dev.loki.lovisual.util.player.effect.StatusEffectView;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.effect.MobEffectInstance;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
|
|
@ -141,14 +142,15 @@ final class TargetHudEffectsRenderer {
|
|||
}
|
||||
|
||||
private static List<MobEffectInstance> collectTargetEffects(TargetHud element, LivingEntity target, boolean preview) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (target == null) {
|
||||
return List.of();
|
||||
}
|
||||
if (target == element.mc.player && !element.effectsSelf.get() && !preview) {
|
||||
if (target == mc.player && !element.effectsSelf.get() && !preview) {
|
||||
return List.of();
|
||||
}
|
||||
if (target instanceof Player player) {
|
||||
StatusEffectView.sync(element.mc);
|
||||
StatusEffectView.sync(mc);
|
||||
List<MobEffectInstance> effects = StatusEffectView.collectHudEffects(player);
|
||||
if (!effects.isEmpty() || !preview) {
|
||||
return effects;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import dev.loki.lovisual.render.engine.text.Fonts;
|
|||
import dev.loki.lovisual.render.engine.text.TextRenderer;
|
||||
import dev.loki.lovisual.util.player.PlayerSkinResolver;
|
||||
import dev.loki.lovisual.util.pvp.opponents.TotemPopSnapshot;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.resources.Identifier;
|
||||
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) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
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);
|
||||
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);
|
||||
TextRenderer faceRenderer = Fonts.renderer("Inter", FontInfo.Type.Bold, textRenderer);
|
||||
float textScale = PLACEHOLDER_TEXT_SCALE * element.scale.get().floatValue() * HudScale.scale(
|
||||
element.mc.getWindow().getGuiScaledWidth(),
|
||||
element.mc.getWindow().getGuiScaledHeight()
|
||||
mc.getWindow().getGuiScaledWidth(),
|
||||
mc.getWindow().getGuiScaledHeight()
|
||||
);
|
||||
faceRenderer.begin(textScale, false, false);
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -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.text.TextRenderer;
|
||||
import dev.loki.lovisual.util.pvp.opponents.TotemPopSnapshot;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
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) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
try (ProfilerPhase.Scope ignored = ProfilerPhase.scope("2d:target_hud:main");
|
||||
RenderProfiler2D.Section ignoredSection = RenderProfiler2D.section("target_hud:main")) {
|
||||
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();
|
||||
LivingEntity target = TargetHudTargetResolver.resolveTarget(element, preview);
|
||||
TargetHudTargetResolver.updateDisplayTarget(element, target, now);
|
||||
|
|
|
|||
|
|
@ -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.screen.ClientScreen;
|
||||
import dev.loki.lovisual.util.target.TargetManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.ChatScreen;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
|
@ -22,17 +23,19 @@ final class TargetHudTargetResolver {
|
|||
}
|
||||
|
||||
static LivingEntity resolveTarget(TargetHud element, boolean preview) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
LivingEntity target = TargetManager.getTarget();
|
||||
if (target != null && element.playersOnly.get() && !(target instanceof Player)) {
|
||||
target = null;
|
||||
}
|
||||
if (target == null && (preview || ClientScreen.current() instanceof ChatScreen)) {
|
||||
target = element.mc.player;
|
||||
target = mc.player;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
static void updateDisplayTarget(TargetHud element, LivingEntity target, long now) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (target != null) {
|
||||
if (element.displayTarget != target) {
|
||||
element.displayTarget = target;
|
||||
|
|
@ -43,14 +46,14 @@ final class TargetHudTargetResolver {
|
|||
element.healthAnimation = realHealth / baseMaxHealth;
|
||||
element.trailAnimation = element.healthAnimation;
|
||||
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.lastSeenMs = now;
|
||||
return;
|
||||
}
|
||||
|
||||
if (element.displayTarget == element.mc.player && !(ClientScreen.current() instanceof ChatScreen)) {
|
||||
if (element.displayTarget == mc.player && !(ClientScreen.current() instanceof ChatScreen)) {
|
||||
element.lastSeenMs = 0L;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@ public final class DynamicIsland extends AbstractHudElement {
|
|||
private static final int STATUS_PVP = 0xFFFF6368;
|
||||
private static final int PROGRESS_BG = 0x2AFFFFFF;
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final MediaSessionService mediaService = MediaSessionService.get();
|
||||
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);
|
||||
|
|
@ -189,11 +188,13 @@ public final class DynamicIsland extends AbstractHudElement {
|
|||
|
||||
@Override
|
||||
public boolean isMouseOverInteractive(float mx, float my) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return mc != null && isInteractiveScreen(ClientScreen.current()) && contains(mx, my);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMouseClicked(float mx, float my, int button) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || !isInteractiveScreen(ClientScreen.current())) 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) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!Lifecycle.canRunHud()) return;
|
||||
if (mc == null || mc.getWindow() == null || ctx == null) return;
|
||||
Screen currentScreen = ClientScreen.current();
|
||||
|
|
@ -275,6 +277,7 @@ public final class DynamicIsland extends AbstractHudElement {
|
|||
int screenW,
|
||||
int screenH,
|
||||
boolean screenOverlayPass) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null) return;
|
||||
HudScriptLayouts.pollReloadCombo(mc);
|
||||
if (moduleHandle.consumeChanged()) {
|
||||
|
|
@ -562,6 +565,7 @@ public final class DynamicIsland extends AbstractHudElement {
|
|||
}
|
||||
|
||||
private UiScriptModule ensureModule() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.getResourceManager() == null) return null;
|
||||
if (!moduleHandle.ensureLoaded(mc.getResourceManager())) {
|
||||
HudScriptLayouts.reportLoadError(moduleHandle);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +57,6 @@ public final class CustomTabList extends AbstractHudElement {
|
|||
private static final long STRUCTURE_PROBE_INTERVAL_MS = 250L;
|
||||
private static final long DYNAMIC_REFRESH_INTERVAL_MS = 500L;
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final TabListAnimator animator = new TabListAnimator();
|
||||
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);
|
||||
|
|
@ -107,6 +106,7 @@ public final class CustomTabList extends AbstractHudElement {
|
|||
}
|
||||
|
||||
private void renderVanillaTabStratumInternal(GuiGraphicsExtractor ctx, int guiWidth) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!Lifecycle.canRunHud()) return;
|
||||
if (mc == null || mc.getWindow() == null || ctx == null) return;
|
||||
int screenW = Math.max(1, Math.round(HudScale.virtualWidth(
|
||||
|
|
@ -170,6 +170,7 @@ public final class CustomTabList extends AbstractHudElement {
|
|||
int screenW,
|
||||
int screenH,
|
||||
boolean show) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || ctx == null || renderer == null) return;
|
||||
float panelPresence = animator.updatePanel(show);
|
||||
if (!show && panelPresence <= 0.01f && !animator.hasVisibleRows()) {
|
||||
|
|
@ -196,6 +197,7 @@ public final class CustomTabList extends AbstractHudElement {
|
|||
}
|
||||
|
||||
private void refreshSnapshot(int screenW, boolean force) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.getConnection() == null) return;
|
||||
long now = Util.getMillis();
|
||||
int listedPlayers = Math.min(mc.getConnection().getListedOnlinePlayers().size(), TabListModel.maxPlayers());
|
||||
|
|
@ -239,10 +241,12 @@ public final class CustomTabList extends AbstractHudElement {
|
|||
}
|
||||
|
||||
private boolean canRenderReplacement() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return mc != null && mc.player != null && mc.getConnection() != null;
|
||||
}
|
||||
|
||||
private boolean shouldShowNow() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.player == null || mc.getConnection() == null || mc.options == null) return false;
|
||||
if (!mc.options.keyPlayerList.isDown()) return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ public final class ScriptedCompactHudStatRenderer {
|
|||
|
||||
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 CachedUiScriptRuntime scriptRuntime = new CachedUiScriptRuntime(HudScriptLayouts.runtimeReporter());
|
||||
|
||||
|
|
@ -35,6 +34,7 @@ public final class ScriptedCompactHudStatRenderer {
|
|||
TextRenderer textRenderer,
|
||||
GuiGraphicsExtractor ctx,
|
||||
float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (model == null || !model.visible()) return false;
|
||||
if (moduleHandle.isRuntimeBlocked()) return false;
|
||||
if (mc == null || mc.getResourceManager() == null) return false;
|
||||
|
|
@ -73,6 +73,7 @@ public final class ScriptedCompactHudStatRenderer {
|
|||
}
|
||||
|
||||
private UiScriptModule ensureModule() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!moduleHandle.ensureLoaded(mc.getResourceManager())) {
|
||||
HudScriptLayouts.reportLoadError(moduleHandle);
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -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_FADE_IN = "fade_in";
|
||||
private static final String SETTING_FADE_OUT = "fade_out";
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final NumberValue<Integer> startHearts =
|
||||
num("damageTintStartHearts", SETTING_START_HP, 20, 1, 80);
|
||||
private final NumberValue<Integer> fullHearts =
|
||||
|
|
@ -71,6 +70,7 @@ public class DamageTint extends Module implements PostProcessPass {
|
|||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return isEnabled() && mc.player != null && mc.level != null;
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +86,7 @@ public class DamageTint extends Module implements PostProcessPass {
|
|||
|
||||
@Override
|
||||
public boolean render(GpuTextureView src, GpuTextureView dst, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.player == null || mc.level == null) return false;
|
||||
if (src == null || dst == null) return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -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_ALPHA = 0.88f;
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
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 RGBColorValue pearlColorValue =
|
||||
|
|
@ -191,6 +189,7 @@ public class Predictions extends Module {
|
|||
|
||||
@Override
|
||||
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null) {
|
||||
TargetManager.setPredictionTarget(null);
|
||||
return;
|
||||
|
|
@ -215,6 +214,7 @@ public class Predictions extends Module {
|
|||
|
||||
@Override
|
||||
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;
|
||||
|
||||
boolean showPlate = showTimerPlateValue.get();
|
||||
|
|
@ -238,6 +238,7 @@ public class Predictions extends Module {
|
|||
|
||||
@Override
|
||||
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 (timerPlates.isEmpty()) return;
|
||||
|
||||
|
|
@ -276,6 +277,7 @@ public class Predictions extends Module {
|
|||
}
|
||||
|
||||
private List<TrajectoryResult> simulateProjectiles(float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
ClientLevel world = mc.level;
|
||||
if (world == null) return Collections.emptyList();
|
||||
|
||||
|
|
@ -396,6 +398,7 @@ public class Predictions extends Module {
|
|||
}
|
||||
|
||||
private void drawTntTimers(Renderer2D renderer, TextRenderer textRenderer, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
ClientLevel world = mc.level;
|
||||
if (world == null) return;
|
||||
|
||||
|
|
@ -518,6 +521,7 @@ public class Predictions extends Module {
|
|||
}
|
||||
|
||||
private void renderAimTrajectoryGlow(Renderer3D renderer, float tickDelta, int baseColor) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
Player player = mc.player;
|
||||
if (player == null) return;
|
||||
|
||||
|
|
@ -574,6 +578,7 @@ public class Predictions extends Module {
|
|||
}
|
||||
|
||||
private void renderAimIndicatorWorld(Renderer3D renderer, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
Player player = mc.player;
|
||||
if (player == null) return;
|
||||
|
||||
|
|
@ -600,6 +605,7 @@ public class Predictions extends Module {
|
|||
}
|
||||
|
||||
private void updatePredictionTarget(float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!targetHudOnAimValue.get()) {
|
||||
TargetManager.setPredictionTarget(null);
|
||||
return;
|
||||
|
|
@ -710,6 +716,7 @@ public class Predictions extends Module {
|
|||
}
|
||||
|
||||
private void renderImpactEffects(Renderer3D renderer, List<TrajectoryResult> results, int baseColor, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
boolean walls = glowWallsValue.get();
|
||||
RenderPipeline linePipeline = walls
|
||||
? LoVisualRenderPipelines.WORLD_COLORED_LINES_LIQUID_BLEND
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ public final class ProjectilePuncher extends Module {
|
|||
private static final String PROJECTILE_FIREBALL = "fireball";
|
||||
private static final String PROJECTILE_SHULKER_BULLET = "shulker_bullet";
|
||||
private static final int ROTATION_PRIORITY = 10;
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final NumberValue<Double> range =
|
||||
numCommon(
|
||||
"projectilePuncherRange",
|
||||
|
|
@ -428,6 +427,7 @@ public final class ProjectilePuncher extends Module {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player == null || mc.level == null) {
|
||||
clearTargetImmediate();
|
||||
}
|
||||
|
|
@ -435,6 +435,7 @@ public final class ProjectilePuncher extends Module {
|
|||
|
||||
@EventHandler(priority = ROTATION_PRIORITY)
|
||||
public void onRotationUpdate(RotationUpdateEvent event) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled()) return;
|
||||
if (mc.player == null || mc.level == null) {
|
||||
clearTargetImmediate();
|
||||
|
|
@ -461,6 +462,7 @@ public final class ProjectilePuncher extends Module {
|
|||
}
|
||||
|
||||
private void tryAttackPost() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player == null || mc.level == null || mc.gameMode == null) return;
|
||||
if (mc.player.tickCount == lastAttackExecutionAge) {
|
||||
return;
|
||||
|
|
@ -493,6 +495,7 @@ public final class ProjectilePuncher extends Module {
|
|||
}
|
||||
|
||||
private List<Entity> findCandidates() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
double searchRange = Math.max(range.get(), wallRange.get()) + 8.0;
|
||||
List<Entity> candidates = mc.level.getEntities(
|
||||
mc.player,
|
||||
|
|
@ -506,6 +509,7 @@ public final class ProjectilePuncher extends Module {
|
|||
}
|
||||
|
||||
private boolean processTarget(Entity target, float range, float wallsRange) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
RotationWithVector rotation = findRotation(target, range, wallsRange);
|
||||
if (rotation == null) return false;
|
||||
|
||||
|
|
@ -526,6 +530,7 @@ public final class ProjectilePuncher extends Module {
|
|||
}
|
||||
|
||||
private Rotation resolveAttackRotation(Entity target) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player == null) return null;
|
||||
|
||||
Rotation rotation;
|
||||
|
|
@ -548,6 +553,7 @@ public final class ProjectilePuncher extends Module {
|
|||
}
|
||||
|
||||
private boolean passesRaycast(Entity target, float yaw, float pitch) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
RaycastMode mode = raycast.get();
|
||||
if (mode == RaycastMode.NONE) return true;
|
||||
|
||||
|
|
@ -560,10 +566,12 @@ public final class ProjectilePuncher extends Module {
|
|||
}
|
||||
|
||||
private boolean isInRange(Entity target) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return TargetingUtil.distanceToEntityBoxSq(mc.player.getEyePosition(), target) <= range.get() * range.get();
|
||||
}
|
||||
|
||||
private RotationWithVector findRotation(Entity target, float range, float wallsRange) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player == null) return null;
|
||||
|
||||
Vec3 eyes = mc.player.getEyePosition();
|
||||
|
|
@ -635,10 +643,12 @@ public final class ProjectilePuncher extends Module {
|
|||
}
|
||||
|
||||
private double predictedDistanceSq(Entity entity) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return TargetingUtil.distanceToBoxSq(mc.player.getEyePosition(), predictBox(entity));
|
||||
}
|
||||
|
||||
private boolean shouldAttack(Entity entity) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
Vec3 velocity = entity.getDeltaMovement();
|
||||
if (velocity.lengthSqr() < 1.0E-7) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -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_EATING = "attack_while_eating";
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final BooleanMapValue actions = group(
|
||||
"rubberhand_actions",
|
||||
SETTING_ACTIONS,
|
||||
|
|
@ -66,6 +65,7 @@ public class RubberHand extends Module {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled()) return;
|
||||
if (mc.player == null) return;
|
||||
|
||||
|
|
@ -78,6 +78,7 @@ public class RubberHand extends Module {
|
|||
|
||||
@EventHandler
|
||||
private void onSync(EventSync event) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.player == null || mc.gameMode == null) return;
|
||||
if (didAttackThisHold) 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) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
mc.gameMode.attack(player, target);
|
||||
player.swing(InteractionHand.MAIN_HAND);
|
||||
didAttackThisHold = true;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import dev.loki.lovisual.util.player.NetworkStatsUtil;
|
|||
public class TPSSync extends Module {
|
||||
|
||||
private static final String SETTING_START_TPS = "start_tps";
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final BooleanValue enabled =
|
||||
new BooleanValue("tpssync_enabled", true);
|
||||
private final NumberValue<Float> startTps =
|
||||
|
|
@ -32,6 +31,7 @@ public class TPSSync extends Module {
|
|||
|
||||
/** сколько серверных тиков прошло за клиентский */
|
||||
public float getServerTickDelta() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || !enabled.get()) return 1.0f;
|
||||
if (mc == null || mc.level == null) return 1.0f;
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ public class TargetESP extends Module {
|
|||
private static final Identifier CRYSTAL_BLOOM = TextureStorage.BLOOM;
|
||||
private static final float ALPHA_TICK_DT = 1.0f / 20.0f;
|
||||
private static final float ALPHA_SNAP_EPSILON = 0.02f;
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final ModeValue espMode = modeSetting(
|
||||
"targetEspMode",
|
||||
SETTING_MODE,
|
||||
|
|
@ -146,6 +145,7 @@ public class TargetESP extends Module {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null) {
|
||||
target = null;
|
||||
updateRenderTarget(null);
|
||||
|
|
@ -247,6 +247,7 @@ public class TargetESP extends Module {
|
|||
}
|
||||
|
||||
private Renderer3D.CullOptions resolveCullOptions(LivingEntity target) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (target == null || mc.player == null) {
|
||||
return Renderer3D.CullOptions.SMART_TARGET;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_IGNORED_ENTITIES = "ignored_entities";
|
||||
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 EnumValue<BedwarsColorMode> bedwarsColorMode =
|
||||
visibleWhen(enumSetting("defineTargetBedwarsColorMode", SETTING_BEDWARS_COLOR_MODE, BedwarsColorMode.RELATION_COLORS, BedwarsColorMode.values()),
|
||||
|
|
@ -142,6 +141,7 @@ public class DefineTarget extends Module {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.player == null || mc.level == null) return;
|
||||
|
||||
if (isActionPressedOnce(ACTION_TOGGLE_TARGET)) {
|
||||
|
|
@ -150,6 +150,7 @@ public class DefineTarget extends Module {
|
|||
}
|
||||
|
||||
private void handleToggle() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
HitResult hit = mc.hitResult;
|
||||
if (!(hit instanceof EntityHitResult ehr)) return;
|
||||
Entity ent = ehr.getEntity();
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ public class FakePlayer extends Module {
|
|||
private static final String SETTING_AUTO_TOTEM = "auto_totem";
|
||||
private static final float SMASH_THRESHOLD = 1.5f;
|
||||
private static final float SMASH_BASE_DAMAGE_PER_BLOCK = 1.0f;
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final BooleanValue copyInventory =
|
||||
bool("fakePlayerCopyInventory", SETTING_COPY_INVENTORY, false);
|
||||
private final BooleanValue autoTotem =
|
||||
|
|
@ -65,6 +64,7 @@ public class FakePlayer extends Module {
|
|||
private int deathTime;
|
||||
|
||||
public FakePlayer() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
AttackEntityCallback.EVENT.register((player, world, hand, target, hitResult) -> {
|
||||
if (!isEnabled()) return InteractionResult.PASS;
|
||||
if (mc.player == null || mc.level == null) return InteractionResult.PASS;
|
||||
|
|
@ -112,6 +112,7 @@ public class FakePlayer extends Module {
|
|||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || mc.player == null) return;
|
||||
|
||||
despawnFake();
|
||||
|
|
@ -168,6 +169,7 @@ public class FakePlayer extends Module {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null || mc.player == null) return;
|
||||
if (fakePlayer == null) return;
|
||||
|
||||
|
|
@ -186,6 +188,7 @@ public class FakePlayer extends Module {
|
|||
}
|
||||
|
||||
private int allocateFakeEntityId() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
int id = nextFakeEntityId--;
|
||||
while (mc.level != null && mc.level.getEntity(id) != null) {
|
||||
id = nextFakeEntityId--;
|
||||
|
|
@ -204,6 +207,7 @@ public class FakePlayer extends Module {
|
|||
}
|
||||
|
||||
private void handleAttack(Player attacker, Player target) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null) return;
|
||||
|
||||
ItemStack weapon = attacker.getMainHandItem();
|
||||
|
|
@ -270,6 +274,7 @@ public class FakePlayer extends Module {
|
|||
}
|
||||
|
||||
private void applyDamage(Player target, DamageSource source, float damage) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (damage <= 0f) return;
|
||||
target.handleDamageEvent(source);
|
||||
|
||||
|
|
@ -335,6 +340,7 @@ public class FakePlayer extends Module {
|
|||
}
|
||||
|
||||
private Holder<Enchantment> entryOf(ResourceKey<Enchantment> key) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (key == null || mc.level == null) return null;
|
||||
Registry<Enchantment> registry =
|
||||
mc.level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT);
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
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.features.gui.clickgui.settings.TextListSetting;
|
||||
import dev.loki.lovisual.features.module.Module;
|
||||
|
|
@ -24,14 +26,45 @@ import java.util.Set;
|
|||
)
|
||||
public class NoSound extends Module {
|
||||
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 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"));
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
MUTE("Mute"),
|
||||
REDUCE("Reduce");
|
||||
|
||||
public final String id;
|
||||
|
||||
Mode(String id) {
|
||||
this.id = 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;
|
||||
String normalized = id.toLowerCase(Locale.ROOT);
|
||||
for (String mutedId : soundIds.get()) {
|
||||
|
|
@ -39,4 +72,10 @@ public class NoSound extends Module {
|
|||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -35,13 +35,12 @@ public class AutoTool extends Module {
|
|||
private final NumberValue<Integer> restoreDelayMs =
|
||||
num("autoToolRestoreDelayMs", SETTING_RESTORE_DELAY, 300, 0, 1000);
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
private int originalSlot = -1;
|
||||
private long lastBreakMs = 0L;
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc != null && mc.player != null && originalSlot >= 0) {
|
||||
InventorySwap.INSTANCE.selectHotbar(originalSlot);
|
||||
}
|
||||
|
|
@ -51,6 +50,7 @@ public class AutoTool extends Module {
|
|||
|
||||
@EventHandler
|
||||
private void onGameTick(GameTickEvent event) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.player == null || mc.level == null || mc.options == null || mc.gameMode == null) {
|
||||
resetState();
|
||||
return;
|
||||
|
|
@ -83,6 +83,7 @@ public class AutoTool extends Module {
|
|||
}
|
||||
|
||||
private int findBestHotbarTool(BlockPos pos) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player == null || mc.level == null) return -1;
|
||||
BlockState state = mc.level.getBlockState(pos);
|
||||
if (state.isAir()) return -1;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import dev.loki.lovisual.util.player.inventory.InventorySwap;
|
|||
public class ClickPearl extends Module {
|
||||
private static final String ACTION_CLICKPEARL = "clickpearl";
|
||||
|
||||
private final Minecraft client = Minecraft.getInstance();
|
||||
private ItemStack previousHeld = ItemStack.EMPTY;
|
||||
|
||||
public ClickPearl() {
|
||||
|
|
@ -43,6 +42,7 @@ public class ClickPearl extends Module {
|
|||
@Override
|
||||
public void onTick() {
|
||||
if (!isEnabled()) return;
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
if (client.player == null || client.gameMode == null) return;
|
||||
|
||||
if (isActionPressedOnce(ACTION_CLICKPEARL)) {
|
||||
|
|
@ -54,6 +54,7 @@ public class ClickPearl extends Module {
|
|||
// ТВОЯ ЛОГИКА — НЕ ТРОНУТА
|
||||
// ===========================
|
||||
private void tryThrowPearl() {
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
LocalPlayer player = client.player;
|
||||
MultiPlayerGameMode manager = client.gameMode;
|
||||
if (player == null || manager == null) return;
|
||||
|
|
|
|||
|
|
@ -99,8 +99,6 @@ public final class NoDelay extends Module {
|
|||
visibleWhen(num("noDelayFastBreakProgress", SETTING_FAST_BREAK_PROGRESS, 0.5f, 0.1f, 1.0f),
|
||||
() -> types.get(TYPE_FAST_BREAK));
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
private long pressStartThrow = 0L;
|
||||
private long lastThrow = 0L;
|
||||
private long pressStartInteract = 0L;
|
||||
|
|
@ -122,6 +120,7 @@ public final class NoDelay extends Module {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc == null || mc.player == null) {
|
||||
resetBreakBoost();
|
||||
return;
|
||||
|
|
@ -162,6 +161,7 @@ public final class NoDelay extends Module {
|
|||
}
|
||||
|
||||
public boolean handleFastUse() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled()) 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;
|
||||
|
|
@ -214,6 +214,7 @@ public final class NoDelay extends Module {
|
|||
}
|
||||
|
||||
private void applyBlockPlaceCooldown() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player == null || !mc.options.keyUse.isDown()) return;
|
||||
if (!isBlockPlaceCandidate(mc.player, mc.hitResult)) return;
|
||||
|
||||
|
|
@ -233,6 +234,7 @@ public final class NoDelay extends Module {
|
|||
}
|
||||
|
||||
private void applyFastBreakBoost() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.gameMode == null || mc.getConnection() == null) {
|
||||
resetBreakBoost();
|
||||
return;
|
||||
|
|
@ -282,6 +284,7 @@ public final class NoDelay extends Module {
|
|||
}
|
||||
|
||||
private Direction resolveBreakingDirection(BlockPos breakingPos) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
HitResult hit = mc.hitResult;
|
||||
if (hit instanceof BlockHitResult blockHit && breakingPos.equals(blockHit.getBlockPos())) {
|
||||
return blockHit.getDirection();
|
||||
|
|
@ -319,6 +322,7 @@ public final class NoDelay extends Module {
|
|||
}
|
||||
|
||||
private InteractionResult performFastUse(LocalPlayer player, UseContext ctx) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (ctx.target == UseTarget.THROWABLE) {
|
||||
return mc.gameMode.useItem(player, ctx.hand);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -35,8 +35,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
)
|
||||
public final class BlockESP extends Module {
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
private final ItemIdSetValue blockList =
|
||||
itemList("block_list", TextListSetting.PickerMode.BLOCKS);
|
||||
|
||||
|
|
@ -85,6 +83,7 @@ public final class BlockESP extends Module {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || mc.player == null) return;
|
||||
|
||||
if (scanTimer.passedS(scanInterval.get())) {
|
||||
|
|
@ -100,6 +99,7 @@ public final class BlockESP extends Module {
|
|||
|
||||
@Override
|
||||
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null || mc.player == null) return;
|
||||
if (blockList.get().isEmpty()) return;
|
||||
|
||||
|
|
@ -115,6 +115,7 @@ public final class BlockESP extends Module {
|
|||
}
|
||||
|
||||
private void requestScan() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || mc.player == null) return;
|
||||
if (!scanning.get()) return;
|
||||
if (executor.isShutdown()) return;
|
||||
|
|
@ -130,6 +131,7 @@ public final class BlockESP extends Module {
|
|||
}
|
||||
|
||||
private void scan() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || mc.player == null) return;
|
||||
|
||||
List<BlockPos> result = new ArrayList<>();
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ public class BlockHighlight extends Module {
|
|||
|
||||
private static final double RAINBOW_SPATIAL_SCALE = 12.0;
|
||||
private static final double GEOMETRY_EXPAND = 0.0025;
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final BooleanValue outlineEnabled =
|
||||
bool(
|
||||
"outline_enabled",
|
||||
|
|
@ -240,6 +239,7 @@ public class BlockHighlight extends Module {
|
|||
|
||||
@Override
|
||||
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null || mc.player == null) return;
|
||||
|
||||
if (outlineEnabled.get()) {
|
||||
|
|
@ -252,6 +252,7 @@ public class BlockHighlight extends Module {
|
|||
}
|
||||
|
||||
private void renderTargetOutline(Renderer3D renderer) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
HitResult hit = mc.hitResult;
|
||||
if (!(hit instanceof BlockHitResult blockHit)) return;
|
||||
if (hit.getType() == HitResult.Type.MISS) return;
|
||||
|
|
@ -417,6 +418,7 @@ public class BlockHighlight extends Module {
|
|||
}
|
||||
|
||||
private void renderBreakingHighlight(Renderer3D renderer) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (breakingSelf.get()) {
|
||||
renderSelfBreaking(renderer);
|
||||
}
|
||||
|
|
@ -441,6 +443,7 @@ public class BlockHighlight extends Module {
|
|||
}
|
||||
|
||||
private void renderSelfBreaking(Renderer3D renderer) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
MultiPlayerGameMode interactionManager = mc.gameMode;
|
||||
if (interactionManager == null || !interactionManager.isDestroying()) 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) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || pos == null) return;
|
||||
|
||||
BlockState state = mc.level.getBlockState(pos);
|
||||
|
|
|
|||
|
|
@ -123,7 +123,6 @@ public class Chams extends Module {
|
|||
private static final float NOISE_CONTRAST = 1.15f;
|
||||
private static final float NOISE_BREATHE = 0.55f;
|
||||
private static final float NOISE_DENSITY = 1.0f;
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final ModeValue mode =
|
||||
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() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return isEnabled() && mc.player != null && mc.level != null;
|
||||
}
|
||||
|
||||
|
|
@ -307,6 +307,7 @@ public class Chams extends Module {
|
|||
}
|
||||
|
||||
private FeatureRenderDispatcher getStandaloneHandFeatureDispatcher() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (irisHandFeatureDispatcher != null) {
|
||||
return irisHandFeatureDispatcher;
|
||||
}
|
||||
|
|
@ -342,6 +343,7 @@ public class Chams extends Module {
|
|||
}
|
||||
|
||||
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 (mc.gameMode == null) return false;
|
||||
if (!hands.get() || !shouldRenderHand()) return false;
|
||||
|
|
@ -385,6 +387,7 @@ public class Chams extends Module {
|
|||
}
|
||||
|
||||
public boolean renderPreparedHandScene(FeatureRenderDispatcher dispatcher, SubmitNodeStorage storage) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.player == null || mc.level == null) return false;
|
||||
if (dispatcher == null || storage == null) return false;
|
||||
if (!hands.get() || !shouldRenderHand()) return false;
|
||||
|
|
@ -433,6 +436,7 @@ public class Chams extends Module {
|
|||
CameraRenderState cameraRenderState,
|
||||
Matrix4fc positionMatrix,
|
||||
float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!IrisRuntime.isShaderpackRendererActive() || !isEnabled() || mc.player == null || mc.level == null) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -488,6 +492,7 @@ public class Chams extends Module {
|
|||
FeatureRenderDispatcher dispatcher,
|
||||
float tickDelta,
|
||||
boolean solidPhase) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
IrisHandRendererAccessor irisHand = (IrisHandRendererAccessor) HandRenderer.INSTANCE;
|
||||
irisHand.lovisual$setRenderingSolid(solidPhase);
|
||||
|
||||
|
|
@ -523,6 +528,7 @@ public class Chams extends Module {
|
|||
}
|
||||
|
||||
private void ensureBuffers() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
int w = mc.getWindow().getWidth();
|
||||
int h = mc.getWindow().getHeight();
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
|
@ -612,6 +618,7 @@ public class Chams extends Module {
|
|||
}
|
||||
|
||||
private void ensureDualBuffers() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
int w = mc.getWindow().getWidth();
|
||||
int h = mc.getWindow().getHeight();
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
|
@ -665,6 +672,7 @@ public class Chams extends Module {
|
|||
}
|
||||
|
||||
private boolean renderHands(GpuTextureView src, GpuTextureView dst, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!maskReady) return false;
|
||||
if (handMask == null) {
|
||||
maskReady = false;
|
||||
|
|
@ -792,6 +800,7 @@ public class Chams extends Module {
|
|||
}
|
||||
|
||||
private void renderChamsEffect(GpuTextureView src, GpuTextureView dst, GpuTextureView maskView, String chamsMode, float rawTime) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
int w = mc.getWindow().getWidth();
|
||||
int h = mc.getWindow().getHeight();
|
||||
float time = rawTime;
|
||||
|
|
@ -862,6 +871,7 @@ public class Chams extends Module {
|
|||
}
|
||||
|
||||
private void renderNoise(GpuTextureView src, GpuTextureView dst, GpuTextureView maskView, float rawTime) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
int fillRgb = materialFillRgb();
|
||||
int glowRgb = glowRgb(fillRgb);
|
||||
|
||||
|
|
@ -941,6 +951,7 @@ public class Chams extends Module {
|
|||
private final class HandsPass implements PostProcessPass {
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return Chams.this.isEnabled()
|
||||
&& mc.player != null
|
||||
&& mc.level != null
|
||||
|
|
@ -955,6 +966,7 @@ public class Chams extends Module {
|
|||
|
||||
@Override
|
||||
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 (!hands.get() || !mc.options.getCameraType().isFirstPerson()) return false;
|
||||
return Chams.this.renderHands(src, dst, tickDelta);
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@ import net.minecraft.client.Minecraft;
|
|||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
/**
|
||||
* Classic conical Chinese hat (rice hat / coolie hat): a wide flat brim annulus
|
||||
* topped by a cone dome. Geometry lives in {@link ChinaHatModel}, colors in
|
||||
* Classic conical Chinese hat (rice hat / coolie hat): a wide shallow cone —
|
||||
* 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.
|
||||
*/
|
||||
@ModuleInfo(
|
||||
|
|
@ -36,8 +37,6 @@ import net.minecraft.world.entity.player.Player;
|
|||
)
|
||||
public final class ChinaHat extends Module {
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
private final EnumValue<RenderTarget> renderTarget =
|
||||
enumMode(
|
||||
"render_target",
|
||||
|
|
@ -115,30 +114,16 @@ public final class ChinaHat extends Module {
|
|||
private final NumberValue<Float> brimRadius =
|
||||
num(
|
||||
"brim_radius",
|
||||
0.45f,
|
||||
0.5f,
|
||||
0.2f,
|
||||
0.7f
|
||||
0.9f
|
||||
);
|
||||
private final NumberValue<Float> domeRadius =
|
||||
private final NumberValue<Float> coneHeight =
|
||||
num(
|
||||
"dome_radius",
|
||||
0.16f,
|
||||
0.06f,
|
||||
0.35f
|
||||
);
|
||||
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
|
||||
"cone_height",
|
||||
0.22f,
|
||||
0.05f,
|
||||
0.6f
|
||||
);
|
||||
private final NumberValue<Float> headTopOffset =
|
||||
num(
|
||||
|
|
@ -169,6 +154,7 @@ public final class ChinaHat extends Module {
|
|||
|
||||
@Override
|
||||
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null || mc.player == null) return;
|
||||
|
||||
MeshBuilder tris = renderer.batch(
|
||||
|
|
@ -245,14 +231,11 @@ public final class ChinaHat extends Module {
|
|||
anchor,
|
||||
segments.get(),
|
||||
brimRadius.get() * size,
|
||||
domeRadius.get() * size,
|
||||
domeHeight.get() * size,
|
||||
brimThickness.get() * size,
|
||||
coneHeight.get() * size,
|
||||
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.rimColorAt(palette, mode, speed, index, alphaMul),
|
||||
1.0f
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,10 +13,14 @@ import net.minecraft.util.Mth;
|
|||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
/**
|
||||
* Geometry for the classic ChinaHat: a wide flat brim (annulus) plus a conical dome.
|
||||
* Pure mesh assembly, no Minecraft state. Per-vertex colors come from the caller.
|
||||
* Classic coolie/rice hat: a single smooth cone from the apex resting on the
|
||||
* 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 {
|
||||
|
||||
private ChinaHatModel() {
|
||||
}
|
||||
|
||||
|
|
@ -24,76 +28,59 @@ final class ChinaHatModel {
|
|||
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,
|
||||
MeshBuilder lines,
|
||||
HeadAnchor anchor,
|
||||
int segments,
|
||||
float brimRadius,
|
||||
float domeRadius,
|
||||
float domeHeight,
|
||||
float brimThickness,
|
||||
float coneHeight,
|
||||
float yawDeg,
|
||||
float pitchDeg,
|
||||
float rollDeg,
|
||||
Shade domeShade,
|
||||
Shade brimShade,
|
||||
Shade coneShade,
|
||||
Shade rimShade,
|
||||
float alphaMul) {
|
||||
if (tris == null || lines == null || segments < 3) return;
|
||||
if (tris == null || segments < 3) return;
|
||||
|
||||
float outerY = brimThickness;
|
||||
float innerY = 0.0f;
|
||||
Vec3[] outerTop = new Vec3[segments];
|
||||
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);
|
||||
Layout layout = layout(brimRadius, coneHeight, segments, yawDeg, pitchDeg, rollDeg, anchor);
|
||||
Vec3 apex = layout.apex();
|
||||
Vec3[] rim = layout.rim();
|
||||
|
||||
for (int i = 0; i < segments; i++) {
|
||||
int next = (i + 1) % segments;
|
||||
|
||||
int domeA = domeShade.color(i, 1.06f, alphaMul);
|
||||
int domeB = domeShade.color(next, 1.02f, alphaMul);
|
||||
triangle(tris, tip, innerTop[i], domeA, innerTop[next], domeB);
|
||||
int upperA = coneShade.color(i, 1.06f, alphaMul);
|
||||
int upperB = coneShade.color(next, 1.02f, alphaMul);
|
||||
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);
|
||||
int brimTopB = brimShade.color(next, 0.90f, alphaMul);
|
||||
quad(tris,
|
||||
innerTop[i], brimTopA,
|
||||
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);
|
||||
if (lines == null) continue;
|
||||
int rimA = rimShade.color(i, 1.0f, alphaMul);
|
||||
int rimB = rimShade.color(next, 1.0f, alphaMul);
|
||||
lineGradient(lines, rim[i], rim[next], rimA, rimB);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,19 +92,6 @@ final class ChinaHatModel {
|
|||
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) {
|
||||
mesh.ensureLineCapacity();
|
||||
int i1 = putVertex(mesh, a, cA);
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ import java.util.List;
|
|||
)
|
||||
public final class CosmeticDisplay extends Module {
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
{
|
||||
action("Next Cosmetic", "NONE");
|
||||
action("Prev Cosmetic", "NONE");
|
||||
|
|
@ -68,6 +66,7 @@ public final class CosmeticDisplay extends Module {
|
|||
|
||||
@Override
|
||||
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null || mc.player == null) return;
|
||||
|
||||
CosmeticRenderer.renderOnPlayer(renderer, mc.player, CosmeticType.MODEL, tickDelta);
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ public class Crosshair extends Module {
|
|||
private static final float ORBIZ_PROGRESS_SMOOTH_SPEED = 22.0f;
|
||||
private static final float COOLDOWN_LEAD = 0.06f;
|
||||
private static final float RIPTIDE_CROSS_LENGTH_FACTOR = 0.65f;
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final EnumValue<CrosshairType> crosshairType =
|
||||
enumSetting("crosshairType", "type", CrosshairType.CLASSIC);
|
||||
private final EnumValue<OrbizMotionMode> orbizMotion =
|
||||
|
|
@ -241,6 +240,7 @@ public class Crosshair extends Module {
|
|||
}
|
||||
|
||||
public boolean shouldHideVanilla() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return isEnabled()
|
||||
&& mc.player != null
|
||||
&& mc.level != null
|
||||
|
|
@ -263,6 +263,7 @@ public class Crosshair extends Module {
|
|||
|
||||
@Override
|
||||
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 (!mc.options.getCameraType().isFirstPerson()) return;
|
||||
|
||||
|
|
@ -336,6 +337,7 @@ public class Crosshair extends Module {
|
|||
}
|
||||
|
||||
private float getAttackCooldownProgress() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player instanceof IPlayerAttackCooldown cooldown) {
|
||||
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) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
boolean danger = shouldShowRiptideBlocked(mc.player) || shouldShowCooldownCross(mc.player);
|
||||
float progress = danger ? 1.0f : resolveOrbizProgress();
|
||||
float targetProgress = Mth.clamp(progress * orbizCooldownMultiplier.get().floatValue(), 0.0f, 1.0f);
|
||||
|
|
@ -414,6 +417,7 @@ public class Crosshair extends Module {
|
|||
}
|
||||
|
||||
private float resolveOrbizProgress() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
float charge = getChargeProgress(mc.player);
|
||||
if (charge >= 0.0f) {
|
||||
return Mth.clamp(charge, 0.0f, 1.0f);
|
||||
|
|
@ -422,6 +426,7 @@ public class Crosshair extends Module {
|
|||
}
|
||||
|
||||
private void updateOrbizMotion() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player == null) {
|
||||
orbizOffsetX = AnimationUtility.fast(orbizOffsetX, 0.0f, 10.0f);
|
||||
orbizOffsetY = AnimationUtility.fast(orbizOffsetY, 0.0f, 10.0f);
|
||||
|
|
@ -490,6 +495,7 @@ public class Crosshair extends Module {
|
|||
}
|
||||
|
||||
private int resolveCrosshairTargetColor() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!highlightTarget.get()) return DEFAULT_COLOR;
|
||||
HitResult hit = mc.hitResult;
|
||||
if (!(hit instanceof EntityHitResult ehr)) return DEFAULT_COLOR;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ public class DropESP extends Module {
|
|||
private static final double MATTE_ICON_SIZE = 14.0;
|
||||
private static final double MATTE_ICON_GAP = 4.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 BooleanValue limitCommonDistanceValue = bool("dropEspLimitCommonDistance", SETTING_LIMIT_COMMON_DISTANCE, true);
|
||||
private final NumberValue<Integer> commonMaxDistanceValue =
|
||||
|
|
@ -98,6 +97,7 @@ public class DropESP extends Module {
|
|||
|
||||
@Override
|
||||
public void onRenderHudEngine(Renderer2D renderer, TextRenderer textRenderer, GuiGraphicsExtractor ctx, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null || mc.player == null) return;
|
||||
|
||||
boolean matteMode = isMatteMode();
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ public class ESP extends Module {
|
|||
static final double SHADER_MAX_DISTANCE = 128.0;
|
||||
static final float BOX_3D_GRADIENT_SPEED = 0.00115f;
|
||||
|
||||
final Minecraft mc = Minecraft.getInstance();
|
||||
final BooleanValue renderEntities =
|
||||
bool("espRenderEntities", "render_entities", false);
|
||||
final RGBColorValue entityColor =
|
||||
|
|
@ -185,6 +184,7 @@ public class ESP extends Module {
|
|||
|
||||
@Override
|
||||
public void onRenderHudEngine(Renderer2D renderer, TextRenderer textRenderer, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null || mc.player == null) return;
|
||||
|
||||
Vec3 camPos = mc.gameRenderer.mainCamera().position();
|
||||
|
|
@ -249,6 +249,7 @@ public class ESP extends Module {
|
|||
private final class ShaderEspPass implements PostProcessPass {
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return ESP.this.isEnabled()
|
||||
&& ESP.this.isShaderBox()
|
||||
&& mc.player != null
|
||||
|
|
|
|||
|
|
@ -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.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.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
|
@ -24,9 +25,10 @@ final class Esp3DRenderer {
|
|||
}
|
||||
|
||||
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);
|
||||
if (entries.isEmpty()) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package dev.loki.lovisual.features.module.modules.visuals;
|
||||
|
||||
import dev.loki.lovisual.features.relations.CategoryService;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
|
|
@ -45,9 +46,10 @@ final class EspEntityCollector {
|
|||
}
|
||||
|
||||
static List<EspModel.RenderEntry> collectPlayerEntries(ESP esp, Vec3 camPos) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
List<EspModel.RenderEntry> entries = new ArrayList<>();
|
||||
|
||||
for (Player player : esp.mc.level.players()) {
|
||||
for (Player player : mc.level.players()) {
|
||||
if (!shouldRenderPlayer(esp, player)) continue;
|
||||
int color = resolvePlayerColor(player);
|
||||
double distSq = player.distanceToSqr(camPos);
|
||||
|
|
@ -58,11 +60,12 @@ final class EspEntityCollector {
|
|||
}
|
||||
|
||||
static List<EspModel.RenderEntry> collectSearchEntityEntries(ESP esp, Vec3 camPos) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
List<EspModel.RenderEntry> entries = new ArrayList<>();
|
||||
if (!esp.renderEntities.get()) return entries;
|
||||
|
||||
int color = esp.entityColor.getArgb();
|
||||
for (Entity entity : esp.mc.level.entitiesForRendering()) {
|
||||
for (Entity entity : mc.level.entitiesForRendering()) {
|
||||
if (!shouldRenderEntity(esp, entity)) continue;
|
||||
double distSq = entity.distanceToSqr(camPos);
|
||||
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) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
List<EspModel.RenderEntry> entries = new ArrayList<>();
|
||||
Set<String> selected = esp.shaderChamsEntities.get();
|
||||
if (selected == null || selected.isEmpty()) {
|
||||
|
|
@ -98,7 +102,7 @@ final class EspEntityCollector {
|
|||
}
|
||||
|
||||
int color = esp.shaderChamsEntityColor.getArgb();
|
||||
for (Entity entity : esp.mc.level.entitiesForRendering()) {
|
||||
for (Entity entity : mc.level.entitiesForRendering()) {
|
||||
if (!shouldRenderShaderChamsEntity(esp, entity, selected)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -112,9 +116,10 @@ final class EspEntityCollector {
|
|||
}
|
||||
|
||||
static boolean shouldRenderPlayer(ESP esp, Player p) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (p == null) return false;
|
||||
if (p == esp.mc.player) {
|
||||
if (esp.mc.options.getCameraType().isFirstPerson()) return false;
|
||||
if (p == mc.player) {
|
||||
if (mc.options.getCameraType().isFirstPerson()) return false;
|
||||
return esp.renderSelf.get();
|
||||
}
|
||||
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) {
|
||||
if (esp.mc.level == null) return false;
|
||||
Vec3 start = esp.mc.gameRenderer.mainCamera().position();
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null) return false;
|
||||
Vec3 start = mc.gameRenderer.mainCamera().position();
|
||||
Vec3 end = p.getBoundingBox().getCenter();
|
||||
HitResult result = esp.mc.level.clip(new ClipContext(
|
||||
HitResult result = mc.level.clip(new ClipContext(
|
||||
start,
|
||||
end,
|
||||
ClipContext.Block.COLLIDER,
|
||||
ClipContext.Fluid.NONE,
|
||||
esp.mc.player
|
||||
mc.player
|
||||
));
|
||||
return result.getType() == HitResult.Type.MISS;
|
||||
}
|
||||
|
||||
static boolean shouldRenderEntity(ESP esp, Entity e) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (e == null) 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());
|
||||
if (id == null) return false;
|
||||
return esp.searchEntities.get().contains(id.toString());
|
||||
}
|
||||
|
||||
static boolean shouldRenderShaderChamsEntity(ESP esp, Entity entity, Set<String> selected) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (entity == null) 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());
|
||||
if (id == null) return false;
|
||||
|
|
|
|||
|
|
@ -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.MsaaWorldTarget;
|
||||
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.LevelRenderer;
|
||||
import net.minecraft.client.renderer.ProjectionMatrixBuffer;
|
||||
|
|
@ -46,11 +47,12 @@ final class EspShaderMask {
|
|||
}
|
||||
|
||||
static boolean renderEntityMask(ESP esp, List<EspModel.RenderEntry> entries, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (entries.isEmpty() || esp.shaderMask == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LevelRenderer worldRenderer = esp.mc.levelRenderer;
|
||||
LevelRenderer worldRenderer = mc.levelRenderer;
|
||||
if (worldRenderer == null) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -103,8 +105,8 @@ final class EspShaderMask {
|
|||
Matrix4f capturedPosition = LoVisualWorldMatrices.positionMatrix();
|
||||
if (capturedPosition != null) {
|
||||
modelView.mul(capturedPosition);
|
||||
} else if (esp.mc.gameRenderer != null && esp.mc.gameRenderer.mainCamera() != null) {
|
||||
modelView.mul(new Matrix4f().rotation(esp.mc.gameRenderer.mainCamera().rotation().conjugate(new Quaternionf())));
|
||||
} else if (mc.gameRenderer != null && mc.gameRenderer.mainCamera() != null) {
|
||||
modelView.mul(new Matrix4f().rotation(mc.gameRenderer.mainCamera().rotation().conjugate(new Quaternionf())));
|
||||
}
|
||||
|
||||
PoseStack matrices = new PoseStack();
|
||||
|
|
@ -122,7 +124,7 @@ final class EspShaderMask {
|
|||
|
||||
esp.shaderEspCommandQueue.getSubmitsPerOrder().clear();
|
||||
|
||||
EntityRenderDispatcher entityRenderManager = esp.mc.getEntityRenderDispatcher();
|
||||
EntityRenderDispatcher entityRenderManager = mc.getEntityRenderDispatcher();
|
||||
for (EspModel.RenderEntry entry : entries) {
|
||||
Entity entity = entry.entity();
|
||||
EntityCullingCompat.forceVisibleForShaderEsp(entity);
|
||||
|
|
|
|||
|
|
@ -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.postprocess.PostProcessManager;
|
||||
import dev.loki.lovisual.render.iris.IrisLoVisualFrameHooks;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.RenderBuffers;
|
||||
import net.minecraft.client.renderer.feature.FeatureRenderDispatcher;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
|
@ -26,14 +27,15 @@ final class EspShaderRenderer {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!esp.shaderGlow.get() && !esp.shaderOutline.get() && !esp.shaderFill.get()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Vec3 camPos = esp.mc.gameRenderer.mainCamera().position();
|
||||
Vec3 camPos = mc.gameRenderer.mainCamera().position();
|
||||
List<EspModel.RenderEntry> entries = EspEntityCollector.collectShaderEntries(esp, camPos);
|
||||
if (entries.isEmpty()) {
|
||||
return false;
|
||||
|
|
@ -50,14 +52,15 @@ final class EspShaderRenderer {
|
|||
|
||||
PostProcessManager.copy(src, dst);
|
||||
|
||||
int width = esp.mc.getWindow().getWidth();
|
||||
int height = esp.mc.getWindow().getHeight();
|
||||
int width = mc.getWindow().getWidth();
|
||||
int height = mc.getWindow().getHeight();
|
||||
return EspShaderColor.renderColorPass(esp, dst, width, height);
|
||||
}
|
||||
|
||||
static void ensureShaderBuffers(ESP esp) {
|
||||
int width = esp.mc.getWindow().getWidth();
|
||||
int height = esp.mc.getWindow().getHeight();
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
int width = mc.getWindow().getWidth();
|
||||
int height = mc.getWindow().getHeight();
|
||||
if (width <= 0 || height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -96,10 +99,11 @@ final class EspShaderRenderer {
|
|||
}
|
||||
|
||||
static FeatureRenderDispatcher getShaderEspRenderDispatcher(ESP esp) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (IrisLoVisualFrameHooks.isRenderingAfterIrisFinalization()) {
|
||||
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();
|
||||
if (dispatcher != null) {
|
||||
return dispatcher;
|
||||
|
|
@ -109,19 +113,20 @@ final class EspShaderRenderer {
|
|||
}
|
||||
|
||||
static FeatureRenderDispatcher getStandaloneShaderEspRenderDispatcher(ESP esp) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (esp.shaderEspRenderDispatcher != null) {
|
||||
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;
|
||||
}
|
||||
esp.shaderEspRenderBuffers = new RenderBuffers(1);
|
||||
esp.shaderEspRenderDispatcher = new FeatureRenderDispatcher(
|
||||
esp.shaderEspRenderBuffers,
|
||||
esp.mc.getModelManager(),
|
||||
esp.mc.getAtlasManager(),
|
||||
esp.mc.font,
|
||||
esp.mc.gameRenderer.gameRenderState()
|
||||
mc.getModelManager(),
|
||||
mc.getAtlasManager(),
|
||||
mc.font,
|
||||
mc.gameRenderer.gameRenderState()
|
||||
);
|
||||
return esp.shaderEspRenderDispatcher;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@ public class HitEffect extends Module {
|
|||
private static final float HIT_PARTICLE_DRAG = 0.94f;
|
||||
private static final int HIT_PARTICLE_MAX = 320;
|
||||
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 EnumValue<EffectMode> effectMode =
|
||||
enumSetting("hitBubblesEffectMode", SETTING_EFFECT_MODE, EffectMode.BUBBLES, EffectMode.values());
|
||||
|
|
@ -277,6 +276,7 @@ public class HitEffect extends Module {
|
|||
}
|
||||
|
||||
public void handleHit(Entity target) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.player == null || mc.level == null) return;
|
||||
if (!(target instanceof LivingEntity)) return;
|
||||
if (onlyPlayers.get() && !(target instanceof Player)) return;
|
||||
|
|
@ -312,6 +312,7 @@ public class HitEffect extends Module {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.player == null || mc.level == null) return;
|
||||
|
||||
if (effectMode.get() != EffectMode.BUBBLES || !starsEnabled.get()) {
|
||||
|
|
@ -327,6 +328,7 @@ public class HitEffect extends Module {
|
|||
|
||||
@Override
|
||||
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.player == null || mc.level == null) return;
|
||||
|
||||
if (hitParticlesEnabled.get() && !hitParticles.isEmpty()) {
|
||||
|
|
@ -405,6 +407,7 @@ public class HitEffect extends Module {
|
|||
}
|
||||
|
||||
private void addWave(BlockPos pos) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level != null) {
|
||||
waves.add(new WaveEffect(pos, GameClock.millis()));
|
||||
}
|
||||
|
|
@ -457,6 +460,7 @@ public class HitEffect extends Module {
|
|||
}
|
||||
|
||||
private int reserveHitParticleBudget(int requested) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (requested <= 0 || mc.player == null) {
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -599,6 +603,7 @@ public class HitEffect extends Module {
|
|||
}
|
||||
|
||||
private Vec3 getRtxPoint(Entity target, float yaw, float pitch, double distance) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player == null) return null;
|
||||
|
||||
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) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null) return;
|
||||
if (renderer == null) return;
|
||||
|
||||
|
|
@ -751,6 +757,7 @@ public class HitEffect extends Module {
|
|||
}
|
||||
|
||||
private BlockPos findSurface(BlockPos pos) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
for (int y = 2; y >= -4; y--) {
|
||||
BlockPos p = pos.above(y);
|
||||
if (!mc.level.getBlockState(p).isAir() && mc.level.getBlockState(p.above()).isAir()) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -52,8 +52,6 @@ public final class HoleESP extends Module {
|
|||
private static final int RESCAN_TICKS = 10;
|
||||
private static final int BASE_FILL_ALPHA = 60;
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
private final NumberValue<Integer> rangeXZ =
|
||||
num("holeesp_range_xz", "range_xz", 10, 1, 128);
|
||||
private final NumberValue<Integer> rangeY =
|
||||
|
|
@ -80,6 +78,7 @@ public final class HoleESP extends Module {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null || mc.player == null) {
|
||||
holes = List.of();
|
||||
return;
|
||||
|
|
@ -97,6 +96,7 @@ public final class HoleESP extends Module {
|
|||
|
||||
@Override
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -192,6 +192,7 @@ public final class HoleESP extends Module {
|
|||
}
|
||||
|
||||
private int colorForDistance(AABB box, int argb, int alpha) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
Vec3 center = box.getCenter();
|
||||
double dx = center.x - mc.player.getX();
|
||||
double dz = center.z - mc.player.getZ();
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ import java.util.List;
|
|||
category = ModuleCategory.VISUALS
|
||||
)
|
||||
public class JumpCircles extends Module {
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final EnumValue<CircleMode> mode =
|
||||
enumSetting("jumpCirclesMode", "mode", CircleMode.DEFAULT);
|
||||
private final BooleanValue easeOut = bool("jumpCirclesEaseOut", "ease_out", true);
|
||||
|
|
@ -85,6 +84,7 @@ public class JumpCircles extends Module {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null || mc.player == null) return;
|
||||
|
||||
boolean selfOnly = onlySelf.get();
|
||||
|
|
@ -118,6 +118,7 @@ public class JumpCircles extends Module {
|
|||
|
||||
@Override
|
||||
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null || mc.player == null || circles.isEmpty()) return;
|
||||
|
||||
boolean useDepth = depthTest.get();
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ public class KillEffect extends Module {
|
|||
private static final Identifier ORTHODOX_SOUND =
|
||||
Identifier.fromNamespaceAndPath("lovisual", "sounds/misc/orthodox.wav");
|
||||
private static final long EMBER_GROUND_EXTRA_MS = 0;
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final Random random = new Random();
|
||||
private final ModeValue mode =
|
||||
modeSetting("killEffectMode", SETTING_MODE, "Orthodox", "Orthodox", "FallingLava", "EmbersArc", "Lightning");
|
||||
|
|
@ -149,6 +148,7 @@ public class KillEffect extends Module {
|
|||
|
||||
@Override
|
||||
public void onTick() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null || mc.player == null) return;
|
||||
|
||||
long now = GameClock.millis();
|
||||
|
|
@ -213,6 +213,7 @@ public class KillEffect extends Module {
|
|||
|
||||
@Override
|
||||
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!isEnabled() || mc.level == null || mc.player == null) return;
|
||||
|
||||
if ("Orthodox".equals(mode.get())) {
|
||||
|
|
@ -348,6 +349,7 @@ public class KillEffect extends Module {
|
|||
}
|
||||
|
||||
private void spawnFallingLava(LivingEntity entity) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null) return;
|
||||
int height = (int) (entity.getBbHeight() * 10);
|
||||
int width = (int) (entity.getBbWidth() * 10);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -34,8 +34,6 @@ import java.util.concurrent.ThreadLocalRandom;
|
|||
)
|
||||
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> lightningWidth = num("lightningWidth", "width", 1.0f, 0.1f, 3.0f);
|
||||
private final BooleanValue hurtColor = bool("hurtColor", "hurt_color", true);
|
||||
|
|
@ -58,6 +56,7 @@ public class LightningTargetESP extends Module {
|
|||
|
||||
@Override
|
||||
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player == null || mc.level == null) return;
|
||||
|
||||
LivingEntity currentTarget = TargetManager.getTarget();
|
||||
|
|
@ -87,6 +86,7 @@ public class LightningTargetESP extends Module {
|
|||
}
|
||||
|
||||
private void renderLightning(Renderer3D renderer, float partialTicks, boolean hasActiveTarget, LivingEntity activeTarget) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (lastTargetPos == null) return;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
|
|
|||
|
|
@ -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 int SHADER_TAPS = 11;
|
||||
|
||||
private final Minecraft mc = Minecraft.getInstance();
|
||||
private final MotionBlurResources resources = new MotionBlurResources();
|
||||
|
||||
private final NumberValue<Float> strength =
|
||||
|
|
@ -79,6 +78,7 @@ public final class MotionBlur extends Module implements PostProcessPass {
|
|||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
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) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.level == null || mc.options == null) return true;
|
||||
if (!previousFrameValid || previousFrameNs == 0L) return true;
|
||||
if (previousWorld != mc.level) return true;
|
||||
|
|
@ -270,10 +271,12 @@ public final class MotionBlur extends Module implements PostProcessPass {
|
|||
}
|
||||
|
||||
private CameraType currentPerspective() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return mc != null && mc.options != null ? mc.options.getCameraType() : CameraType.FIRST_PERSON;
|
||||
}
|
||||
|
||||
private void capturePose(float yaw, float pitch, long nowNs) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
previousYaw = yaw;
|
||||
previousPitch = pitch;
|
||||
previousWorld = mc != null ? mc.level : null;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import dev.loki.lovisual.features.relations.PlayerRelations;
|
|||
import dev.loki.lovisual.util.player.PlayerHealthResolver;
|
||||
import dev.loki.lovisual.util.pvp.PvpTargetState;
|
||||
import dev.loki.lovisual.util.text.LegacyTextUtil;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.PlayerInfo;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.Mth;
|
||||
|
|
@ -45,11 +46,12 @@ final class NameTagLabelBuilder {
|
|||
}
|
||||
|
||||
static Component resolveNameplateText(NameTags self, Player player) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (player == null) return Component.empty();
|
||||
String fallback = player.getGameProfile().name();
|
||||
if (!shouldUseTabNames(self)) return Component.literal(fallback);
|
||||
|
||||
var handler = self.mc.getConnection();
|
||||
var handler = mc.getConnection();
|
||||
if (handler == null) return Component.literal(fallback);
|
||||
PlayerInfo entry = handler.getPlayerInfo(player.getUUID());
|
||||
if (entry == null) return Component.literal(fallback);
|
||||
|
|
@ -105,8 +107,9 @@ final class NameTagLabelBuilder {
|
|||
}
|
||||
|
||||
static boolean isCreative(NameTags self, Player player) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (player.isCreative()) return true;
|
||||
var handler = self.mc.getConnection();
|
||||
var handler = mc.getConnection();
|
||||
if (handler == null) return false;
|
||||
PlayerInfo entry = handler.getPlayerInfo(player.getUUID());
|
||||
if (entry == null) return false;
|
||||
|
|
@ -123,8 +126,9 @@ final class NameTagLabelBuilder {
|
|||
}
|
||||
|
||||
static int getPlayerPing(NameTags self, Player player) {
|
||||
if (player == null || self.mc == null || self.mc.getConnection() == null) return -1;
|
||||
PlayerInfo entry = self.mc.getConnection().getPlayerInfo(player.getUUID());
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (player == null || mc == null || mc.getConnection() == null) return -1;
|
||||
PlayerInfo entry = mc.getConnection().getPlayerInfo(player.getUUID());
|
||||
return entry != null ? entry.getLatency() : -1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import dev.loki.lovisual.features.module.Modules;
|
|||
import dev.loki.lovisual.features.relations.CategoryService;
|
||||
import dev.loki.lovisual.render.engine.text.TextRenderer;
|
||||
import dev.loki.lovisual.util.text.TextRenderUtil;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
|
@ -150,9 +151,10 @@ final class NameTagLayoutEngine {
|
|||
}
|
||||
|
||||
static boolean shouldShowDistanceText(NameTags self, Player player) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!self.toggles.get("Show distance")) return false;
|
||||
if (player == null || self.mc.player == null) return false;
|
||||
if (player != self.mc.player) return true;
|
||||
if (player == null || mc.player == null) return false;
|
||||
if (player != mc.player) return true;
|
||||
Freecam freecam = Modules.get(Freecam.class);
|
||||
return freecam != null && freecam.isEnabled();
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue