feat: реорганизация combat/visuals модулей + добавлен InvisESP

This commit is contained in:
loki5512344 2026-09-01 13:53:01 +02:00
parent ff42902c43
commit 4a529466c7
27 changed files with 1478 additions and 2602 deletions

View file

@ -11,6 +11,7 @@ import dev.loki.lovisual.features.command.CommandOutput;
import dev.loki.lovisual.render.engine.svg.SvgRenderOptions;
import dev.loki.lovisual.render.engine.text.TextGlyphFallback;
import dev.loki.lovisual.render.engine.text.TextRenderer;
import dev.loki.lovisual.util.time.GameClock;
import net.minecraft.network.chat.Component;
import java.util.List;
@ -129,13 +130,21 @@ final class BetterChatGlyphRenderer {
tr.render(text.toString(), runX - shadowOffset, textY, BetterChatRenderer.TMP_COLOR, false);
tr.render(text.toString(), runX, textY + shadowOffset, BetterChatRenderer.TMP_COLOR, false);
tr.render(text.toString(), runX, textY - shadowOffset, BetterChatRenderer.TMP_COLOR, false);
long now = GameClock.millis();
float flow = (now % 2600L) / 2600.0f;
float breath = 0.5f + 0.5f * (float) Math.sin(now / 5200.0 * Math.PI * 2.0);
tr.renderQuadGradient(text.toString(), runX, textY, (idx, cp, x0, y0, x1, y1, out) -> {
float t0 = (float) ((x0 - runX) / runW);
float t1 = (float) ((x1 - runX) / runW);
int leftTop = BetterChatDrawUtil.mulAlpha(CommandOutput.mixRgb(start, end, t0), alpha);
int rightTop = BetterChatDrawUtil.mulAlpha(CommandOutput.mixRgb(start, end, t1), alpha);
int leftBottom = BetterChatDrawUtil.mulAlpha(CommandOutput.mixRgb(start, bottomMix, t0), alpha);
int rightBottom = BetterChatDrawUtil.mulAlpha(CommandOutput.mixRgb(start, bottomMix, t1), alpha);
float t0 = (float) ((x0 - runX) / runW) + flow;
float t1 = (float) ((x1 - runX) / runW) + flow;
if (t0 > 1.0f) t0 -= 1.0f;
if (t1 > 1.0f) t1 -= 1.0f;
int base0 = CommandOutput.mixRgb(start, end, t0);
int base1 = CommandOutput.mixRgb(start, end, t1);
int leftTop = BetterChatDrawUtil.mulAlpha(mixBreath(base0, breath), alpha);
int rightTop = BetterChatDrawUtil.mulAlpha(mixBreath(base1, breath), alpha);
int leftBottom = BetterChatDrawUtil.mulAlpha(mixBreath(CommandOutput.mixRgb(start, bottomMix, t0), breath), alpha);
int rightBottom = BetterChatDrawUtil.mulAlpha(mixBreath(CommandOutput.mixRgb(start, bottomMix, t1), breath), alpha);
out[0] = leftTop;
out[1] = leftBottom;
out[2] = rightBottom;
@ -145,4 +154,8 @@ final class BetterChatGlyphRenderer {
tr.end();
}
}
private static int mixBreath(int rgb, float breath) {
return CommandOutput.mixRgb(rgb, 0xFFFFFF, 0.06f * breath);
}
}

View file

@ -0,0 +1,88 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.gui.chat;
import com.mojang.brigadier.suggestion.Suggestion;
import dev.loki.lovisual.features.command.CommandManager;
import dev.loki.lovisual.mixins.accessors.ChatScreenAccessor;
import dev.loki.lovisual.util.screen.ClientScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.screens.ChatScreen;
import net.minecraft.client.input.KeyEvent;
import java.util.List;
/**
* Tab / arrow-key cycling through the custom {@code @}-command suggestion popup.
* Vanilla CommandSuggestions only drives suggestions for {@code /} commands, so our
* {@code @} popup needs its own keyboard handling: Up/Down move the highlight, Tab applies.
*/
public final class BetterChatTabHandler {
private BetterChatTabHandler() {
}
private static String lastInput = "";
private static int lastSelection = -1;
private static int lastTotal = 0;
public static boolean handleKey(KeyEvent input) {
if (input == null) return false;
boolean down = input.isDown();
boolean up = input.isUp();
if (!down && !up && !input.isCycleFocus()) return false;
Minecraft mc = Minecraft.getInstance();
if (!(ClientScreen.current() instanceof ChatScreen chatScreen)) return false;
EditBox field = ((ChatScreenAccessor) chatScreen).getChatField();
if (field == null) return false;
String text = field.getValue();
if (text == null || text.isEmpty()) return false;
if (BetterChatSuggestionState.commandPrefix(text) != '@') return false;
int cursor = field.getCursorPosition();
List<Suggestion> suggestions = CommandManager.suggest(text, cursor);
if (suggestions == null || suggestions.isEmpty()) return false;
int total = suggestions.size();
if (!text.equals(lastInput) || lastTotal != total) {
lastInput = text;
lastTotal = total;
lastSelection = -1;
}
if (down || up) {
int delta = down ? 1 : -1;
int selection = lastSelection < 0 ? (delta > 0 ? 0 : total - 1) : (lastSelection + delta) % total;
if (selection < 0) selection += total;
lastSelection = selection;
trackSelection(suggestions, selection);
return true;
}
int selection = lastSelection < 0 ? 0 : (lastSelection + (input.hasShiftDown() ? -1 : 1)) % total;
if (selection < 0) selection += total;
lastSelection = selection;
Suggestion suggestion = suggestions.get(selection);
try {
field.setValue(suggestion.apply(text));
BetterChatEditBoxUtil.setCursorSafe(field, field.getValue().length());
trackSelection(suggestions, selection);
} catch (Exception ignored) {
return false;
}
return true;
}
private static void trackSelection(List<Suggestion> suggestions, int selection) {
BetterChatRenderer.suggestEntries = suggestions;
BetterChatRenderer.suggestCurrentSelection = selection;
BetterChatRenderer.suggestCurrentStart = 0;
BetterChatRenderer.lastSnapshot = null;
}
}

View file

@ -5,7 +5,7 @@
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.visuals;
package dev.loki.lovisual.features.module.modules.combat;
import dev.loki.lovisual.util.time.GameClock;
@ -24,7 +24,7 @@ import dev.loki.lovisual.render.engine.renderer.FullScreenRenderer;
import dev.loki.lovisual.render.engine.uniform.impl.PostProcessUniforms;
//todo Description
@ModuleInfo(id = "damagetint", displayName = "DamageTint", category = ModuleCategory.VISUALS)
@ModuleInfo(id = "damagetint", displayName = "DamageTint", category = ModuleCategory.COMBAT)
public class DamageTint extends Module implements PostProcessPass {
private static final String SETTING_START_HP = "start_hp";

View file

@ -5,7 +5,7 @@
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.visuals;
package dev.loki.lovisual.features.module.modules.combat;
import com.mojang.blaze3d.pipeline.RenderPipeline;
import com.mojang.blaze3d.vertex.PoseStack;
@ -69,7 +69,7 @@ import java.util.List;
import java.util.Locale;
//todo Description
@ModuleInfo(id = "predictions", displayName = "Predictions", category = ModuleCategory.VISUALS)
@ModuleInfo(id = "predictions", displayName = "Predictions", category = ModuleCategory.COMBAT)
public class Predictions extends Module {
private static final String SETTING_LINE_COLOR = "line_color";
private static final String SETTING_PEARL_CUSTOM_COLOR = "pearl_custom_color";

View file

@ -5,7 +5,7 @@
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.visuals;
package dev.loki.lovisual.features.module.modules.combat;
import dev.loki.lovisual.util.time.GameClock;
@ -45,7 +45,7 @@ import java.util.List;
import java.util.function.IntFunction;
//todo Description
@ModuleInfo(id = "targetesp", displayName = "TargetESP", aliases = {"target", "targetrender"}, category = ModuleCategory.VISUALS)
@ModuleInfo(id = "targetesp", displayName = "TargetESP", aliases = {"target", "targetrender"}, category = ModuleCategory.COMBAT)
public class TargetESP extends Module {
private static final String SETTING_MODE = "mode";

View file

@ -1,856 +0,0 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.visuals;
import com.mojang.blaze3d.vertex.PoseStack;
import dev.loki.lovisual.config.values.*;
import dev.loki.lovisual.features.module.*;
import dev.loki.lovisual.features.module.Module;
import dev.loki.lovisual.util.block.*;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import dev.loki.lovisual.config.common.CommonSettingSchemas;
import dev.loki.lovisual.config.values.*;
import dev.loki.lovisual.features.gui.clickgui.settings.TextListSetting;
import dev.loki.lovisual.features.module.*;
import dev.loki.lovisual.features.module.modules.misc.DefineTarget;
import dev.loki.lovisual.render.engine.RenderState;
import dev.loki.lovisual.render.engine.renderer.Renderer3D;
import dev.loki.lovisual.util.block.*;
import dev.loki.lovisual.util.block.bed.BedBlockUtil;
import dev.loki.lovisual.util.block.bed.BedwarsTeamColorUtil;
import dev.loki.lovisual.util.block.bed.SelfBedMode;
import dev.loki.lovisual.util.block.bed.SelfBedTracker;
import dev.loki.lovisual.util.logging.DebugLog;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
//todo Description
@ModuleInfo(
id = "bedwarsesp",
displayName = "BedwarsESP",
category = ModuleCategory.VISUALS
)
public class BedwarsESP extends Module {
//todo не доделан
private static final int SECTION_SCAN_BUDGET_PER_TICK = 4;
private static final int SECTION_SCAN_REFRESH_TICKS = 40;
private final Minecraft mc = Minecraft.getInstance();
private final WorldSectionBlockScanner sectionScanner = new WorldSectionBlockScanner();
private final EnumValue<BlockScanMode> modeValue =
enumCommon(
"bedwars_esp_mode",
"mode",
CommonSettingSchemas.ESP_SCAN_MODE,
BlockScanMode.LOS,
BlockScanMode.LOS,
BlockScanMode.UNRESTRICTED
);
private final BooleanValue limitDistanceValue =
boolCommon(
"bedwars_esp_limit_distance",
"limit_distance",
CommonSettingSchemas.ESP_LIMIT_DISTANCE,
true
);
private final NumberValue<Integer> maxDistanceValue =
visibleWhen(numCommon(
"bedwars_esp_max_distance",
"max_distance",
CommonSettingSchemas.ESP_MAX_DISTANCE,
64,
1,
256
), limitDistanceValue::get);
private final NumberValue<Integer> maxTargetsValue =
numCommon(
"bedwars_esp_max_targets",
"max_targets",
CommonSettingSchemas.ESP_MAX_TARGETS,
32,
1,
256
);
private final BlockScanContext scanContext = new BlockScanContext(mc, null);
private final BooleanValue useTracersValue =
boolCommon(
"bedwars_esp_use_tracers",
"use_tracers",
CommonSettingSchemas.ESP_USE_TRACERS,
false
);
private final NumberValue<Float> lineWidth =
numCommon(
"bedwars_esp_line_width",
"line_width",
CommonSettingSchemas.ESP_LINE_WIDTH,
1.5f,
0.5f,
4.0f
);
private final EnumValue<BlockOutlineShapeMode> outlineShapeMode =
enumCommon(
"bedwars_esp_outline_shape",
"outline_shape",
CommonSettingSchemas.ESP_OUTLINE_SHAPE,
BlockOutlineShapeMode.FULL_BLOCK,
BlockOutlineShapeMode.values()
);
private final EnumValue<SelfBedMode> selfBedMode =
enumMode(
"bedwars_esp_self_bed",
SelfBedMode.NONE,
SelfBedMode.values()
);
private final NumberValue<Float> spawnBedDistance =
visibleWhen(num(
"bedwars_esp_spawn_bed_distance",
"spawn_bed_distance",
24.0f,
16.0f,
48.0f
), () -> selfBedMode.get() == SelfBedMode.SPAWN_LOCATION);
private final NumberValue<Integer> maxLayers =
num(
"bedwars_esp_max_layers",
"max_layers",
1,
1,
5
);
private final BooleanValue showOpeningsValue =
bool(
"bedwars_esp_show_shell",
"show_openings",
true
);
private final RGBAColorValue selfBedColor =
color(
"bedwars_esp_self_bed_color",
"self_bed_color",
"#8026FF71"
);
private final RGBAColorValue otherBedColor =
color(
"bedwars_esp_other_bed_color",
"other_bed_color",
"#80FF6262"
);
private final RGBAColorValue selfOpeningColor =
visibleWhen(color(
"bedwars_esp_self_shell_color",
"self_opening_color",
"#4026BFFF"
), showOpeningsValue::get);
private final RGBAColorValue otherOpeningColor =
visibleWhen(color(
"bedwars_esp_other_shell_color",
"other_opening_color",
"#40FFC247"
), showOpeningsValue::get);
private final Map<BlockPos, ObservedBed> observedBedStates = new ConcurrentHashMap<>();
private final Map<BlockPos, BlockPos> bedPartToKey = new ConcurrentHashMap<>();
private final Set<Long> loggedBeds = ConcurrentHashMap.newKeySet();
private volatile Map<BlockPos, BedRenderTarget> renderTargets = new ConcurrentHashMap<>();
private ClientLevel lastWorld;
private BlockScanMode lastMode;
private int lastScanSignature = Integer.MIN_VALUE;
private int sectionScanRefreshTicks;
private static AABB createFaceBox(BlockPos bedPos, net.minecraft.core.Direction face) {
final double inset = 0.18;
final double depth = 0.045;
final double eps = 0.0025;
double minX = bedPos.getX();
double minY = bedPos.getY();
double minZ = bedPos.getZ();
double maxX = minX + 1.0;
double maxY = minY + 1.0;
double maxZ = minZ + 1.0;
return switch (face) {
case DOWN -> new AABB(minX + inset, minY - eps, minZ + inset, maxX - inset, minY + depth, maxZ - inset);
case UP -> new AABB(minX + inset, maxY - depth, minZ + inset, maxX - inset, maxY + eps, maxZ - inset);
case NORTH -> new AABB(minX + inset, minY + inset, minZ - eps, maxX - inset, maxY - inset, minZ + depth);
case SOUTH -> new AABB(minX + inset, minY + inset, maxZ - depth, maxX - inset, maxY - inset, maxZ + eps);
case WEST -> new AABB(minX - eps, minY + inset, minZ + inset, minX + depth, maxY - inset, maxZ - inset);
case EAST -> new AABB(maxX - depth, minY + inset, minZ + inset, maxX + eps, maxY - inset, maxZ - inset);
};
}
private static int multiplyAlpha(int argb, float factor) {
int alpha = (argb >>> 24) & 0xFF;
int nextAlpha = Math.max(0, Math.min(255, Math.round(alpha * factor)));
return (argb & 0x00FFFFFF) | (nextAlpha << 24);
}
private static int emphasizeAlpha(int argb) {
int alpha = (argb >>> 24) & 0xFF;
int nextAlpha = Math.max(alpha, 170);
return (argb & 0x00FFFFFF) | (Math.min(255, nextAlpha) << 24);
}
private static void addFilledBox(Renderer3D renderer, AABB box, int argb) {
int a = (argb >>> 24) & 0xFF;
int r = (argb >>> 16) & 0xFF;
int g = (argb >>> 8) & 0xFF;
int b = argb & 0xFF;
double minX = box.minX;
double minY = box.minY;
double minZ = box.minZ;
double maxX = box.maxX;
double maxY = box.maxY;
double maxZ = box.maxZ;
renderer.quad(minX, minY, minZ, maxX, minY, minZ, maxX, minY, maxZ, minX, minY, maxZ, r, g, b, a);
renderer.quad(minX, maxY, minZ, minX, maxY, maxZ, maxX, maxY, maxZ, maxX, maxY, minZ, r, g, b, a);
renderer.quad(minX, minY, maxZ, maxX, minY, maxZ, maxX, maxY, maxZ, minX, maxY, maxZ, r, g, b, a);
renderer.quad(minX, minY, minZ, minX, maxY, minZ, maxX, maxY, minZ, maxX, minY, minZ, r, g, b, a);
renderer.quad(maxX, minY, minZ, maxX, maxY, minZ, maxX, maxY, maxZ, maxX, minY, maxZ, r, g, b, a);
renderer.quad(minX, minY, minZ, minX, minY, maxZ, minX, maxY, maxZ, minX, maxY, minZ, r, g, b, a);
}
private static void addOutlineBox(Renderer3D renderer, AABB box, int argb) {
int a = (argb >>> 24) & 0xFF;
int r = (argb >>> 16) & 0xFF;
int g = (argb >>> 8) & 0xFF;
int b = argb & 0xFF;
double minX = box.minX;
double minY = box.minY;
double minZ = box.minZ;
double maxX = box.maxX;
double maxY = box.maxY;
double maxZ = box.maxZ;
renderer.line(minX, minY, minZ, maxX, minY, minZ, r, g, b, a);
renderer.line(maxX, minY, minZ, maxX, minY, maxZ, r, g, b, a);
renderer.line(maxX, minY, maxZ, minX, minY, maxZ, r, g, b, a);
renderer.line(minX, minY, maxZ, minX, minY, minZ, r, g, b, a);
renderer.line(minX, maxY, minZ, maxX, maxY, minZ, r, g, b, a);
renderer.line(maxX, maxY, minZ, maxX, maxY, maxZ, r, g, b, a);
renderer.line(maxX, maxY, maxZ, minX, maxY, maxZ, r, g, b, a);
renderer.line(minX, maxY, maxZ, minX, maxY, minZ, r, g, b, a);
renderer.line(minX, minY, minZ, minX, maxY, minZ, r, g, b, a);
renderer.line(maxX, minY, minZ, maxX, maxY, minZ, r, g, b, a);
renderer.line(maxX, minY, maxZ, maxX, maxY, maxZ, r, g, b, a);
renderer.line(minX, minY, maxZ, minX, maxY, maxZ, r, g, b, a);
}
@Override
public void onEnable() {
resetRuntimeState();
scanContext.refresh();
BlockObservationHub.requestSodiumRebuild(mc, scanBlockRadius());
requestLocalSectionScan();
}
@Override
public void onTick() {
if (!isEnabled() || mc.level == null) {
return;
}
if (lastWorld != mc.level) {
if (lastWorld != null) {
resetRuntimeState();
RenderSectionBlockScanner.clear();
}
lastWorld = mc.level;
}
scanContext.refresh();
if (mc.player == null) {
return;
}
BlockScanMode mode = modeValue.get();
if (lastMode != mode) {
clearRenderedBeds();
lastMode = mode;
requestLocalSectionScan();
}
maintainSectionScanQueue();
drainSectionScanQueue();
reconcileObservedBeds("cached");
}
@Override
public void onDisable() {
resetRuntimeState();
}
private void resetRuntimeState() {
clearTargets();
lastMode = null;
lastWorld = mc.level;
lastScanSignature = Integer.MIN_VALUE;
sectionScanRefreshTicks = 0;
sectionScanner.reset();
}
private void clearTargets() {
clearRenderedBeds();
observedBedStates.clear();
}
private void clearRenderedBeds() {
renderTargets = new ConcurrentHashMap<>();
bedPartToKey.clear();
loggedBeds.clear();
}
@Override
public WorldPhase getWorldPhase() {
return WorldPhase.END_MAIN;
}
@Override
public void onRenderWorld(PoseStack matrices, SubmitNodeCollector consumers) {
}
@Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
if (!isEnabled() || mc.level == null || mc.player == null) return;
float previousLineWidth = RenderState.lineWidth;
RenderState.lineWidth = Math.max(0.5f, lineWidth.get());
try {
DefineTarget defineTarget = Modules.get(DefineTarget.class);
boolean useTeamColors = defineTarget != null && defineTarget.useBedwarsTeamColors();
for (BedRenderTarget target : renderTargets.values()) {
int bedColor = target.self() ? selfBedColor.getArgb() : otherBedColor.getArgb();
if (useTeamColors && target.teamColorRgb() != -1) {
bedColor = BedwarsTeamColorUtil.replaceRgb(bedColor, target.teamColorRgb());
}
for (BlockPos bedPart : target.bedParts()) {
BlockState state = mc.level.getBlockState(bedPart);
if (!BedBlockUtil.isBed(state)) {
continue;
}
BlockOutlineRenderer.render(renderer, mc.level, bedPart, state, outlineShapeMode.get(), emphasizeAlpha(bedColor));
}
if (!showOpeningsValue.get()) {
continue;
}
int openingColor = target.self() ? selfOpeningColor.getArgb() : otherOpeningColor.getArgb();
if (useTeamColors && target.teamColorRgb() != -1) {
openingColor = BedwarsTeamColorUtil.replaceRgb(openingColor, target.teamColorRgb());
}
for (FaceMarker opening : target.openingMarkers()) {
addFilledBox(renderer, opening.box(), multiplyAlpha(openingColor, 0.85f));
addOutlineBox(renderer, opening.box(), emphasizeAlpha(openingColor));
}
}
} finally {
RenderState.lineWidth = previousLineWidth;
}
renderTracers(renderer, tickDelta);
}
public void acceptSodiumBlockState(int x, int y, int z, BlockState state) {
if (!isEnabled() || !BedBlockUtil.isBed(state)) {
return;
}
recordObservedBlock(new BlockPos(x, y, z), state, false, RenderStatus.HIDDEN);
}
public void acceptSodiumRenderedBlockState(BlockPos pos, BlockState state) {
if (!isEnabled() || !BedBlockUtil.isBed(state)) {
return;
}
recordObservedBlock(pos, state, false, RenderStatus.VISIBLE);
}
public void acceptWorldBlockUpdate(BlockPos pos, BlockState state) {
if (!isEnabled()) {
return;
}
recordObservedBlock(pos, state, true);
}
private void recordObservedBlock(BlockPos rawPos, BlockState state) {
recordObservedBlock(rawPos, state, false, RenderStatus.PRESERVE);
}
private void recordObservedBlock(BlockPos rawPos, BlockState state, boolean forgetScannedSection) {
recordObservedBlock(rawPos, state, forgetScannedSection, RenderStatus.PRESERVE);
}
private void recordObservedBlock(BlockPos rawPos,
BlockState state,
boolean forgetScannedSection,
RenderStatus renderStatus) {
if (rawPos == null || state == null) {
return;
}
BlockPos pos = rawPos.immutable();
if (forgetScannedSection) {
sectionScanner.forget(pos);
}
if (!BedBlockUtil.isBed(state)) {
observedBedStates.remove(pos);
removeBedByPart(pos);
return;
}
RenderSectionBlockScanner.recordBlock(pos, state);
observedBedStates.compute(pos, (ignored, previous) -> {
boolean rendered = switch (renderStatus) {
case PRESERVE -> previous != null && previous.rendered();
case HIDDEN -> false;
case VISIBLE -> true;
};
return new ObservedBed(state, rendered);
});
}
private void updateObservedBed(BlockPos pos, BlockState state, String source) {
if (mc.level == null || mc.player == null || !withinDistance(pos)) {
removeBedByPart(pos);
return;
}
ObservedBed observed = observedBedStates.get(pos);
boolean visible = observed != null && observed.rendered();
if (!acceptsMode(pos, visible)) {
removeBedByPart(pos);
return;
}
BlockPos key = BedBlockUtil.canonicalBedPos(pos, state);
if (key == null) {
removeBedByPart(pos);
return;
}
key = key.immutable();
if (!renderTargets.containsKey(key) && renderTargets.size() >= maxTargetsValue.get()) {
return;
}
Map<BlockPos, BedRenderTarget> built = buildBedTargets(List.of(
new RenderSectionBlockScanner.ScanResult(pos, state, visible)
));
BedRenderTarget target = built.get(key);
if (target == null) {
return;
}
BedRenderTarget previous = renderTargets.put(key, target);
if (previous != null) {
for (BlockPos bedPart : previous.bedParts()) {
bedPartToKey.remove(bedPart);
}
}
for (BlockPos bedPart : target.bedParts()) {
bedPartToKey.put(bedPart, key);
}
if (loggedBeds.add(key.asLong())) {
DebugLog.info(
"[BedwarsESP] detected %s at %s visible=%s source=%s mode=%s",
BuiltInRegistries.BLOCK.getKey(state.getBlock()),
key.toShortString(),
visible,
source,
modeValue.get().getId()
);
}
}
private void reconcileObservedBeds(String source) {
for (Map.Entry<BlockPos, ObservedBed> entry : observedBedStates.entrySet()) {
BlockPos pos = entry.getKey();
ObservedBed observed = entry.getValue();
BlockState state = observed.state();
if (mc.level != null && withinDistance(pos)) {
BlockState currentState = mc.level.getBlockState(pos);
if (BedBlockUtil.isBed(currentState)) {
if (currentState != state) {
observedBedStates.put(pos, new ObservedBed(currentState, observed.rendered()));
state = currentState;
}
} else {
observedBedStates.remove(pos);
removeBedByPart(pos);
continue;
}
}
updateObservedBed(pos, state, source);
}
}
private void removeBedByPart(BlockPos pos) {
BlockPos key = bedPartToKey.remove(pos);
if (key == null) {
return;
}
BedRenderTarget removed = renderTargets.remove(key);
loggedBeds.remove(key.asLong());
if (removed != null) {
for (BlockPos bedPart : removed.bedParts()) {
bedPartToKey.remove(bedPart);
}
}
}
private void maintainSectionScanQueue() {
if (mc.level == null || mc.player == null) {
return;
}
int signature = currentScanSignature();
if (signature != lastScanSignature || ++sectionScanRefreshTicks >= SECTION_SCAN_REFRESH_TICKS) {
lastScanSignature = signature;
sectionScanRefreshTicks = 0;
requestLocalSectionScan();
}
}
private void requestLocalSectionScan() {
if (mc.level == null || mc.player == null) {
return;
}
int radius = scanBlockRadius();
int minY;
int maxY;
if (limitDistanceValue.get()) {
int eyeY = (int) Math.floor(mc.player.getEyePosition().y);
minY = eyeY - radius;
maxY = eyeY + radius;
} else {
minY = mc.level.getMinY();
maxY = mc.level.getMaxY();
}
sectionScanner.enqueueAround(mc, radius, minY, maxY);
}
private void drainSectionScanQueue() {
sectionScanner.drain(
mc,
SECTION_SCAN_BUDGET_PER_TICK,
BedBlockUtil::isBed,
this::recordObservedBlock
);
}
private int scanBlockRadius() {
return limitDistanceValue.get() ? Math.max(1, maxDistanceValue.get()) : 256;
}
private int currentScanSignature() {
int centerChunkX = SectionPos.blockToSectionCoord(mc.player.getBlockX());
int centerChunkZ = SectionPos.blockToSectionCoord(mc.player.getBlockZ());
int centerSectionY = SectionPos.blockToSectionCoord(mc.player.getBlockY());
int result = 17;
result = 31 * result + centerChunkX;
result = 31 * result + centerChunkZ;
result = 31 * result + centerSectionY;
result = 31 * result + (limitDistanceValue.get() ? 1 : 0);
result = 31 * result + maxDistanceValue.get();
result = 31 * result + maxLayers.get();
return result;
}
private boolean withinDistance(BlockPos pos) {
if (!limitDistanceValue.get() || mc.player == null) {
return true;
}
Vec3 eye = mc.player.getEyePosition();
double dx = pos.getX() + 0.5 - eye.x;
double dy = pos.getY() + 0.5 - eye.y;
double dz = pos.getZ() + 0.5 - eye.z;
int maxDistance = maxDistanceValue.get();
return dx * dx + dy * dy + dz * dz <= (double) maxDistance * (double) maxDistance;
}
private boolean acceptsMode(BlockPos pos, boolean visible) {
return switch (modeValue.get()) {
case LOS -> visible;
case UNRESTRICTED -> true;
};
}
private Map<BlockPos, BedRenderTarget> buildBedTargets(Collection<RenderSectionBlockScanner.ScanResult> scanResults) {
Map<BlockPos, MutableBedTarget> grouped = new HashMap<>();
for (RenderSectionBlockScanner.ScanResult result : scanResults) {
BlockState state = result.state();
BlockPos key = BedBlockUtil.canonicalBedPos(result.pos(), state);
if (key == null) {
continue;
}
MutableBedTarget target = grouped.computeIfAbsent(key, ignored -> new MutableBedTarget());
target.sourcePos = result.pos();
target.sourceState = state;
target.visible |= result.visible();
Integer teamColorRgb = BedwarsTeamColorUtil.getBedTeamRgb(state);
if (teamColorRgb != null) {
target.teamColorRgb = teamColorRgb;
}
target.self |= SelfBedTracker.INSTANCE.isSelfBed(
selfBedMode.get(),
state,
result.pos(),
mc.player,
spawnBedDistance.get()
);
target.bedParts.add(result.pos().immutable());
net.minecraft.core.Direction direction = BedBlockUtil.anotherBedPartDirection(state);
if (direction != null) {
target.bedParts.add(result.pos().relative(direction).immutable());
}
}
Map<BlockPos, BedRenderTarget> next = new HashMap<>(grouped.size());
for (Map.Entry<BlockPos, MutableBedTarget> entry : grouped.entrySet()) {
MutableBedTarget value = entry.getValue();
if (value.sourcePos == null || value.sourceState == null || value.bedParts.isEmpty()) {
continue;
}
List<AABB> bedBoxes = new ArrayList<>(value.bedParts.size());
double cx = 0.0;
double cy = 0.0;
double cz = 0.0;
for (BlockPos bedPart : value.bedParts) {
bedBoxes.add(new AABB(bedPart));
Vec3 center = Vec3.atCenterOf(bedPart);
cx += center.x;
cy += center.y;
cz += center.z;
}
Vec3 tracerCenter = new Vec3(cx / value.bedParts.size(), cy / value.bedParts.size(), cz / value.bedParts.size());
List<FaceMarker> openingMarkers = List.of();
if (showOpeningsValue.get()) {
LinkedHashSet<BlockPos> shellPositions = new LinkedHashSet<>();
List<BedBlockUtil.LayeredBlockPos> layerPositions = BedBlockUtil.searchBedLayer(
value.sourcePos,
value.sourceState,
maxLayers.get()
);
layerPositions.sort(Comparator.comparingInt(BedBlockUtil.LayeredBlockPos::layer));
for (BedBlockUtil.LayeredBlockPos layerPos : layerPositions) {
shellPositions.add(layerPos.pos());
}
openingMarkers = collectOpeningMarkers(value.bedParts, shellPositions);
}
next.put(entry.getKey(), new BedRenderTarget(
List.copyOf(value.bedParts),
List.copyOf(bedBoxes),
openingMarkers,
tracerCenter,
value.self,
value.visible,
value.teamColorRgb
));
}
return next;
}
private List<FaceMarker> collectOpeningMarkers(Collection<BlockPos> bedParts, Collection<BlockPos> shellPositions) {
if (bedParts.isEmpty()) {
return List.of();
}
Set<BlockPos> bedSet = new HashSet<>(bedParts);
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int minZ = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int maxY = Integer.MIN_VALUE;
int maxZ = Integer.MIN_VALUE;
for (BlockPos pos : bedParts) {
minX = Math.min(minX, pos.getX());
minY = Math.min(minY, pos.getY());
minZ = Math.min(minZ, pos.getZ());
maxX = Math.max(maxX, pos.getX());
maxY = Math.max(maxY, pos.getY());
maxZ = Math.max(maxZ, pos.getZ());
}
for (BlockPos pos : shellPositions) {
minX = Math.min(minX, pos.getX());
minY = Math.min(minY, pos.getY());
minZ = Math.min(minZ, pos.getZ());
maxX = Math.max(maxX, pos.getX());
maxY = Math.max(maxY, pos.getY());
maxZ = Math.max(maxZ, pos.getZ());
}
minX--;
minY--;
minZ--;
maxX++;
maxY++;
maxZ++;
ArrayDeque<BlockPos> queue = new ArrayDeque<>();
Set<BlockPos> reachable = new HashSet<>();
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y++) {
seedOpeningBoundary(queue, reachable, bedSet, mutable.set(x, y, minZ));
seedOpeningBoundary(queue, reachable, bedSet, mutable.set(x, y, maxZ));
}
}
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
seedOpeningBoundary(queue, reachable, bedSet, mutable.set(x, minY, z));
seedOpeningBoundary(queue, reachable, bedSet, mutable.set(x, maxY, z));
}
}
for (int y = minY; y <= maxY; y++) {
for (int z = minZ; z <= maxZ; z++) {
seedOpeningBoundary(queue, reachable, bedSet, mutable.set(minX, y, z));
seedOpeningBoundary(queue, reachable, bedSet, mutable.set(maxX, y, z));
}
}
while (!queue.isEmpty()) {
BlockPos current = queue.removeFirst();
for (net.minecraft.core.Direction direction : BlockScanContext.DIRECTIONS) {
mutable.set(current).move(direction);
if (mutable.getX() < minX || mutable.getX() > maxX
|| mutable.getY() < minY || mutable.getY() > maxY
|| mutable.getZ() < minZ || mutable.getZ() > maxZ) {
continue;
}
seedOpeningBoundary(queue, reachable, bedSet, mutable);
}
}
LinkedHashSet<FaceKey> openings = new LinkedHashSet<>();
for (BlockPos airPos : reachable) {
for (net.minecraft.core.Direction direction : BlockScanContext.DIRECTIONS) {
BlockPos neighbor = airPos.relative(direction);
if (!bedSet.contains(neighbor)) {
continue;
}
openings.add(new FaceKey(neighbor.immutable(), direction.getOpposite()));
}
}
if (openings.isEmpty()) {
return List.of();
}
List<FaceMarker> result = new ArrayList<>(openings.size());
for (FaceKey opening : openings) {
result.add(new FaceMarker(createFaceBox(opening.pos(), opening.face())));
}
return List.copyOf(result);
}
private void seedOpeningBoundary(ArrayDeque<BlockPos> queue,
Set<BlockPos> reachable,
Set<BlockPos> bedSet,
BlockPos pos) {
if (bedSet.contains(pos) || !scanContext.isPassable(pos)) {
return;
}
BlockPos immutable = pos.immutable();
if (reachable.add(immutable)) {
queue.addLast(immutable);
}
}
private void renderTracers(Renderer3D renderer, float tickDelta) {
Tracers tracers = Modules.get(Tracers.class);
if (!useTracersValue.get() || tracers == null) return;
DefineTarget defineTarget = Modules.get(DefineTarget.class);
boolean useTeamColors = defineTarget != null && defineTarget.useBedwarsTeamColors();
for (BedRenderTarget target : renderTargets.values()) {
int color = target.self() ? selfBedColor.getArgb() : otherBedColor.getArgb();
if (useTeamColors && target.teamColorRgb() != -1) {
color = BedwarsTeamColorUtil.replaceRgb(color, target.teamColorRgb());
}
tracers.render3DTracersRaw(renderer, tickDelta, List.of(target.tracerCenter()), color);
}
}
private static final class MutableBedTarget {
private final LinkedHashSet<BlockPos> bedParts = new LinkedHashSet<>();
private BlockPos sourcePos;
private BlockState sourceState;
private boolean self;
private boolean visible;
private int teamColorRgb = -1;
}
private enum RenderStatus {
PRESERVE,
HIDDEN,
VISIBLE
}
private record ObservedBed(BlockState state, boolean rendered) {
}
private record FaceKey(BlockPos pos, net.minecraft.core.Direction face) {
}
private record FaceMarker(AABB box) {
}
private record BedRenderTarget(
List<BlockPos> bedParts,
List<AABB> bedBoxes,
List<FaceMarker> openingMarkers,
Vec3 tracerCenter,
boolean self,
boolean visible,
int teamColorRgb
) {
}
}

