- Выберите загрузчик игры
- Fabric
Иишка, надо допилить(лень убирать подписи от ии)
Java:
package fun.rich.features.impl.render;
import fun.rich.features.module.Module;
import fun.rich.features.module.ModuleCategory;
import fun.rich.features.module.setting.implement.BooleanSetting;
import fun.rich.features.module.setting.implement.ColorSetting;
import fun.rich.features.module.setting.implement.SelectSetting;
import fun.rich.features.module.setting.implement.SliderSettings;
import fun.rich.utils.client.managers.event.EventHandler;
import fun.rich.events.render.WorldRenderEvent;
import fun.rich.utils.display.color.ColorAssist;
import fun.rich.utils.display.geometry.Render3D;
import fun.rich.utils.client.Instance;
import net.minecraft.client.render.Camera;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.math.Vec3d;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.gl.ShaderProgramKeys;
import net.minecraft.client.render.*;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.RotationAxis;
import org.joml.Matrix4f;
import org.joml.Vector4i;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Iterator;
@FieldDefaults(level = AccessLevel.PRIVATE)
public class LineGlyphs extends Module {
public static LineGlyphs getInstance() {
return Instance.get(LineGlyphs.class);
}
BooleanSetting glowing = new BooleanSetting("Свечение", "Добавляет свечение линиям").setValue(true);
BooleanSetting dashed = new BooleanSetting("Пунктир", "Делает линии пунктирными").setValue(false);
SelectSetting colorMode = new SelectSetting("Режим цвета", "Выбор режима цвета").value("Client", "Picker", "DoublePicker").selected("Client");
ColorSetting pickColor1 = new ColorSetting("Цвет 1", "Первый цвет").value(ColorAssist.getColor(100, 200, 255)).visible(() -> colorMode.getSelected().contains("Picker"));
ColorSetting pickColor2 = new ColorSetting("Цвет 2", "Второй цвет").value(ColorAssist.getColor(255, 100, 200)).visible(() -> colorMode.getSelected().equals("DoublePicker"));
final List<FallingLine> lines = new ArrayList<>();
final Random random = new Random();
// Константы (бывшие настройки)
private static final int LINE_COUNT = 120; // Увеличено с 85 до 120
private static final float FALL_SPEED = 0.07f;
private static final float LINE_LENGTH = 17f;
private static final float SEGMENT_LENGTH = 2f;
private static final float ZIGZAG_WIDTH = 5f;
public LineGlyphs() {
super("LineGlyphs", "Line Glyphs", ModuleCategory.RENDER);
setup(glowing, dashed, colorMode, pickColor1, pickColor2);
}
@Override
public void activate() {
super.activate();
generateLines();
}
@Override
public void deactivate() {
super.deactivate();
lines.clear();
}
private void generateLines() {
lines.clear();
if (mc.player == null) return;
for (int i = 0; i < LINE_COUNT; i++) {
lines.add(new FallingLine(random, mc.player.getPos()));
}
}
@EventHandler
public void onWorldRender(WorldRenderEvent e) {
if (mc.player == null) return;
// Подсчитываем сколько линий нужно заспавнить
int linesToSpawn = LINE_COUNT - lines.size();
// Обновляем линии и удаляем те, что нужно респавнить
Iterator<FallingLine> iterator = lines.iterator();
while (iterator.hasNext()) {
FallingLine line = iterator.next();
line.update(mc.player.getPos(), FALL_SPEED, SEGMENT_LENGTH, ZIGZAG_WIDTH, LINE_LENGTH);
// Если линия должна респавниться - удаляем и увеличиваем счетчик
if (line.shouldRespawn(mc.player.getPos())) {
iterator.remove();
linesToSpawn++;
}
}
// Спавним все нужные линии сразу (мгновенно)
for (int i = 0; i < linesToSpawn; i++) {
lines.add(new FallingLine(random, mc.player.getPos()));
}
// Рендерим линии
renderLines(e.getStack());
}
private void renderLines(MatrixStack stack) {
if (lines.isEmpty() || mc.gameRenderer == null) return;
Camera camera = mc.gameRenderer.getCamera();
Vec3d camPos = camera.getPos();
// Рендерим каждую линию
for (int lineIndex = 0; lineIndex < lines.size(); lineIndex++) {
FallingLine line = lines.get(lineIndex);
if (line.points.size() < 2) continue;
int color = getColor(lineIndex);
// Рисуем линию между всеми точками
for (int i = 0; i < line.points.size() - 1; i++) {
Vec3d start = line.points.get(i);
Vec3d end = line.points.get(i + 1);
// Проверяем, не слишком ли далеко от камеры
if (start.distanceTo(camPos) > 60 || end.distanceTo(camPos) > 60) continue;
// Вычисляем альфу для каждого сегмента (затухание к началу следа)
float segmentAlpha = (float)(i + 1) / line.points.size();
int segmentColor = ColorAssist.setAlpha(color, (int)(segmentAlpha * 255));
// Рисуем линию с эффектом свечения если включено
float width = glowing.isValue() ? 2.5f : 1.5f;
// Если включен пунктир - рисуем сегментами
if (dashed.isValue()) {
drawDashedLine(start, end, segmentColor, width);
if (glowing.isValue()) {
int glowColor = ColorAssist.setAlpha(color, (int)(segmentAlpha * 100));
drawDashedLine(start, end, glowColor, 4.0f);
}
} else {
// Используем Render3D для рендеринга сплошных линий
Render3D.drawLine(start, end, segmentColor, width, true);
// Если включено свечение - рисуем дополнительную толстую линию с меньшей прозрачностью
if (glowing.isValue()) {
int glowColor = ColorAssist.setAlpha(color, (int)(segmentAlpha * 100));
Render3D.drawLine(start, end, glowColor, 4.0f, true);
}
}
}
// Рисуем кружки в точках поворота
renderTurnCircles(stack, line, color, camPos);
}
}
private void drawDashedLine(Vec3d start, Vec3d end, int color, float width) {
// Параметры пунктира
double dashLength = 0.15; // Длина штриха
double gapLength = 0.1; // Длина пробела
double totalPattern = dashLength + gapLength;
// Вычисляем направление и длину линии
Vec3d direction = end.subtract(start);
double lineLength = direction.length();
Vec3d normalizedDir = direction.normalize();
// Рисуем штрихи
double currentDistance = 0;
while (currentDistance < lineLength) {
double dashEnd = Math.min(currentDistance + dashLength, lineLength);
Vec3d dashStart = start.add(normalizedDir.multiply(currentDistance));
Vec3d dashEndPos = start.add(normalizedDir.multiply(dashEnd));
Render3D.drawLine(dashStart, dashEndPos, color, width, true);
currentDistance += totalPattern;
}
}
private void renderTurnCircles(MatrixStack stack, FallingLine line, int color, Vec3d camPos) {
if (line.turnPoints.isEmpty() || mc.gameRenderer == null) return;
Camera camera = mc.gameRenderer.getCamera();
float pitch = camera.getPitch();
float yaw = camera.getYaw();
// Рисуем bloom частицу в каждой точке поворота (как в WorldParticles)
for (Vec3d turnPoint : line.turnPoints) {
// Проверяем расстояние от камеры
if (turnPoint.distanceTo(camPos) > 60) continue;
// Вычисляем альфу для частицы (затухание для старых точек)
float alpha = 1.0f;
if (!line.points.isEmpty()) {
Vec3d lastPoint = line.points.get(line.points.size() - 1);
double distanceFromEnd = turnPoint.distanceTo(lastPoint);
alpha = Math.max(0.3f, 1.0f - (float)(distanceFromEnd / LINE_LENGTH));
}
int particleColor = ColorAssist.setAlpha(color, (int)(alpha * 255));
float size = 0.2f;
// Позиция относительно камеры
double posX = turnPoint.x - camPos.x;
double posY = turnPoint.y - camPos.y;
double posZ = turnPoint.z - camPos.z;
// Создаем матрицу для частицы (как в WorldParticles)
MatrixStack matrices = new MatrixStack();
matrices.push();
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(pitch));
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw + 180.0F));
matrices.translate(posX, posY, posZ);
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(-yaw));
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(pitch));
// Используем Render3D.drawTexture как в WorldParticles
Render3D.drawTexture(
matrices.peek(),
Identifier.of("textures/features/particles/bloom.png"),
-size / 2,
-size / 2,
size,
size,
new org.joml.Vector4i(particleColor, particleColor, particleColor, particleColor),
true // depth = true
);
matrices.pop();
}
}
private int getColor(int index) {
return switch (colorMode.getSelected()) {
case "Client" -> ColorAssist.getClientColor();
case "Picker" -> pickColor1.getColor();
case "DoublePicker" -> ColorAssist.gradient(pickColor1.getColor(), pickColor2.getColor(), index, 10);
default -> ColorAssist.getClientColor();
};
}
// Внутренний класс для представления падающей линии
private static class FallingLine {
List<Vec3d> points = new ArrayList<>();
List<Vec3d> turnPoints = new ArrayList<>(); // Точки поворота для кружков
Vec3d currentDirection; // Текущее направление движения
double distanceTraveled = 0; // Пройденное расстояние в текущем сегменте
double currentSegmentLength; // Длина текущего сегмента
Random random = new Random();
public FallingLine(Random random, Vec3d playerPos) {
// Спавним линию прямо перед игроком на высоте 14 блоков
if (mc.gameRenderer == null || mc.player == null) {
// Fallback если камера недоступна
double range = 15;
double x = playerPos.x + (random.nextDouble() - 0.5) * range;
double y = playerPos.y + random.nextDouble() * 14; // От пола до 14 блоков вверх
double z = playerPos.z + (random.nextDouble() - 0.5) * range;
points.add(new Vec3d(x, y, z));
} else {
// Получаем направление взгляда игрока
float yaw = mc.player.getYaw();
float pitch = mc.player.getPitch();
double yawRad = Math.toRadians(yaw);
double pitchRad = Math.toRadians(pitch);
// Спавним на расстоянии 2-8 блоков
double distance = 2 + random.nextDouble() * 6;
// Направление взгляда (с учетом pitch)
double dirX = -Math.sin(yawRad) * Math.cos(pitchRad);
double dirY = -Math.sin(pitchRad);
double dirZ = Math.cos(yawRad) * Math.cos(pitchRad);
// Вычисляем вектора "вправо" и "вверх" относительно направления взгляда
// Вектор вправо = cross(направление, мировой_вверх)
Vec3d lookDir = new Vec3d(dirX, dirY, dirZ);
Vec3d worldUp = new Vec3d(0, 1, 0);
Vec3d right = lookDir.crossProduct(worldUp).normalize();
// Вектор вверх = cross(вправо, направление)
Vec3d up = right.crossProduct(lookDir).normalize();
// Добавляем смещение равномерно по всему экрану
// Используем равномерное распределение для покрытия всего экрана
double horizontalSpread = (random.nextDouble() - 0.5) * 24; // -12 до +12 блоков (увеличено)
double verticalSpread = (random.nextDouble() - 0.5) * 18; // -9 до +9 блоков (увеличено)
// Базовая позиция перед игроком
double x = playerPos.x + dirX * distance;
double y = playerPos.y + dirY * distance;
double z = playerPos.z + dirZ * distance;
// Добавляем смещение по краям экрана
x += right.x * horizontalSpread + up.x * verticalSpread;
y += right.y * horizontalSpread + up.y * verticalSpread;
z += right.z * horizontalSpread + up.z * verticalSpread;
points.add(new Vec3d(x, y, z));
}
// Начинаем со случайного направления
currentDirection = getRandomDirection();
// Случайная длина первого сегмента
currentSegmentLength = 0.5 + random.nextDouble() * 1.5; // 0.5-2.0 блоков
}
public void update(Vec3d playerPos, float fallSpeed, float segmentLengthSetting, float zigzagWidth, float maxLineLength) {
if (points.isEmpty()) return;
Vec3d lastPoint = points.get(points.size() - 1);
// Двигаемся в текущем направлении
Vec3d movement = currentDirection.multiply(fallSpeed);
Vec3d newPoint = lastPoint.add(movement);
points.add(newPoint);
distanceTraveled += fallSpeed;
// Проверяем нужно ли повернуть (прошли весь сегмент)
if (distanceTraveled >= currentSegmentLength) {
// Сохраняем точку поворота для кружка
turnPoints.add(newPoint);
// Делаем поворот!
makeRandomTurn(zigzagWidth);
distanceTraveled = 0;
// Новая случайная длина сегмента (0.5-2.0 блоков, полностью случайная)
currentSegmentLength = 0.5 + random.nextDouble() * 1.5;
}
// Удаляем старые точки если линия стала длиннее максимальной длины
double totalLength = calculateTotalLength();
while (totalLength > maxLineLength && points.size() > 2) {
Vec3d removedPoint = points.remove(0);
// Удаляем turnPoints которые были удалены вместе с точками линии
turnPoints.removeIf(turnPoint -> turnPoint.equals(removedPoint));
totalLength = calculateTotalLength();
}
// Дополнительно удаляем turnPoints которые слишком далеко от текущей линии
if (!points.isEmpty()) {
Vec3d firstPoint = points.get(0);
turnPoints.removeIf(turnPoint -> {
// Проверяем есть ли эта точка в текущем списке points
boolean isInPoints = points.stream().anyMatch(p -> p.distanceTo(turnPoint) < 0.1);
// Если точки нет в списке и она далеко от начала - удаляем
return !isInPoints && turnPoint.distanceTo(firstPoint) > maxLineLength;
});
}
}
private double calculateTotalLength() {
double length = 0;
for (int i = 0; i < points.size() - 1; i++) {
length += points.get(i).distanceTo(points.get(i + 1));
}
return length;
}
private void makeRandomTurn(float zigzagWidth) {
// Выбираем полностью случайное направление (6 основных направлений)
currentDirection = getRandomDirection();
}
private Vec3d getRandomDirection() {
int directionType = random.nextInt(6);
return switch (directionType) {
case 0 -> new Vec3d(1, 0, 0); // Вправо
case 1 -> new Vec3d(-1, 0, 0); // Влево
case 2 -> new Vec3d(0, 1, 0); // Вверх
case 3 -> new Vec3d(0, -1, 0); // Вниз
case 4 -> new Vec3d(0, 0, 1); // Вперед
case 5 -> new Vec3d(0, 0, -1); // Назад
default -> new Vec3d(0, -1, 0);
};
}
public boolean shouldRespawn(Vec3d playerPos) {
if (points.isEmpty()) return true;
Vec3d lastPoint = points.get(points.size() - 1);
// Респавним если линия ушла слишком далеко от игрока (уменьшено до 18)
double distance = lastPoint.distanceTo(playerPos);
if (distance > 18) return true;
// Респавним если линия вне поля зрения камеры (более агрессивно)
if (mc.gameRenderer != null) {
Camera camera = mc.gameRenderer.getCamera();
Vec3d cameraPos = camera.getPos();
Vec3d cameraDir = Vec3d.fromPolar(camera.getPitch(), camera.getYaw());
// Вектор от камеры к линии
Vec3d toLine = lastPoint.subtract(cameraPos).normalize();
// Вычисляем угол между направлением камеры и направлением к линии
double dotProduct = cameraDir.dotProduct(toLine);
// Если угол больше 60 градусов (линия сбоку или позади), респавним быстрее
// Было -0.3 (107 градусов), стало 0.5 (60 градусов) - более агрессивный респавн
if (dotProduct < 0.5) return true;
}
return false;
}
}
}