View file

@ -1,835 +0,0 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.visuals;
import com.mojang.blaze3d.vertex.PoseStack;
import dev.loki.lovisual.config.values.*;
import dev.loki.lovisual.features.module.*;
import dev.loki.lovisual.features.module.Module;
import dev.loki.lovisual.util.block.*;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import dev.loki.lovisual.config.common.CommonSettingSchemas;
import dev.loki.lovisual.config.values.*;
import dev.loki.lovisual.features.gui.clickgui.settings.TextListSetting;
import dev.loki.lovisual.features.module.*;
import dev.loki.lovisual.render.engine.RenderState;
import dev.loki.lovisual.render.engine.renderer.Renderer3D;
import dev.loki.lovisual.util.block.*;
import dev.loki.lovisual.util.logging.DebugLog;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
//todo Description
@ModuleInfo(
id = "blockesp",
displayName = "BlockESP",
aliases = {"xray", "oreesp", "blocks"},
category = ModuleCategory.VISUALS
)
public class BlockESP extends Module {
private static final int SECTION_SCAN_BUDGET_PER_TICK = 10;
private static final int SECTION_SCAN_BURST_BUDGET_PER_TICK = 64;
private static final int SECTION_SCAN_BURST_TICKS = 4;
private static final int SECTION_SCAN_REFRESH_TICKS = 40;
private static final int SODIUM_CANDIDATE_DRAIN_BUDGET_PER_TICK = 4096;
private static final int RECONCILE_BUDGET_PER_TICK = 512;
private static final int FULL_RECONCILE_REFRESH_TICKS = 20;
private final Minecraft mc = Minecraft.getInstance();
private final BlockDeobfuscationScanner deobfuscationScanner = new BlockDeobfuscationScanner();
private final WorldSectionBlockScanner sectionScanner = new WorldSectionBlockScanner();
private final EnumValue<BlockScanMode> modeValue =
enumCommon(
"blockEspMode",
"mode",
CommonSettingSchemas.ESP_SCAN_MODE,
BlockScanMode.LOS,
BlockScanMode.values()
);
private final BooleanValue deobfuscationValue =
bool(
"blockEspDeobfuscation",
"deobfuscation",
false
);
private final BooleanValue limitDistanceValue =
boolCommon(
"blockEspLimitDistance",
"limit_distance",
CommonSettingSchemas.ESP_LIMIT_DISTANCE,
true
);
private final NumberValue<Integer> maxDistanceValue =
visibleWhen(numCommon(
"blockEspMaxDistance",
"max_distance",
CommonSettingSchemas.ESP_MAX_DISTANCE,
64,
1,
256
), limitDistanceValue::get);
private final NumberValue<Integer> maxTargetsValue =
numCommon(
"blockEspMaxTargets",
"max_targets",
CommonSettingSchemas.ESP_MAX_TARGETS,
128,
1,
4096
);
private final NumberValue<Integer> deobfuscationRadiusValue =
visibleWhen(num(
"blockEspDeobfuscationRadius",
"deobfuscation_radius",
5,
1,
64
), deobfuscationValue::get);
private final NumberValue<Integer> deobfuscationUpValue =
visibleWhen(num(
"blockEspDeobfuscationUp",
"deobfuscation_up",
5,
1,
32
), deobfuscationValue::get);
private final NumberValue<Integer> deobfuscationDownValue =
visibleWhen(num(
"blockEspDeobfuscationDown",
"deobfuscation_down",
5,
1,
32
), deobfuscationValue::get);
private final BooleanValue deobfuscationFastValue =
visibleWhen(bool(
"blockEspDeobfuscationFast",
"deobfuscation_fast",
false
), deobfuscationValue::get);
private final ItemIdSetValue searchBlocksValue =
itemList(
"blockEspSearchBlocks",
"search_blocks",
TextListSetting.PickerMode.BLOCKS
);
private final BlockScanContext scanContext = new BlockScanContext(mc, searchBlocksValue);
private final BooleanValue useTracersValue =
boolCommon(
"blockEspUseTracers",
"use_tracers",
CommonSettingSchemas.ESP_USE_TRACERS,
false
);
private final EnumValue<BlockOutlineShapeMode> outlineShapeMode =
enumCommon(
"blockEspOutlineShape",
"outline_shape",
CommonSettingSchemas.ESP_OUTLINE_SHAPE,
BlockOutlineShapeMode.FULL_BLOCK,
BlockOutlineShapeMode.values()
);
private final NumberValue<Float> lineWidth =
numCommon(
"blockEspLineWidth",
"line_width",
CommonSettingSchemas.ESP_LINE_WIDTH,
1.5f,
0.5f,
4.0f
);
private final RGBColorValue visibleColor =
colorNoAlpha(
"blockEspVisibleColor",
"visible_color",
"#FF0000"
);
private final RGBColorValue chunkColor =
colorNoAlpha(
"blockEspChunkColor",
"chunk_color",
"#FFFF00"
);
private final BooleanValue clusterTracersValue =
visibleWhen(bool(
"blockEspClusterTracers",
"cluster_tracers",
false
), useTracersValue::get);
private final Map<BlockPos, ObservedTarget> observedTargetStates = new ConcurrentHashMap<>();
private final Set<Long> loggedTargets = ConcurrentHashMap.newKeySet();
private final ArrayDeque<BlockPos> reconcileQueue = new ArrayDeque<>();
private final Set<Long> queuedReconcileTargets = ConcurrentHashMap.newKeySet();
private volatile Map<BlockPos, Target> renderTargets = new ConcurrentHashMap<>();
private ClientLevel lastWorld;
private BlockScanMode lastMode;
private int lastSearchBlocksHash;
private int lastScanSignature = Integer.MIN_VALUE;
private int sectionScanRefreshTicks;
private int sectionScanBurstTicks;
private int fastSnapshotGeneration;
private int fullReconcileRefreshTicks;
@Override
public void onEnable() {
resetRuntimeState();
scanContext.refresh();
lastSearchBlocksHash = currentSearchBlocksHash();
publishFastScanSnapshot();
BlockObservationHub.requestSodiumRebuild(mc, scanBlockRadius());
requestLocalSectionScan(true);
}
@Override
public void onDisable() {
BlockEspSodiumCandidateCollector.disable();
resetRuntimeState();
}
@Override
public void onTick() {
if (!isEnabled() || mc.level == null) {
BlockEspSodiumCandidateCollector.disable();
return;
}
if (lastWorld != mc.level) {
if (lastWorld != null) {
resetRuntimeState();
RenderSectionBlockScanner.clear();
}
lastWorld = mc.level;
}
scanContext.refresh();
if (mc.player == null) {
BlockEspSodiumCandidateCollector.disable();
return;
}
publishFastScanSnapshot();
int searchBlocksHash = currentSearchBlocksHash();
if (lastSearchBlocksHash != searchBlocksHash) {
clearObservedTargets();
deobfuscationScanner.reset();
sectionScanner.reset();
lastSearchBlocksHash = searchBlocksHash;
fastSnapshotGeneration++;
publishFastScanSnapshot();
BlockObservationHub.requestSodiumRebuild(mc, scanBlockRadius());
requestLocalSectionScan(true);
}
BlockScanMode mode = modeValue.get();
if (lastMode != mode) {
renderTargets = new ConcurrentHashMap<>();
loggedTargets.clear();
reconcileQueue.clear();
queuedReconcileTargets.clear();
lastMode = mode;
requestLocalSectionScan(true);
}
drainSodiumCandidateQueue();
maintainSectionScanQueue();
drainSectionScanQueue();
if (deobfuscationValue.get()) {
deobfuscationScanner.tick(
mc,
1,
deobfuscationRadiusValue.get(),
deobfuscationUpValue.get(),
deobfuscationDownValue.get(),
deobfuscationFastValue.get()
);
mergeDeobfuscatedTargets();
}
maintainReconcileQueue();
drainReconcileQueue("cached", RECONCILE_BUDGET_PER_TICK);
}
public void acceptSodiumBlockState(int x, int y, int z, BlockState state) {
if (!isEnabled() || !scanContext.isConfiguredTarget(state)) {
return;
}
acceptObservedCandidate(BlockPos.asLong(x, y, z), state, "sodium-direct", RenderStatus.HIDDEN);
}
public void acceptWorldBlockUpdate(BlockPos pos, BlockState state) {
if (!isEnabled()) {
return;
}
acceptBlockUpdate(pos, state);
}
private void acceptBlockUpdate(BlockPos pos, BlockState state) {
if (pos == null || state == null) {
return;
}
sectionScanner.forget(pos);
scanContext.refresh();
boolean target = scanContext.isConfiguredTarget(state);
boolean deobfuscatedPositive = deobfuscationScanner.hasPositiveState(pos);
deobfuscationScanner.acceptBlockUpdate(pos, state, scanContext::isConfiguredTarget);
if (!target && deobfuscatedPositive) {
BlockState positiveState = deobfuscationScanner.getPositiveState(pos);
if (scanContext.isConfiguredTarget(positiveState)) {
recordTargetCandidate(pos, positiveState);
updateObservedTarget(pos, positiveState, "deobfuscation-update");
}
return;
}
if (!target) {
removeTargetCandidate(pos);
return;
}
recordTargetCandidate(pos, state);
updateObservedTarget(pos, state, "world-update");
if (deobfuscationValue.get()) {
DebugLog.info("[BlockESP] deobfuscated %s at %s", BuiltInRegistries.BLOCK.getKey(state.getBlock()), pos.toShortString());
}
}
private void resetRuntimeState() {
clearObservedTargets();
lastMode = null;
lastWorld = mc.level;
lastSearchBlocksHash = currentSearchBlocksHash();
lastScanSignature = Integer.MIN_VALUE;
sectionScanRefreshTicks = 0;
sectionScanBurstTicks = 0;
fullReconcileRefreshTicks = 0;
fastSnapshotGeneration++;
BlockEspSodiumCandidateCollector.clear();
deobfuscationScanner.reset();
sectionScanner.reset();
}
private void clearObservedTargets() {
renderTargets = new ConcurrentHashMap<>();
observedTargetStates.clear();
loggedTargets.clear();
reconcileQueue.clear();
queuedReconcileTargets.clear();
BlockEspSodiumCandidateCollector.clear();
}
private void recordTargetCandidate(BlockPos rawPos, BlockState state) {
recordTargetCandidate(rawPos, state, RenderStatus.PRESERVE);
}
private void recordTargetCandidate(BlockPos rawPos, BlockState state, RenderStatus renderStatus) {
if (rawPos == null || state == null) {
return;
}
BlockPos pos = rawPos.immutable();
RenderSectionBlockScanner.recordBlock(pos, state);
observedTargetStates.compute(pos, (ignored, previous) -> {
boolean rendered = switch (renderStatus) {
case PRESERVE -> previous != null && previous.rendered();
case HIDDEN -> false;
case VISIBLE -> true;
};
return new ObservedTarget(state, rendered);
});
enqueueReconcile(pos);
}
private void removeTargetCandidate(BlockPos pos) {
if (pos == null) {
return;
}
observedTargetStates.remove(pos);
renderTargets.remove(pos);
loggedTargets.remove(pos.asLong());
queuedReconcileTargets.remove(pos.asLong());
sectionScanner.forget(pos);
if (mc.level != null) {
RenderSectionBlockScanner.recordBlock(pos, mc.level.getBlockState(pos));
}
}
private void updateObservedTarget(BlockPos pos, BlockState state, String source) {
if (mc.level == null || mc.player == null || !withinDistance(pos)) {
renderTargets.remove(pos);
return;
}
ObservedTarget observed = observedTargetStates.get(pos);
boolean visible = observed != null && observed.rendered();
if (!acceptsMode(pos, visible)) {
renderTargets.remove(pos);
return;
}
if (!renderTargets.containsKey(pos) && renderTargets.size() >= maxTargetsValue.get()) {
return;
}
Target target = new Target();
target.pos = pos;
target.state = state;
target.box = new AABB(pos);
target.visible = visible;
renderTargets.put(pos, target);
if (loggedTargets.add(pos.asLong())) {
DebugLog.info(
"[BlockESP] detected %s at %s visible=%s source=%s mode=%s",
BuiltInRegistries.BLOCK.getKey(state.getBlock()),
pos.toShortString(),
visible,
source,
modeValue.get().getId()
);
}
}
private void drainSodiumCandidateQueue() {
BlockEspSodiumCandidateCollector.drain(
SODIUM_CANDIDATE_DRAIN_BUDGET_PER_TICK,
candidate -> {
if (candidate.generation() == fastSnapshotGeneration) {
RenderStatus renderStatus = candidate.source() == BlockEspSodiumCandidateCollector.CandidateSource.SODIUM_BUFFERED_QUAD
? RenderStatus.VISIBLE
: RenderStatus.HIDDEN;
acceptObservedCandidate(
candidate.packedPos(),
candidate.state(),
candidate.source().name().toLowerCase(),
renderStatus
);
}
}
);
}
private void acceptObservedCandidate(long packedPos, BlockState hintedState, String source) {
acceptObservedCandidate(packedPos, hintedState, source, RenderStatus.PRESERVE);
}
private void acceptObservedCandidate(long packedPos, BlockState hintedState, String source, RenderStatus renderStatus) {
if (mc.level == null || mc.player == null) {
return;
}
BlockPos pos = BlockPos.of(packedPos);
if (!withinDistance(pos)) {
removeTargetCandidate(pos);
return;
}
BlockState currentState = mc.level.getBlockState(pos);
BlockState state = scanContext.isConfiguredTarget(currentState) ? currentState : hintedState;
if (!scanContext.isConfiguredTarget(state)) {
removeTargetCandidate(pos);
return;
}
recordTargetCandidate(pos, state, renderStatus);
updateObservedTarget(pos, state, source);
}
private void enqueueReconcile(BlockPos pos) {
if (pos == null) {
return;
}
long key = pos.asLong();
if (queuedReconcileTargets.add(key)) {
reconcileQueue.addLast(pos.immutable());
}
}
private void maintainReconcileQueue() {
if (++fullReconcileRefreshTicks < FULL_RECONCILE_REFRESH_TICKS) {
return;
}
fullReconcileRefreshTicks = 0;
for (BlockPos pos : observedTargetStates.keySet()) {
enqueueReconcile(pos);
}
}
private void drainReconcileQueue(String source, int maxEntries) {
int processed = 0;
while (processed < maxEntries && !reconcileQueue.isEmpty()) {
BlockPos pos = reconcileQueue.removeFirst();
queuedReconcileTargets.remove(pos.asLong());
ObservedTarget observed = observedTargetStates.get(pos);
if (observed == null) {
processed++;
continue;
}
BlockState state = observed.state();
if (mc.level != null && withinDistance(pos)) {
BlockState currentState = mc.level.getBlockState(pos);
if (scanContext.isConfiguredTarget(currentState)) {
if (currentState != state) {
observedTargetStates.put(pos, new ObservedTarget(currentState, observed.rendered()));
state = currentState;
}
} else if (!deobfuscationScanner.hasPositiveState(pos)) {
removeTargetCandidate(pos);
processed++;
continue;
}
}
updateObservedTarget(pos, state, source);
processed++;
}
}
private void maintainSectionScanQueue() {
if (mc.level == null || mc.player == null) {
return;
}
int signature = currentScanSignature();
if (signature != lastScanSignature || ++sectionScanRefreshTicks >= SECTION_SCAN_REFRESH_TICKS) {
lastScanSignature = signature;
sectionScanRefreshTicks = 0;
requestLocalSectionScan(false);
}
}
private void requestLocalSectionScan(boolean burst) {
if (mc.level == null || mc.player == null) {
return;
}
int radius = scanBlockRadius();
int minY;
int maxY;
if (limitDistanceValue.get()) {
int eyeY = (int) Math.floor(mc.player.getEyePosition().y);
minY = eyeY - radius;
maxY = eyeY + radius;
} else {
minY = mc.level.getMinY();
maxY = mc.level.getMaxY();
}
sectionScanner.enqueueAround(mc, radius, minY, maxY);
if (burst) {
sectionScanBurstTicks = SECTION_SCAN_BURST_TICKS;
}
}
private void drainSectionScanQueue() {
int budget = sectionScanBurstTicks > 0 ? SECTION_SCAN_BURST_BUDGET_PER_TICK : SECTION_SCAN_BUDGET_PER_TICK;
if (sectionScanBurstTicks > 0) {
sectionScanBurstTicks--;
}
sectionScanner.drain(
mc,
budget,
scanContext::isConfiguredTarget,
(pos, state) -> acceptObservedCandidate(pos.asLong(), state, "section")
);
}
private void publishFastScanSnapshot() {
if (!isEnabled() || mc.player == null) {
BlockEspSodiumCandidateCollector.disable();
return;
}
BlockEspSodiumCandidateCollector.publishSnapshot(
true,
scanContext.targetBlocksSnapshot(),
limitDistanceValue.get(),
scanBlockRadius(),
mc.player.getBlockX(),
mc.player.getBlockY(),
mc.player.getBlockZ(),
fastSnapshotGeneration
);
}
private int scanBlockRadius() {
return limitDistanceValue.get() ? Math.max(1, maxDistanceValue.get()) : 256;
}
private int currentSearchBlocksHash() {
Set<String> targets = searchBlocksValue.get();
return targets == null ? 0 : targets.hashCode();
}
private int currentScanSignature() {
int centerChunkX = SectionPos.blockToSectionCoord(mc.player.getBlockX());
int centerChunkZ = SectionPos.blockToSectionCoord(mc.player.getBlockZ());
int centerSectionY = SectionPos.blockToSectionCoord(mc.player.getBlockY());
int result = 17;
result = 31 * result + centerChunkX;
result = 31 * result + centerChunkZ;
result = 31 * result + centerSectionY;
result = 31 * result + (limitDistanceValue.get() ? 1 : 0);
result = 31 * result + maxDistanceValue.get();
result = 31 * result + currentSearchBlocksHash();
return result;
}
private void mergeDeobfuscatedTargets() {
for (RenderSectionBlockScanner.ScanResult result : deobfuscationScanner.snapshot(
mc,
scanContext::isConfiguredTarget,
null,
maxTargetsValue.get()
)) {
acceptObservedCandidate(result.pos().asLong(), result.state(), "deobfuscation");
}
}
private boolean withinDistance(BlockPos pos) {
if (!limitDistanceValue.get() || mc.player == null) {
return true;
}
Vec3 eye = mc.player.getEyePosition();
double dx = pos.getX() + 0.5 - eye.x;
double dy = pos.getY() + 0.5 - eye.y;
double dz = pos.getZ() + 0.5 - eye.z;
int maxDistance = maxDistanceValue.get();
return dx * dx + dy * dy + dz * dz <= (double) maxDistance * (double) maxDistance;
}
private boolean isCurrentTargetBlock(BlockPos pos) {
if (mc.level == null || pos == null) {
return false;
}
return scanContext.isConfiguredTarget(mc.level.getBlockState(pos));
}
private boolean acceptsMode(BlockPos pos, boolean visible) {
return switch (modeValue.get()) {
case LOS -> visible;
case UNRESTRICTED -> true;
};
}
@Override
public WorldPhase getWorldPhase() {
return WorldPhase.END_MAIN;
}
@Override
public void onRenderWorld(PoseStack matrices, SubmitNodeCollector consumers) {
}
@Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
if (!isEnabled() || mc.level == null || mc.player == null) {
return;
}
float previousLineWidth = RenderState.lineWidth;
RenderState.lineWidth = Math.max(0.5f, lineWidth.get());
try {
for (Target target : renderTargets.values()) {
if (!withinDistance(target.pos)) {
continue;
}
BlockState state = target.state;
if (mc.level != null) {
BlockState current = mc.level.getBlockState(target.pos);
if (!scanContext.isConfiguredTarget(current)) {
removeTargetCandidate(target.pos);
continue;
}
state = current;
if (!state.equals(target.state)) {
ObservedTarget observed = observedTargetStates.get(target.pos);
boolean rendered = observed != null && observed.rendered();
observedTargetStates.put(target.pos, new ObservedTarget(state, rendered));
target.state = state;
}
}
int color = 0xFF000000 | ((target.visible ? visibleColor.getArgb() : chunkColor.getArgb()) & 0x00FFFFFF);
BlockOutlineRenderer.render(renderer, mc.level, target.pos, state, outlineShapeMode.get(), color);
}
} finally {
RenderState.lineWidth = previousLineWidth;
}
renderBlockTracers(renderer, tickDelta);
}
private void drawBox(Renderer3D renderer, AABB box, int r, int g, int b, int a) {
double x1 = box.minX;
double y1 = box.minY;
double z1 = box.minZ;
double x2 = box.maxX;
double y2 = box.maxY;
double z2 = box.maxZ;
line(renderer, x1, y1, z1, x2, y1, z1, r, g, b, a);
line(renderer, x2, y1, z1, x2, y1, z2, r, g, b, a);
line(renderer, x2, y1, z2, x1, y1, z2, r, g, b, a);
line(renderer, x1, y1, z2, x1, y1, z1, r, g, b, a);
line(renderer, x1, y2, z1, x2, y2, z1, r, g, b, a);
line(renderer, x2, y2, z1, x2, y2, z2, r, g, b, a);
line(renderer, x2, y2, z2, x1, y2, z2, r, g, b, a);
line(renderer, x1, y2, z2, x1, y2, z1, r, g, b, a);
line(renderer, x1, y1, z1, x1, y2, z1, r, g, b, a);
line(renderer, x2, y1, z1, x2, y2, z1, r, g, b, a);
line(renderer, x2, y1, z2, x2, y2, z2, r, g, b, a);
line(renderer, x1, y1, z2, x1, y2, z2, r, g, b, a);
}
private void line(Renderer3D renderer,
double x1, double y1, double z1,
double x2, double y2, double z2,
int r, int g, int b, int a) {
renderer.line(x1, y1, z1, x2, y2, z2, r, g, b, a);
}
private void renderBlockTracers(Renderer3D renderer, float tickDelta) {
Tracers tracers = Modules.get(Tracers.class);
if (!useTracersValue.get() || tracers == null) {
return;
}
if (!clusterTracersValue.get()) {
for (Target target : renderTargets.values()) {
if (!withinDistance(target.pos) || !isCurrentTargetBlock(target.pos)) {
continue;
}
int color = target.visible ? visibleColor.getArgb() : chunkColor.getArgb();
tracers.render3DTracersRaw(renderer, tickDelta, List.of(Vec3.atCenterOf(target.pos)), color);
}
return;
}
List<List<Target>> clusters = buildClusters(renderTargets.values().stream()
.filter(target -> withinDistance(target.pos) && isCurrentTargetBlock(target.pos))
.toList());
for (List<Target> cluster : clusters) {
boolean anyVisible = cluster.stream().anyMatch(target -> target.visible);
int color = anyVisible ? visibleColor.getArgb() : chunkColor.getArgb();
tracers.render3DTracersRaw(renderer, tickDelta, List.of(clusterCenter(cluster)), color);
}
}
private List<List<Target>> buildClusters(Collection<Target> targets) {
Map<BlockPos, Target> map = new HashMap<>();
for (Target target : targets) {
map.put(target.pos, target);
}
Set<BlockPos> visited = new HashSet<>();
List<List<Target>> clusters = new ArrayList<>();
for (Target target : targets) {
if (visited.contains(target.pos)) {
continue;
}
List<Target> cluster = new ArrayList<>();
ArrayDeque<BlockPos> queue = new ArrayDeque<>();
queue.add(target.pos);
visited.add(target.pos);
while (!queue.isEmpty()) {
BlockPos current = queue.poll();
Target currentTarget = map.get(current);
if (currentTarget != null) {
cluster.add(currentTarget);
}
for (BlockPos neighbor : new BlockPos[]{
current.above(), current.below(),
current.north(), current.south(),
current.east(), current.west()
}) {
if (!visited.contains(neighbor) && map.containsKey(neighbor)) {
visited.add(neighbor);
queue.add(neighbor);
}
}
}
clusters.add(cluster);
}
return clusters;
}
private Vec3 clusterCenter(List<Target> cluster) {
double x = 0.0;
double y = 0.0;
double z = 0.0;
for (Target target : cluster) {
Vec3 center = Vec3.atCenterOf(target.pos);
x += center.x;
y += center.y;
z += center.z;
}
int size = cluster.size();
return new Vec3(x / size, y / size, z / size);
}
public static class Target {
BlockPos pos;
BlockState state;
AABB box;
boolean visible;
}
private enum RenderStatus {
PRESERVE,
HIDDEN,
VISIBLE
}
private record ObservedTarget(BlockState state, boolean rendered) {
}
}

View file

@ -0,0 +1,312 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.visuals;
import dev.loki.lovisual.config.common.CommonSettingSchemas;
import dev.loki.lovisual.config.values.BooleanValue;
import dev.loki.lovisual.config.values.EnumValue;
import dev.loki.lovisual.config.values.NumberValue;
import dev.loki.lovisual.config.values.RGBAColorValue;
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.render.engine.animation.AnimatedRenderColors;
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 dev.loki.lovisual.render.helpers.HeadAnchor;
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
* {@link ChinaHatPalette}; this class only wires settings to rendering.
*/
@ModuleInfo(
id = "chinahat",
displayName = "ChinaHat",
category = ModuleCategory.VISUALS
)
public final class ChinaHat extends Module {
private final Minecraft mc = Minecraft.getInstance();
private final EnumValue<RenderTarget> renderTarget =
enumMode(
"render_target",
RenderTarget.BOTH,
RenderTarget.values()
);
private final BooleanValue useRelations =
boolCommon(
"use_relations",
"use_relations",
CommonSettingSchemas.PLAYER_USE_RELATIONS,
true
);
private final RGBAColorValue primaryColor =
visibleWhen(common(
color(
"primary_color",
"#CCE8C547"
),
CommonSettingSchemas.RENDER_PRIMARY_COLOR.commonI18nKey()
), () -> !useRelations.get());
private final EnumValue<ColorMode> colorMode =
enumCommon(
"color_mode",
"color_mode",
CommonSettingSchemas.RENDER_COLOR_MODE,
ColorMode.STATIC,
ColorMode.values()
);
private final RGBAColorValue secondaryColor =
visibleWhen(common(
color(
"secondary_color",
"#AA9A7A1A"
),
CommonSettingSchemas.RENDER_SECONDARY_COLOR.commonI18nKey()
), this::usesSecondaryManualColor);
private final NumberValue<Integer> colorSpeed =
numCommon(
"color_speed",
"color_speed",
CommonSettingSchemas.RENDER_COLOR_SPEED,
18,
2,
54
);
private final NumberValue<Float> scale =
numCommon(
"scale",
"scale",
CommonSettingSchemas.RENDER_SCALE,
1.15f,
0.45f,
2.0f
);
private final BooleanValue depthTest =
boolCommon(
"depth_test",
"depth_test",
CommonSettingSchemas.RENDER_DEPTH_TEST,
true
);
private final RGBAColorValue rimColor =
visibleWhen(color(
"rim_color",
"#EEF3D18A"
), () -> !useRelations.get());
private final NumberValue<Integer> segments =
num(
"segments",
40,
6,
96
);
private final NumberValue<Float> brimRadius =
num(
"brim_radius",
0.45f,
0.2f,
0.7f
);
private final NumberValue<Float> domeRadius =
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
);
private final NumberValue<Float> headTopOffset =
num(
"head_top_offset",
0.3f,
0.05f,
0.5f
);
private final NumberValue<Float> headForwardOffset =
num(
"head_forward_offset",
0.0f,
-0.25f,
0.25f
);
private final NumberValue<Float> headRightOffset =
num(
"head_right_offset",
0.0f,
-0.25f,
0.25f
);
@Override
public WorldPhase getWorldPhase() {
return WorldPhase.BEFORE_TRANSLUCENT;
}
@Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
if (!isEnabled() || mc.level == null || mc.player == null) return;
MeshBuilder tris = renderer.batch(
depthTest.get()
? LoVisualRenderPipelines.WORLD_COLORED_DEPTH
: LoVisualRenderPipelines.WORLD_COLORED,
depthTest.get()
? Renderer3D.DepthMode.PRE_DEPTH
: Renderer3D.DepthMode.NONE
);
MeshBuilder lines = renderer.batch(
depthTest.get()
? LoVisualRenderPipelines.WORLD_COLORED_LINES_DEPTH
: LoVisualRenderPipelines.WORLD_COLORED_LINES,
depthTest.get()
? Renderer3D.DepthMode.PRE_DEPTH
: Renderer3D.DepthMode.NONE
);
if (tris == null || lines == null) return;
if (shouldRenderSelf() && !mc.options.getCameraType().isFirstPerson()) {
renderAttached(tris, lines, mc.player, tickDelta);
}
if (shouldRenderPlayers()) {
for (Player player : mc.level.players()) {
if (player == null || player == mc.player || player.isRemoved()) continue;
if (!player.isAlive() || player.getHealth() <= 0.0f) continue;
renderAttached(tris, lines, player, tickDelta);
}
}
}
private boolean shouldRenderSelf() {
return renderTarget.get() == RenderTarget.SELF || renderTarget.get() == RenderTarget.BOTH;
}
private boolean shouldRenderPlayers() {
return renderTarget.get() == RenderTarget.PLAYERS || renderTarget.get() == RenderTarget.BOTH;
}
private boolean usesSecondaryManualColor() {
if (useRelations.get()) return false;
return AnimatedRenderColors.usesSecondary(animatedColorMode());
}
private void renderAttached(MeshBuilder tris, MeshBuilder lines, Player player, float tickDelta) {
HeadAnchor anchor = HeadAnchor.of(
player,
tickDelta,
headTopOffset.get(),
headForwardOffset.get(),
headRightOffset.get()
);
ChinaHatPalette.Palette palette = ChinaHatPalette.resolve(
player,
useRelations.get(),
primaryColor.getArgb(),
secondaryColor.getArgb(),
rimColor.getArgb()
);
AnimatedRenderColors.Mode mode = animatedColorMode();
int speed = colorSpeed.get();
float size = scale.get();
ChinaHatModel.build(
tris,
lines,
anchor,
segments.get(),
brimRadius.get() * size,
domeRadius.get() * size,
domeHeight.get() * size,
brimThickness.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
);
}
private AnimatedRenderColors.Mode animatedColorMode() {
return switch (colorMode.get()) {
case RAINBOW -> AnimatedRenderColors.Mode.RAINBOW;
case LIGHT_RAINBOW -> AnimatedRenderColors.Mode.LIGHT_RAINBOW;
case SKY -> AnimatedRenderColors.Mode.SKY;
case FADE -> AnimatedRenderColors.Mode.FADE;
case DOUBLE_COLOR -> AnimatedRenderColors.Mode.DOUBLE_COLOR;
case ANALOGOUS -> AnimatedRenderColors.Mode.ANALOGOUS;
case THEME -> AnimatedRenderColors.Mode.THEME;
case STATIC -> AnimatedRenderColors.Mode.STATIC;
};
}
public enum RenderTarget implements EnumValue.IdProvider {
SELF("self"),
PLAYERS("players"),
BOTH("both");
private final String id;
RenderTarget(String id) {
this.id = id;
}
@Override
public String id() {
return id;
}
}
public enum ColorMode implements EnumValue.IdProvider {
STATIC("static"),
RAINBOW("rainbow"),
LIGHT_RAINBOW("light_rainbow"),
SKY("sky"),
FADE("fade"),
DOUBLE_COLOR("double_color"),
ANALOGOUS("analogous"),
THEME("theme");
private final String id;
ColorMode(String id) {
this.id = id;
}
@Override
public String id() {
return id;
}
}
}

View file

@ -0,0 +1,133 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.visuals;
import dev.loki.lovisual.render.engine.uniform.MeshBuilder;
import dev.loki.lovisual.render.helpers.HeadAnchor;
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.
*/
final class ChinaHatModel {
private ChinaHatModel() {
}
interface Shade {
int color(int index, float verticalLight, float alphaMul);
}
static void build(MeshBuilder tris,
MeshBuilder lines,
HeadAnchor anchor,
int segments,
float brimRadius,
float domeRadius,
float domeHeight,
float brimThickness,
float yawDeg,
float pitchDeg,
float rollDeg,
Shade domeShade,
Shade brimShade,
Shade rimShade,
float alphaMul) {
if (tris == null || lines == 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);
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 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);
}
}
private static void triangle(MeshBuilder mesh, Vec3 a, Vec3 b, int cA, Vec3 c, int cC) {
mesh.ensureTriCapacity();
int i1 = putVertex(mesh, a, cA);
int i2 = putVertex(mesh, b, cA);
int i3 = putVertex(mesh, c, cC);
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);
int i2 = putVertex(mesh, b, cB);
mesh.line(i1, i2);
}
private static int putVertex(MeshBuilder mesh, Vec3 p, int argb) {
return mesh.vec3(p.x, p.y, p.z)
.color((argb >>> 24) & 255, (argb >>> 16) & 255, (argb >>> 8) & 255, argb & 255)
.next();
}
}

View file

@ -0,0 +1,144 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.visuals;
import dev.loki.lovisual.features.relations.CategoryService;
import dev.loki.lovisual.render.engine.animation.AnimatedRenderColors;
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
import dev.loki.lovisual.util.time.GameClock;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.player.Player;
/**
* Color resolution helpers for ChinaHat. Shared color-math and per-vertex shading,
* mirroring the TazikHat palette pipeline but reusable by the facade module.
*/
final class ChinaHatPalette {
private ChinaHatPalette() {
}
private static final double RAINBOW_SPATIAL_SCALE = 12.0;
record Palette(int primary, int secondary, int rim) {
}
static Palette resolve(Player player,
boolean useRelations,
int primaryArgb,
int secondaryArgb,
int rimArgb) {
if (useRelations) {
int relation = CategoryService.getColor(player);
int primary = withAlpha(relation, alphaOr(primaryArgb, 255));
int secondary = withAlpha(
AnimatedRenderColors.analogousColor(relation),
alphaOr(secondaryArgb, 170)
);
int rim = withAlpha(brighten(relation, 1.35f), alphaOr(rimArgb, 238));
return new Palette(primary, secondary, rim);
}
return new Palette(primaryArgb, secondaryArgb, rimArgb);
}
static int colorAt(Palette palette,
AnimatedRenderColors.Mode mode,
int speed,
int index,
float verticalLight,
float alphaMul) {
int dynamic = getDynamicColor(palette.primary, palette.secondary, mode, speed, index);
float circumference = 0.5f + 0.5f * Mth.sin(index * 0.47f + GameClock.millis() * 0.0007f);
int mixed = multiplyRgb(dynamic, verticalLight + circumference * 0.16f);
return withAlpha(mixed, Math.round(alpha(mixed) * alphaMul));
}
static int rimColorAt(Palette palette,
AnimatedRenderColors.Mode mode,
int speed,
int index,
float alphaMul) {
int dynamic = getDynamicColor(palette.rim, palette.secondary, mode, speed, index + 120);
float t = 0.5f + 0.5f * Mth.sin(index * 0.71f + GameClock.millis() * 0.0011f);
int mixed = mix(dynamic, brighten(palette.primary, 1.35f), t);
return withAlpha(mixed, Math.round(alpha(mixed) * alphaMul));
}
static int getDynamicColor(int primary,
int secondary,
AnimatedRenderColors.Mode mode,
int speed,
int index) {
int spatialIndex = Math.floorMod((int) Math.round(index * RAINBOW_SPATIAL_SCALE), 3600);
return AnimatedRenderColors.resolve(mode, speed, spatialIndex, primary, secondary, true);
}
static int mix(int a, int b, float t) {
float clamped = AnimationUtility.clamp01(t);
int aa = alpha(a);
int ar = red(a);
int ag = green(a);
int ab = blue(a);
int ba = alpha(b);
int br = red(b);
int bg = green(b);
int bb = blue(b);
int ca = Math.round(AnimationUtility.lerp(aa, ba, clamped));
int cr = Math.round(AnimationUtility.lerp(ar, br, clamped));
int cg = Math.round(AnimationUtility.lerp(ag, bg, clamped));
int cb = Math.round(AnimationUtility.lerp(ab, bb, clamped));
return (ca << 24) | (cr << 16) | (cg << 8) | cb;
}
static int multiplyRgb(int argb, float factor) {
int a = alpha(argb);
int r = Mth.clamp(Math.round(red(argb) * factor), 0, 255);
int g = Mth.clamp(Math.round(green(argb) * factor), 0, 255);
int b = Mth.clamp(Math.round(blue(argb) * factor), 0, 255);
return (a << 24) | (r << 16) | (g << 8) | b;
}
static int brighten(int argb, float factor) {
return multiplyRgb(argb, factor);
}
static int withAlpha(int argb, int alpha) {
int a = Mth.clamp(alpha, 0, 255);
return (a << 24) | (argb & 0x00FFFFFF);
}
static int alphaOr(int argb, int fallback) {
int alpha = alpha(argb);
return alpha <= 0 ? Mth.clamp(fallback, 0, 255) : alpha;
}
static int red(int argb) {
return (argb >>> 16) & 255;
}
static int green(int argb) {
return (argb >>> 8) & 255;
}
static int blue(int argb) {
return argb & 255;
}
static int alpha(int argb) {
return (argb >>> 24) & 255;
}
}

View file

@ -0,0 +1,76 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.visuals;
import dev.loki.lovisual.config.values.NumberValue;
import dev.loki.lovisual.config.values.RGBAColorValue;
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.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.effect.MobEffects;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.AABB;
@ModuleInfo(
id = "invisesp",
displayName = "InvisESP",
aliases = {"invis"},
category = ModuleCategory.VISUALS,
description = "module.invisesp.description"
)
public final class InvisEsp extends Module {
private final Minecraft mc = Minecraft.getInstance();
private final RGBAColorValue outlineColor =
color("invisesp_color", "color", "#FF00E5FF");
private final NumberValue<Float> lineWidth =
num("invisesp_line_width", "line_width", 1.5f, 0.5f, 6.0f);
{
setDefaultBind("L");
}
@Override
public WorldPhase getWorldPhase() {
return WorldPhase.END_MAIN;
}
@Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
if (!isEnabled() || mc.level == null || mc.player == null) return;
float previousLineWidth = RenderState.lineWidth;
MeshBuilder lines;
try {
RenderState.lineWidth = lineWidth.get();
lines = renderer.batch(LoVisualRenderPipelines.WORLD_COLORED_LINES, Renderer3D.DepthMode.NONE);
} finally {
RenderState.lineWidth = previousLineWidth;
}
int color = outlineColor.getArgb();
for (Player player : mc.level.players()) {
if (player == mc.player) continue;
if (!isInvisible(player)) continue;
AABB box = Esp2DRenderer.resolveWorldBox(player, tickDelta);
EspMeshBuilder.addOutlineBox(lines, box, color, color, color, color, color, color, color, color);
}
}
private boolean isInvisible(Player player) {
return player.isInvisible() || player.hasEffect(MobEffects.INVISIBILITY);
}
}

View file

@ -1,45 +0,0 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.visuals;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
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;
//todo Description
@ModuleInfo(
id = "seeinvisibles",
displayName = "SeeInvisibles",
category = ModuleCategory.VISUALS
)
public class SeeInvisibles extends Module {
private final NumberValue<Integer> alpha = num("seeInvisiblesAlpha", "alpha", 110, 0, 255);
public boolean shouldRenderInvisiblePlayer(LivingEntity entity) {
return isEnabled() && entity instanceof Player && entity.isInvisible();
}
public boolean shouldRenderInvisiblePlayer(EntityType<?> entityType, boolean invisible) {
return isEnabled() && invisible && entityType == net.minecraft.world.entity.EntityTypes.PLAYER;
}
public int getInvisiblePlayerTintArgb() {
int alphaByte = Mth.clamp(alpha.get(), 0, 255);
return (alphaByte << 24) | 0x00FFFFFF;
}
public float getInvisiblePlayerAlpha01() {
return Mth.clamp(alpha.get(), 0, 255) / 255.0f;
}
}

View file

@ -1,356 +0,0 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.features.module.modules.visuals;
import com.mojang.blaze3d.textures.GpuSampler;
import com.mojang.blaze3d.textures.GpuTextureView;
import dev.loki.lovisual.features.module.*;
import dev.loki.lovisual.features.module.Module;
import dev.loki.lovisual.util.screen.ClientScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.inventory.InventoryScreen;
import net.minecraft.client.renderer.texture.AbstractTexture;
import net.minecraft.resources.Identifier;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix4f;
import dev.loki.lovisual.config.values.BooleanValue;
import dev.loki.lovisual.config.values.ModeValue;
import dev.loki.lovisual.features.module.*;
import dev.loki.lovisual.features.relations.CategoryService;
import dev.loki.lovisual.mixininterface.IEntity;
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
import dev.loki.lovisual.render.engine.color.RenderColor;
import dev.loki.lovisual.render.engine.core.ViewportContext;
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
import dev.loki.lovisual.render.engine.renderer.MeshRenderer;
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
import dev.loki.lovisual.render.engine.renderer.Renderer3D;
import dev.loki.lovisual.render.engine.text.TextRenderer;
import dev.loki.lovisual.render.engine.uniform.MeshBuilder;
import java.util.List;
//todo Description
@ModuleInfo(id = "tracers", displayName = "Tracers", aliases = {"arrows", "lines"}, category = ModuleCategory.VISUALS)
public class Tracers extends Module {
private static final String SETTING_TRACE_FRIENDS = "trace_friends";
private static final String SETTING_MOVE_RADIUS_BONUS = "move_radius_bonus";
private static final String SETTING_MODE = "mode";
private static final String SETTING_ARROW_TEXTURE = "arrow_texture";
private static final Identifier ARROW_1 = Identifier.fromNamespaceAndPath("lovisual", "textures/hud/elements/arrow.png");
private static final Identifier ARROW_2 = Identifier.fromNamespaceAndPath("lovisual", "textures/hud/elements/arrow2.png");
private static final Identifier ARROW_3 = Identifier.fromNamespaceAndPath("lovisual", "textures/hud/elements/arrow3.png");
private static final Identifier ARROW_4 = Identifier.fromNamespaceAndPath("lovisual", "textures/hud/elements/arrow4.png");
private static final float BASE_RADIUS = 48.0f;
private static final float INVENTORY_RADIUS_BONUS = 50.0f;
private static final float MOVE_RADIUS_BONUS = 1.5f;
private static final float ARROW_W = 10.0f;
private static final float ARROW_H = 10.0f;
private static final float ARROW_OFFSET_X = -5.0f;
private static final float ARROW_OFFSET_Y = -6.0f;
private static final int ARROW_ALPHA = 235;
private static final float ARROW_ROTATION_OFFSET_DEG = 90.0f;
private static final float ARROW_DISTANCE_NEAR = 6.0f;
private static final float ARROW_DISTANCE_FAR = 60.0f;
private static final float ARROW_RADIUS_MIN_FACTOR = 0.55f;
private static final float ARROW_RADIUS_MAX_FACTOR = 1.0f;
private static final MeshBuilder ARROW_MESH =
new MeshBuilder(LoVisualRenderPipelines.UI_TEXTURED_ADDITIVE);
private final Minecraft mc = Minecraft.getInstance();
private final BooleanValue traceFriendsValue = bool("tracersTraceFriends", SETTING_TRACE_FRIENDS, false);
private final ModeValue tracersMode =
modeSetting("tracersMode", SETTING_MODE, "Lines", "Lines", "Arrows");
private final BooleanValue moveRadiusBonusValue =
visibleWhen(bool("tracersMoveRadiusBonus", SETTING_MOVE_RADIUS_BONUS, false), this::isArrowsMode);
private final ModeValue arrowTexture =
visibleWhen(modeSetting("tracersArrowTexture", SETTING_ARROW_TEXTURE, "Arrow", "Arrow", "Arrow2", "Arrow3", "Arrow4"),
this::isArrowsMode);
private float animationStep;
private float animatedYaw;
private float animatedPitch;
private float yaw;
private static boolean isMoving(Vec2 move) {
if (move == null) return false;
return move.x != 0.0f || move.y != 0.0f;
}
private static boolean isNameValid(String name) {
if (name == null) return false;
int len = name.length();
if (len < 3 || len > 16) return false;
for (int i = 0; i < len; i++) {
char c = name.charAt(i);
if (c == '_') continue;
if (!Character.isLetterOrDigit(c)) return false;
}
return true;
}
private static int withAlpha(int argb, int alpha) {
int a = Mth.clamp(alpha, 0, 255);
return (argb & 0x00FFFFFF) | (a << 24);
}
private static float radiusFactorForDistance(double distance) {
if (distance <= ARROW_DISTANCE_NEAR) return ARROW_RADIUS_MIN_FACTOR;
if (distance >= ARROW_DISTANCE_FAR) return ARROW_RADIUS_MAX_FACTOR;
float t = (float) ((distance - ARROW_DISTANCE_NEAR) / (ARROW_DISTANCE_FAR - ARROW_DISTANCE_NEAR));
t = Mth.clamp(t, 0.0f, 1.0f);
return Mth.lerp(t, ARROW_RADIUS_MIN_FACTOR, ARROW_RADIUS_MAX_FACTOR);
}
private static void renderArrow(Identifier textureId,
double x,
double y,
float angleRad,
int argb,
float scale) {
if (textureId == null) return;
Renderer2D.deferRenderThreadAction(() -> renderArrowImmediate(textureId, x, y, angleRad, argb, scale));
}
private static void renderArrowImmediate(Identifier textureId,
double x,
double y,
float angleRad,
int argb,
float scale) {
Minecraft mc = Minecraft.getInstance();
if (mc == null || mc.gameRenderer == null || mc.gameRenderer.mainRenderTarget() == null) return;
AbstractTexture tex = mc.getTextureManager().getTexture(textureId);
if (tex == null) return;
GpuTextureView view = tex.getTextureView();
GpuSampler sampler = tex.getSampler();
if (view == null || sampler == null) return;
RenderColor c = new RenderColor(argb);
ARROW_MESH.begin();
ARROW_MESH.ensureQuadCapacity();
int i1 = ARROW_MESH.vec2(ARROW_OFFSET_X * scale, ARROW_OFFSET_Y * scale)
.vec2(0, 0).color(c.r, c.g, c.b, c.a).next();
int i2 = ARROW_MESH.vec2(ARROW_OFFSET_X * scale, (ARROW_OFFSET_Y + ARROW_H) * scale)
.vec2(0, 1).color(c.r, c.g, c.b, c.a).next();
int i3 = ARROW_MESH.vec2((ARROW_OFFSET_X + ARROW_W) * scale, (ARROW_OFFSET_Y + ARROW_H) * scale)
.vec2(1, 1).color(c.r, c.g, c.b, c.a).next();
int i4 = ARROW_MESH.vec2((ARROW_OFFSET_X + ARROW_W) * scale, ARROW_OFFSET_Y * scale)
.vec2(1, 0).color(c.r, c.g, c.b, c.a).next();
ARROW_MESH.quad(i1, i2, i3, i4);
ARROW_MESH.end();
Matrix4f transform = new Matrix4f()
.translation((float) x, (float) y, 0.0f)
.rotateZ(angleRad);
MeshRenderer.begin()
.attachments(mc.gameRenderer.mainRenderTarget())
.pipeline(LoVisualRenderPipelines.UI_TEXTURED_ADDITIVE)
.mesh(ARROW_MESH)
.transform(transform)
.sampler("u_Texture", view, sampler)
.end();
}
@Override
public WorldPhase getWorldPhase() {
return WorldPhase.END_MAIN;
}
@Override
public HudPhase getHudPhase() {
return HudPhase.BEFORE_MISC_OVERLAYS;
}
@Override
public void onRenderWorldEngine(Renderer3D renderer, Renderer3D depthRenderer, float tickDelta) {
if (!isEnabled() || mc.level == null || mc.player == null) return;
if (!isLinesMode()) return;
Vec3 camPos = getActiveCameraPos(tickDelta);
Vec3 look = getActiveCameraLook(tickDelta);
Vec3 start = camPos.add(look.scale(10.0));
for (Player p : mc.level.players()) {
if (p == mc.player) continue;
if (!traceFriendsValue.get() && CategoryService.isFriend(p)) continue;
int c = resolveColor(p);
Vec3 targetWorld = obtainEntityLerpedPos(p, tickDelta);
renderer.line(
start.x, start.y, start.z,
targetWorld.x, targetWorld.y, targetWorld.z,
(c >> 16) & 255,
(c >> 8) & 255,
c & 255,
(c >> 24) & 255
);
}
}
@Override
public void onRenderHudEngineForeground(Renderer2D renderer, TextRenderer textRenderer, net.minecraft.client.gui.GuiGraphicsExtractor ctx, float tickDelta) {
if (!isEnabled() || mc.level == null || mc.player == null) return;
if (!isArrowsMode()) return;
if (!mc.options.getCameraType().isFirstPerson()) return;
Vec2 move = mc.player.input != null ? mc.player.input.getMoveVector() : Vec2.ZERO;
float moveX = move.x;
float moveZ = move.y;
float scale = ViewportContext.getScaleFactor();
float moveScale = moveRadiusBonusValue.get() ? 10.0f : 0.0f;
animatedYaw = AnimationUtility.fast(animatedYaw, moveX * moveScale * scale, 5.0f);
animatedPitch = AnimationUtility.fast(animatedPitch, moveZ * moveScale * scale, 5.0f);
yaw = AnimationUtility.fast(yaw, getActiveCameraYaw(), 10.0f);
float size = BASE_RADIUS;
if (ClientScreen.current() instanceof InventoryScreen) {
size += INVENTORY_RADIUS_BONUS;
}
if (moveRadiusBonusValue.get() && isMoving(move)) {
size += MOVE_RADIUS_BONUS;
}
animationStep = AnimationUtility.fast(animationStep, size * scale, 6.0f);
double centerX = mc.getWindow().getWidth() * 0.5;
double centerY = mc.getWindow().getHeight() * 0.5;
Vec3 camPos = getActiveCameraPos(tickDelta);
Identifier textureId = resolveArrowTexture();
for (Player player : mc.level.players()) {
if (player == mc.player) continue;
if (!traceFriendsValue.get() && CategoryService.isFriend(player)) continue;
Vec3 targetWorld = obtainEntityLerpedPos(player, tickDelta);
double relX = targetWorld.x - camPos.x;
double relZ = targetWorld.z - camPos.z;
double yawRad = Math.toRadians(yaw);
double cos = Math.cos(yawRad);
double sin = Math.sin(yawRad);
double rotY = -(relZ * cos - relX * sin);
double rotX = -(relX * cos + relZ * sin);
float angleDeg = (float) Math.toDegrees(Math.atan2(rotY, rotX));
double dist = targetWorld.distanceTo(camPos);
float radiusFactor = shouldUseAdaptiveRadius() ? radiusFactorForDistance(dist) : 1.0f;
double radius = animationStep * radiusFactor;
double x2 = radius * Mth.cos((float) Math.toRadians(angleDeg)) + centerX + animatedYaw;
double y2 = radius * Mth.sin((float) Math.toRadians(angleDeg)) + centerY + animatedPitch;
int color = withAlpha(resolveColor(player), ARROW_ALPHA);
float rotRad = (float) Math.toRadians(angleDeg + ARROW_ROTATION_OFFSET_DEG);
renderArrow(textureId, x2, y2, rotRad, color, scale);
}
}
private Vec3 getActiveCameraPos(float tickDelta) {
Freecam fc = Modules.get(Freecam.class);
if (fc != null && fc.isEnabled()) {
return new Vec3(
Mth.lerp(tickDelta, fc.camPosPrev.x, fc.camPos.x),
Mth.lerp(tickDelta, fc.camPosPrev.y, fc.camPos.y),
Mth.lerp(tickDelta, fc.camPosPrev.z, fc.camPos.z)
);
}
return mc.gameRenderer.mainCamera().position();
}
private Vec3 getActiveCameraLook(float tickDelta) {
Freecam fc = Modules.get(Freecam.class);
if (fc != null && fc.isEnabled()) {
float yaw = (float) Math.toRadians(fc.camYaw);
float pitch = (float) Math.toRadians(fc.camPitch);
return new Vec3(
-Math.sin(yaw) * Math.cos(pitch),
-Math.sin(pitch),
Math.cos(yaw) * Math.cos(pitch)
).normalize();
}
return mc.player.getViewVector(tickDelta).normalize();
}
private float getActiveCameraYaw() {
Freecam fc = Modules.get(Freecam.class);
if (fc != null && fc.isEnabled()) {
return fc.camYaw;
}
return mc.gameRenderer.mainCamera().yRot();
}
public void render3DTracersRaw(
Renderer3D renderer,
float tickDelta,
List<Vec3> targets,
int argb
) {
if (mc.player == null || mc.level == null || targets == null || targets.isEmpty()) return;
Vec3 camPos = getActiveCameraPos(tickDelta);
Vec3 look = getActiveCameraLook(tickDelta);
Vec3 start = camPos.add(look.scale(10.0));
int a = (argb >> 24) & 255;
int r = (argb >> 16) & 255;
int g = (argb >> 8) & 255;
int b = argb & 255;
for (Vec3 targetWorld : targets) {
renderer.line(
start.x, start.y, start.z,
targetWorld.x, targetWorld.y, targetWorld.z,
r, g, b, a
);
}
}
private Vec3 obtainEntityLerpedPos(Entity e, float f) {
try {
return e.getPosition(f);
} catch (Throwable t) {
if (e instanceof IEntity a) {
return a.get$InstantRenderPos().lerp(e.position(), f);
}
return e.position();
}
}
private int resolveColor(Player p) {
return CategoryService.getColor(p);
}
private boolean shouldUseAdaptiveRadius() {
if (mc.player == null) return true;
return !(ClientScreen.current() instanceof InventoryScreen);
}
private Identifier resolveArrowTexture() {
String mode = arrowTexture.get();
if ("Arrow2".equalsIgnoreCase(mode)) return ARROW_2;
if ("Arrow3".equalsIgnoreCase(mode)) return ARROW_3;
if ("Arrow4".equalsIgnoreCase(mode)) return ARROW_4;
return ARROW_1;
}
private boolean isLinesMode() {
return "Lines".equalsIgnoreCase(tracersMode.get());
}
private boolean isArrowsMode() {
return "Arrows".equalsIgnoreCase(tracersMode.get());
}
}

View file

@ -23,6 +23,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import dev.loki.lovisual.features.command.CommandManager;
import dev.loki.lovisual.features.gui.chat.BetterChatRenderer;
import dev.loki.lovisual.features.gui.chat.BetterChatTabHandler;
import dev.loki.lovisual.features.gui.hud.draggable.impl.BetterChat;
import dev.loki.lovisual.mixins.accessors.ChatScreenSuggestorAccessor;
import dev.loki.lovisual.Lifecycle;
@ -92,6 +93,15 @@ public class ChatScreenMixin {
}
}
@Inject(method = "keyPressed(Lnet/minecraft/client/input/KeyEvent;)Z", at = @At("HEAD"), cancellable = true, require = 0)
private void lovisual$betterChat$tabCompleteCommands(KeyEvent input, CallbackInfoReturnable<Boolean> cir) {
if (!Lifecycle.canRunHud()) return;
if (!BetterChat.isActive()) return;
if (BetterChatTabHandler.handleKey(input)) {
cir.setReturnValue(true);
}
}
@Unique
private boolean lovisual$betterChat$shouldHideVanillaChat() {
if (!Lifecycle.canRunHud()) return false;

View file

@ -8,28 +8,19 @@
package dev.loki.lovisual.mixins;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ItemParticleOption;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import dev.loki.lovisual.features.module.Modules;
import dev.loki.lovisual.features.module.modules.visuals.NoRender;
import dev.loki.lovisual.util.block.BlockObservationHub;
@Mixin(ClientLevel.class)
public abstract class ClientLevelMixin {
@Inject(method = "setServerVerifiedBlockState", at = @At("TAIL"))
private void lovisual$recordBlockUpdate(BlockPos pos, BlockState state, @Block.UpdateFlags int flags, CallbackInfo ci) {
BlockObservationHub.observeWorldUpdate(pos, state);
}
@Inject(
method = "doAddParticle(Lnet/minecraft/core/particles/ParticleOptions;ZZDDDDDD)V",
at = @At("HEAD"),

View file

@ -20,9 +20,6 @@ public abstract class LivingEntityRenderStateMixin implements ViewObstructionFad
@Unique
private float lovisual$viewObstructionFadeAlpha = 1.0f;
@Unique
private boolean lovisual$seeInvisibleFadeActive;
@Override
public boolean lovisual$isViewObstructionFadeActive() {
return lovisual$viewObstructionFadeActive;
@ -42,14 +39,4 @@ public abstract class LivingEntityRenderStateMixin implements ViewObstructionFad
public void lovisual$setViewObstructionFadeAlpha(float alpha) {
this.lovisual$viewObstructionFadeAlpha = alpha;
}
@Override
public boolean lovisual$isSeeInvisibleFadeActive() {
return lovisual$seeInvisibleFadeActive;
}
@Override
public void lovisual$setSeeInvisibleFadeActive(boolean active) {
this.lovisual$seeInvisibleFadeActive = active;
}
}

View file

@ -34,7 +34,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import dev.loki.lovisual.features.module.Modules;
import dev.loki.lovisual.features.module.modules.visuals.NameTags;
import dev.loki.lovisual.features.module.modules.visuals.NoRender;
import dev.loki.lovisual.features.module.modules.visuals.SeeInvisibles;
import dev.loki.lovisual.render.ViewObstructionFadeContext;
import dev.loki.lovisual.render.ViewObstructionFadeState;
import dev.loki.lovisual.render.iris.IrisRuntime;
@ -75,22 +74,10 @@ public abstract class LivingEntityRendererMixin<T extends LivingEntity, S extend
float obstructionFadeAlpha = obstructionFadeActive ? noRender.getEntityFadeAlpha(entity) : 1.0f;
obstructionFadeActive = obstructionFadeActive && obstructionFadeAlpha < 0.99f;
SeeInvisibles seeInvisibles = Modules.get(SeeInvisibles.class);
boolean seeInvisibleActive = seeInvisibles != null && seeInvisibles.shouldRenderInvisiblePlayer(entity);
float seeInvisibleAlpha = seeInvisibleActive ? seeInvisibles.getInvisiblePlayerAlpha01() : 1.0f;
boolean seeInvisibleFadeActive = seeInvisibleActive && seeInvisibleAlpha < 0.99f;
if (seeInvisibleActive) {
state.isInvisibleToPlayer = false;
}
boolean active = obstructionFadeActive || seeInvisibleFadeActive;
boolean active = obstructionFadeActive;
float alpha = obstructionFadeAlpha;
if (seeInvisibleFadeActive) {
alpha = Math.min(alpha, seeInvisibleAlpha);
}
fadeState.lovisual$setViewObstructionFadeActive(active);
fadeState.lovisual$setViewObstructionFadeAlpha(active ? alpha : 1.0f);
fadeState.lovisual$setSeeInvisibleFadeActive(seeInvisibleFadeActive);
}
@ModifyExpressionValue(
@ -169,21 +156,6 @@ public abstract class LivingEntityRendererMixin<T extends LivingEntity, S extend
return RenderTypes.entityTranslucent(getTextureLocation(state), true);
}
@ModifyConstant(
method = "submit",
constant = @Constant(intValue = 654311423)
)
private int lovisual$seeInvisibles$overrideInvisiblePlayerAlpha(int vanillaTint, S state) {
SeeInvisibles seeInvisibles = Modules.get(SeeInvisibles.class);
if (seeInvisibles != null
&& state instanceof ViewObstructionFadeState fadeState
&& fadeState.lovisual$isSeeInvisibleFadeActive()
&& seeInvisibles.shouldRenderInvisiblePlayer(state.entityType, state.isInvisible)) {
return seeInvisibles.getInvisiblePlayerTintArgb();
}
return vanillaTint;
}
@Inject(method = "submit", at = @At("HEAD"))
private void lovisual$pushViewObstructionFadeContext(S state, com.mojang.blaze3d.vertex.PoseStack matrixStack, net.minecraft.client.renderer.SubmitNodeCollector orderedRenderCommandQueue, net.minecraft.client.renderer.state.level.CameraRenderState cameraRenderState, CallbackInfo ci) {
ViewObstructionFadeContext.push(state instanceof ViewObstructionFadeState fadeState ? fadeState : null);

View file

@ -1,64 +0,0 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.mixins.sodium;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.Material;
import net.caffeinemc.mods.sodium.client.render.model.MutableQuadViewImpl;
import net.minecraft.client.renderer.block.dispatch.BlockStateModel;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import dev.loki.lovisual.util.block.BlockObservationHub;
import java.util.ArrayDeque;
@Pseudo
@Mixin(targets = "net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.BlockRenderer")
public abstract class SodiumBlockVisibilityMixin {
@Unique
private static final ThreadLocal<ArrayDeque<RenderedBlock>> LOVISUAL_BLOCK_STACK =
ThreadLocal.withInitial(ArrayDeque::new);
@Inject(method = "renderModel", at = @At("HEAD"), remap = false)
private void lovisual$pushRenderedBlock(BlockStateModel model, BlockState state, BlockPos pos, BlockPos origin, CallbackInfo ci) {
LOVISUAL_BLOCK_STACK.get().addLast(new RenderedBlock(pos, state));
}
@Inject(method = "renderModel", at = @At("RETURN"), remap = false)
private void lovisual$popRenderedBlock(BlockStateModel model, BlockState state, BlockPos pos, BlockPos origin, CallbackInfo ci) {
ArrayDeque<RenderedBlock> stack = LOVISUAL_BLOCK_STACK.get();
if (!stack.isEmpty()) {
stack.removeLast();
}
if (stack.isEmpty()) {
LOVISUAL_BLOCK_STACK.remove();
}
}
@Inject(method = "bufferQuad", at = @At("HEAD"), remap = false)
private void lovisual$observeBufferedQuad(MutableQuadViewImpl quad,
float[] brightness,
Material material,
CallbackInfo ci) {
ArrayDeque<RenderedBlock> stack = LOVISUAL_BLOCK_STACK.get();
if (stack.isEmpty()) {
return;
}
RenderedBlock rendered = stack.peekLast();
BlockObservationHub.observeSodiumRenderedBlock(rendered.pos(), rendered.state());
}
private record RenderedBlock(BlockPos pos, BlockState state) {
}
}

View file

@ -1,35 +0,0 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.mixins.sodium;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import dev.loki.lovisual.util.block.BlockObservationHub;
@Pseudo
@Mixin(targets = "net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderMeshingTask")
public abstract class SodiumChunkBuilderMeshingTaskMixin {
@ModifyVariable(
method = "execute(Lnet/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildContext;Lnet/caffeinemc/mods/sodium/client/util/task/CancellationToken;)Lnet/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildOutput;",
at = @At("STORE"),
ordinal = 0,
remap = false
)
private BlockState lovisual$captureSodiumSectionBlock(BlockState state,
@Local(index = 22) int x,
@Local(index = 20) int y,
@Local(index = 21) int z) {
BlockObservationHub.observeSodiumBlock(x, y, z, state);
return state;
}
}

View file

@ -15,8 +15,4 @@ public interface ViewObstructionFadeState {
void lovisual$setViewObstructionFadeActive(boolean active);
void lovisual$setViewObstructionFadeAlpha(float alpha);
boolean lovisual$isSeeInvisibleFadeActive();
void lovisual$setSeeInvisibleFadeActive(boolean active);
}

View file

@ -1,177 +0,0 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.util.block;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Hot-path Sodium bridge for BlockESP.
*
* <p>This class is intentionally primitive-only on the meshing path: no module lookup,
* no setting reads, no BlockPos allocation unless a configured block actually matched.</p>
*/
public enum BlockEspSodiumCandidateCollector {
;
private static final int MAX_QUEUED_CANDIDATES = 32768;
private static final Snapshot DISABLED = new Snapshot(false, Set.of(), false, 0, 0, 0, 0, 0);
private static final ConcurrentLinkedQueue<Long> CANDIDATE_KEYS = new ConcurrentLinkedQueue<>();
private static final Map<Long, Candidate> PENDING_CANDIDATES = new ConcurrentHashMap<>();
private static final AtomicInteger QUEUED_COUNT = new AtomicInteger();
private static volatile Snapshot snapshot = DISABLED;
public static void publishSnapshot(
boolean enabled,
Set<Block> targetBlocks,
boolean limitDistance,
int maxDistance,
int centerX,
int centerY,
int centerZ,
int generation
) {
if (!enabled || targetBlocks == null || targetBlocks.isEmpty()) {
snapshot = DISABLED;
clear();
return;
}
snapshot = new Snapshot(
true,
Set.copyOf(targetBlocks),
limitDistance,
Math.max(1, maxDistance),
centerX,
centerY,
centerZ,
generation
);
}
public static void disable() {
snapshot = DISABLED;
clear();
}
public static void clear() {
CANDIDATE_KEYS.clear();
PENDING_CANDIDATES.clear();
QUEUED_COUNT.set(0);
}
public static void observeSodiumBlock(int x, int y, int z, BlockState state) {
offer(x, y, z, state, CandidateSource.SODIUM_SECTION_SCAN);
}
public static void observeSodiumRenderedBlock(int x, int y, int z, BlockState state) {
offer(x, y, z, state, CandidateSource.SODIUM_BUFFERED_QUAD);
}
private static void offer(int x, int y, int z, BlockState state, CandidateSource source) {
Snapshot current = snapshot;
if (!current.enabled || state == null || state.isAir()) {
return;
}
if (!current.targets.contains(state.getBlock())) {
return;
}
if (current.limitDistance && !current.withinDistance(x, y, z)) {
return;
}
long packedPos = BlockPos.asLong(x, y, z);
Candidate candidate = new Candidate(packedPos, state, source, current.generation);
while (true) {
Candidate pending = PENDING_CANDIDATES.putIfAbsent(packedPos, candidate);
if (pending == null) {
break;
}
if (source != CandidateSource.SODIUM_BUFFERED_QUAD
|| pending.source == CandidateSource.SODIUM_BUFFERED_QUAD) {
return;
}
if (PENDING_CANDIDATES.replace(packedPos, pending, candidate)) {
return;
}
}
int queued = QUEUED_COUNT.incrementAndGet();
if (queued > MAX_QUEUED_CANDIDATES) {
QUEUED_COUNT.decrementAndGet();
PENDING_CANDIDATES.remove(packedPos, candidate);
return;
}
CANDIDATE_KEYS.add(packedPos);
}
public static int drain(int maxCandidates, CandidateConsumer consumer) {
if (maxCandidates <= 0 || consumer == null) {
return 0;
}
int drained = 0;
while (drained < maxCandidates) {
Long packedPos = CANDIDATE_KEYS.poll();
if (packedPos == null) {
break;
}
Candidate candidate = PENDING_CANDIDATES.remove(packedPos);
if (candidate == null) {
continue;
}
QUEUED_COUNT.decrementAndGet();
consumer.accept(candidate);
drained++;
}
return drained;
}
public enum CandidateSource {
SODIUM_SECTION_SCAN,
SODIUM_BUFFERED_QUAD
}
@FunctionalInterface
public interface CandidateConsumer {
void accept(Candidate candidate);
}
private record Snapshot(
boolean enabled,
Set<Block> targets,
boolean limitDistance,
int maxDistance,
int centerX,
int centerY,
int centerZ,
int generation
) {
boolean withinDistance(int x, int y, int z) {
long dx = (long) x - centerX;
long dy = (long) y - centerY;
long dz = (long) z - centerZ;
long max = maxDistance;
return dx * dx + dy * dy + dz * dz <= max * max;
}
}
public record Candidate(long packedPos, BlockState state, CandidateSource source, int generation) {
}
}

View file

@ -1,103 +0,0 @@
/*
* This file is part of the LoVisual Client distribution.
* Copyright (c) 2026 pivosos2007.
*
* Licensed under the GNU General Public License v3.0.
*/
package dev.loki.lovisual.util.block;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;
import dev.loki.lovisual.features.module.Modules;
import dev.loki.lovisual.features.module.modules.visuals.BedwarsESP;
import dev.loki.lovisual.features.module.modules.visuals.BlockESP;
import dev.loki.lovisual.util.block.bed.BedBlockUtil;
public enum BlockObservationHub {
;
public static void requestSodiumRebuild(Minecraft client, int requestedRadius) {
if (client == null || client.level == null || client.player == null) {
return;
}
if (!FabricLoader.getInstance().isModLoaded("sodium")) {
return;
}
try {
Class<?> cls = Class.forName("net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer");
Object renderer = cls.getMethod("instanceNullable").invoke(null);
if (renderer == null) {
return;
}
int renderRadius = (client.options.getEffectiveRenderDistance() + 1) * 16;
int radius = Math.max(1, Math.min(requestedRadius, renderRadius));
BlockPos center = client.player.blockPosition();
cls.getMethod(
"scheduleRebuildForBlockArea",
int.class, int.class, int.class, int.class, int.class, int.class, boolean.class
).invoke(
renderer,
center.getX() - radius,
client.level.getMinY(),
center.getZ() - radius,
center.getX() + radius,
client.level.getMaxY(),
center.getZ() + radius,
true
);
} catch (Throwable ignored) {
// Sodium отсутствует или изменился API — молча пропускаем
}
}
public static void observeSodiumBlock(int x, int y, int z, BlockState state) {
if (state == null || state.isAir()) {
return;
}
BlockEspSodiumCandidateCollector.observeSodiumBlock(x, y, z, state);
if (BedBlockUtil.isBed(state)) {
BedwarsESP bedwarsEsp = Modules.get(BedwarsESP.class);
if (bedwarsEsp != null && bedwarsEsp.isEnabled()) {
bedwarsEsp.acceptSodiumBlockState(x, y, z, state);
}
}
}
public static void observeSodiumRenderedBlock(BlockPos pos, BlockState state) {
if (pos == null || state == null || state.isAir()) {
return;
}
BlockEspSodiumCandidateCollector.observeSodiumRenderedBlock(pos.getX(), pos.getY(), pos.getZ(), state);
if (BedBlockUtil.isBed(state)) {
BedwarsESP bedwarsEsp = Modules.get(BedwarsESP.class);
if (bedwarsEsp != null && bedwarsEsp.isEnabled()) {
bedwarsEsp.acceptSodiumRenderedBlockState(pos, state);
}
}
}
public static void observeWorldUpdate(BlockPos pos, BlockState state) {
if (pos == null || state == null) {
return;
}
BlockESP blockEsp = Modules.get(BlockESP.class);
if (blockEsp != null && blockEsp.isEnabled()) {
blockEsp.acceptWorldBlockUpdate(pos, state);
}
BedwarsESP bedwarsEsp = Modules.get(BedwarsESP.class);
if (bedwarsEsp != null && bedwarsEsp.isEnabled()) {
bedwarsEsp.acceptWorldBlockUpdate(pos, state);
}
}
}

View file

@ -545,7 +545,6 @@
"setting.freecam.vertical_speed": "Vertical speed",
"setting.freecam.toggle_input": "Toggle input",
"setting.fullbright.min_light": "Min light",
"setting.seeinvisibles.alpha": "Alpha",
"setting.soundesp.name_mode": "Name mode",
"setting.soundesp.name_mode.option.Technical": "Technical",
"setting.soundesp.name_mode.option.Localized": "Localized",
@ -833,20 +832,6 @@
"setting.crosshair.orbiz_move_multiplier": "Move multiplier",
"setting.crosshair.orbiz_motion_limit": "Motion limit",
"setting.crosshair.orbiz_motion_smoothing": "Motion smoothing",
"setting.tracers.trace_friends": "Trace friends",
"setting.tracers.move_radius_bonus": "Movement radius bonus",
"setting.tracers.mode": "Mode",
"setting.tracers.mode.option.Lines": "Lines",
"setting.tracers.mode.option.Arrows": "Arrows",
"setting.tracers.arrow_texture": "Arrow texture",
"setting.tracers.arrow_texture.option.Arrow": "Arrow 1",
"setting.tracers.arrow_texture.option.Arrow2": "Arrow 2",
"setting.tracers.arrow_texture.option.Arrow3": "Arrow 3",
"setting.tracers.arrow_texture.option.Arrow4": "Arrow 4",
"setting.tracers.default_color": "Default color",
"setting.tracers.friend_color": "Friend color",
"setting.tracers.enemy_color": "Enemy color",
"setting.tracers.staff_color": "Staff color",
"setting.trails.only_self": "Only self",
"setting.trails.hide_first_person": "Hide in first person",
"setting.trails.depth_test": "Depth test",
@ -1309,21 +1294,8 @@
"setting.blockhighlight.breaking_animation.option.static": "Static",
"setting.blockhighlight.breaking_color": "Breaking color",
"setting.blockhighlight.breaking_color2": "Second breaking color",
"setting.bedwarsesp.self_bed": "Self bed",
"setting.bedwarsesp.self_bed.option.none": "None",
"setting.bedwarsesp.self_bed.option.color": "By color",
"setting.bedwarsesp.self_bed.option.spawn_location": "By spawn",
"setting.bedwarsesp.spawn_bed_distance": "Spawn bed distance",
"setting.bedwarsesp.max_layers": "Max layers",
"setting.bedwarsesp.show_openings": "Show openings",
"setting.bedwarsesp.self_bed_color": "Self bed color",
"setting.bedwarsesp.other_bed_color": "Other bed color",
"setting.bedwarsesp.self_opening_color": "Self opening color",
"setting.bedwarsesp.other_opening_color": "Other opening color",
"setting.blockesp.search_blocks": "Search blocks",
"setting.blockesp.visible_color": "Visible color",
"setting.blockesp.chunk_color": "Chunk color",
"setting.blockesp.cluster_tracers": "Cluster tracers",
"setting.invisesp.color": "Color",
"setting.invisesp.line_width": "Line width",
"setting.holeesp.range_xz": "Range XZ",
"setting.holeesp.range_y": "Range Y",
"setting.holeesp.height": "Height",
@ -1938,6 +1910,7 @@
"setting.draggable.triangulator.triangulator_cache_lifetime": "Cache lifetime",
"setting.draggable.triangulator.triangulator_auto_reset": "Auto reset",
"module.autobed.description": "Automatically places and detonates beds in explosive dimensions.",
"module.invisesp.description": "Outlines players hidden by invisibility.",
"module.holeesp.description": "Highlights safe holes with a distance-faded fill.",
"setting.autobed.place": "Place",
"setting.autobed.explode": "Explode",
@ -3221,5 +3194,19 @@
"command.toggle.description": "Enables, disables, or toggles a module.",
"command.tps.description": "Shows estimated server ticks per second.",
"command.username.description": "Shows the current Minecraft username and UUID.",
"command.xaero.description": "Adds, lists, or removes Xaero waypoints."
"command.xaero.description": "Adds, lists, or removes Xaero waypoints.",
"module.chinahat.description": "Renders a classic conical Chinese hat on players' heads.",
"setting.chinahat.render_target": "Render",
"setting.chinahat.render_target.option.self": "Self",
"setting.chinahat.render_target.option.players": "Players",
"setting.chinahat.render_target.option.both": "Both",
"setting.chinahat.rim_color": "Rim color",
"setting.chinahat.segments": "Segments",
"setting.chinahat.brim_radius": "Brim radius",
"setting.chinahat.dome_radius": "Dome radius",
"setting.chinahat.dome_height": "Dome height",
"setting.chinahat.brim_thickness": "Brim thickness",
"setting.chinahat.head_top_offset": "Head height",
"setting.chinahat.head_forward_offset": "Forward offset",
"setting.chinahat.head_right_offset": "Right offset"
}

View file

@ -480,7 +480,6 @@
"setting.freecam.vertical_speed": "Вертикальная скорость",
"setting.freecam.toggle_input": "Переключить ввод",
"setting.fullbright.min_light": "Мин. свет",
"setting.seeinvisibles.alpha": "Альфа",
"setting.soundesp.name_mode": "Режим имени",
"setting.soundesp.name_mode.option.Technical": "Технический",
"setting.soundesp.name_mode.option.Localized": "Локализованный",
@ -768,20 +767,6 @@
"setting.crosshair.orbiz_move_multiplier": "Множитель движения",
"setting.crosshair.orbiz_motion_limit": "Лимит движения",
"setting.crosshair.orbiz_motion_smoothing": "Сглаживание движения",
"setting.tracers.trace_friends": "Трейсить друзей",
"setting.tracers.move_radius_bonus": "Бонус радиуса от движения",
"setting.tracers.mode": "Режим",
"setting.tracers.mode.option.Lines": "Линии",
"setting.tracers.mode.option.Arrows": "Стрелки",
"setting.tracers.arrow_texture": "Текстура стрелки",
"setting.tracers.arrow_texture.option.Arrow": "Стрела 1",
"setting.tracers.arrow_texture.option.Arrow2": "Стрела 2",
"setting.tracers.arrow_texture.option.Arrow3": "Стрела 3",
"setting.tracers.arrow_texture.option.Arrow4": "Стрела 4",
"setting.tracers.default_color": "Цвет обычных",
"setting.tracers.friend_color": "Цвет друзей",
"setting.tracers.enemy_color": "Цвет врагов",
"setting.tracers.staff_color": "Цвет стаффа",
"setting.trails.only_self": "Только себя",
"setting.trails.hide_first_person": "Скрывать в первом лице",
"setting.trails.depth_test": "Глубинный тест",
@ -1103,22 +1088,9 @@
"setting.blockhighlight.breaking_animation.option.static": "Статичная",
"setting.blockhighlight.breaking_color": "Цвет ломания",
"setting.blockhighlight.breaking_color2": "Второй цвет ломания",
"setting.bedwarsesp.self_bed": "Своя кровать",
"setting.bedwarsesp.self_bed.option.none": "Нет",
"setting.bedwarsesp.self_bed.option.color": "По цвету",
"setting.bedwarsesp.self_bed.option.spawn_location": "По спавну",
"setting.bedwarsesp.spawn_bed_distance": "Дистанция кровати от спавна",
"setting.bedwarsesp.max_layers": "Макс. слоёв",
"setting.bedwarsesp.show_openings": "Показывать вскрытия",
"setting.bedwarsesp.self_bed_color": "Цвет своей кровати",
"setting.bedwarsesp.other_bed_color": "Цвет чужой кровати",
"setting.bedwarsesp.self_opening_color": "Цвет вскрытия своей кровати",
"setting.bedwarsesp.other_opening_color": "Цвет вскрытия чужой кровати",
"setting.aspectratio.aspect_multiplier": "Множитель соотношения сторон",
"setting.blockesp.search_blocks": "Искомые блоки",
"setting.blockesp.visible_color": "Цвет видимых",
"setting.blockesp.chunk_color": "Цвет чанка",
"setting.blockesp.cluster_tracers": "Кластерные трейсеры",
"setting.invisesp.color": "Цвет",
"setting.invisesp.line_width": "Толщина линии",
"setting.holeesp.range_xz": "Радиус XZ",
"setting.holeesp.range_y": "Радиус Y",
"setting.holeesp.height": "Высота",
@ -1733,6 +1705,7 @@
"setting.draggable.triangulator.triangulator_cache_lifetime": "Время жизни кеша",
"setting.draggable.triangulator.triangulator_auto_reset": "Автосброс",
"module.autobed.description": "Автоматически ставит и взрывает кровати в измерениях, где они взрываются.",
"module.invisesp.description": "Обводит игроков, скрытых невидимостью.",
"module.holeesp.description": "Подсвечивает безопасные холки с фейдом по дистанции.",
"setting.autobed.place": "Ставить",
"setting.autobed.explode": "Взрывать",
@ -3011,5 +2984,19 @@
"command.toggle.description": "Включает, отключает или переключает модуль.",
"command.tps.description": "Показывает расчётное количество серверных тиков в секунду.",
"command.username.description": "Показывает текущее имя Minecraft и UUID.",
"command.xaero.description": "Добавляет, показывает или удаляет точки Xaero."
"command.xaero.description": "Добавляет, показывает или удаляет точки Xaero.",
"module.chinahat.description": "Рисует классическую коническую китайскую шляпу на головах игроков.",
"setting.chinahat.render_target": "Рендер",
"setting.chinahat.render_target.option.self": "Себе",
"setting.chinahat.render_target.option.players": "Игрокам",
"setting.chinahat.render_target.option.both": "Всем",
"setting.chinahat.rim_color": "Цвет обода",
"setting.chinahat.segments": "Сегменты",
"setting.chinahat.brim_radius": "Радиус полей",
"setting.chinahat.dome_radius": "Радиус купола",
"setting.chinahat.dome_height": "Высота купола",
"setting.chinahat.brim_thickness": "Толщина полей",
"setting.chinahat.head_top_offset": "Высота на голове",
"setting.chinahat.head_forward_offset": "Смещение вперёд",
"setting.chinahat.head_right_offset": "Смещение вправо"
}

View file

@ -171,9 +171,7 @@
"security.TranslatableTextContentMixin",
"security.TranslationStorageMixin",
"sodium.SodiumBlockRendererMixin",
"sodium.SodiumBlockVisibilityMixin",
"sodium.SodiumChunkMeshFormatsMixin",
"sodium.SodiumChunkBuilderMeshingTaskMixin",
"sodium.SodiumChunkVertexMixin",
"sodium.SodiumShaderChunkRendererMixin",
"sodium.SodiumVertexConsumerTrackerMixin",