• Ищем качественного (не новичок) разработчиков Xenforo для этого форума! В идеале, чтобы ты был фулл стек программистом. Если у тебя есть что показать, то свяжись с нами по контактным данным: https://t.me/DREDD

Визуальная часть PotionProject exp3.1 ready

Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Янв 2025
Сообщения
32
Реакции
0
Выберите загрузчик игры
  1. Vanilla
  2. Forge
  3. Fabric
  4. OptiFine
Функция улучшает визуал зелий при броске
1747775539944.png
1747775548023.png

Отображает время до падения, Круг действия зелья, название эффекта

Код:
Expand Collapse Copy
package wtf.loft.functions.impl.render;

import com.google.common.eventbus.Subscribe;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity;
import net.minecraft.entity.projectile.PotionEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.potion.*;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.RayTraceContext;
import net.minecraft.util.math.RayTraceContext.BlockMode;
import net.minecraft.util.math.RayTraceContext.FluidMode;
import net.minecraft.util.math.RayTraceResult.Type;
import net.minecraft.util.math.vector.Vector2f;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.registry.Registry;
import org.lwjgl.opengl.GL11;
import wtf.loft.events.EventDisplay;
import wtf.loft.events.WorldEvent;
import wtf.loft.functions.api.Category;
import wtf.loft.functions.api.Function;
import wtf.loft.functions.api.FunctionRegister;
import wtf.loft.utils.projections.ProjectionUtil;
import wtf.loft.utils.render.ColorUtils;
import wtf.loft.utils.render.DisplayUtils;
import wtf.loft.utils.render.font.Fonts;

import java.util.ArrayList;
import java.util.List;

@FunctionRegister(
        name = "PotionProject",
        type = Category.Render
)
public class PotionProject extends Function {
    private float lineWidth = 1.5F;
    final List<ThrowablePoint> throwablePoints = new ArrayList<>();
    private final List<PotionRadius> potionRadii = new ArrayList<>();

    @Subscribe
    public void onRender(EventDisplay e) {
        for(ThrowablePoint throwablePoint : this.throwablePoints) {
            Vector3d pos = throwablePoint.position;
            Vector2f projection = ProjectionUtil.project(pos.x, pos.y - 0.3, pos.z);
            int ticks = throwablePoint.ticks;
            ResourceLocation texture = throwablePoint.texture;
            if (!projection.equals(new Vector2f(Float.MAX_VALUE, Float.MAX_VALUE))) {
                boolean isMoving = ticks > 0;
                if (isMoving) {
                    double time = (double)(ticks * 50) / 1000.0;
                    String text = String.format("%.1f сек.", time);
                    float width = Fonts.montserrat.getWidth(text, 5.0F);
                    float textWidth = width + 8.0F + 8.0F;
                    float posX = projection.x - textWidth / 2.0F;
                    float posY = projection.y;
                    DisplayUtils.drawRoundedRect(posX + 2.0F, posY + 2.0F - 3.0F, textWidth - 4.0F, 9.0F, 0.0F, ColorUtils.rgba(24, 24, 24, 80));
                    DisplayUtils.drawImage(texture, (float)((int)posX + 4), (float)((int)posY + 2 + 1 - 4), 8.0F, 8.0F, -1);
                    Fonts.montserrat.drawText(e.getMatrixStack(), text, posX + 14.0F, posY + 4.5F - 4.0F, -1, 5.0F);
                }
            }
        }

        for(PotionRadius radius : potionRadii) {
            Vector2f projection = ProjectionUtil.project(radius.position.x, radius.position.y, radius.position.z);
            if (!projection.equals(new Vector2f(Float.MAX_VALUE, Float.MAX_VALUE))) {
                String effectText = getEffectText(radius.potionType);
                String radiusText = String.format("%.1f блоков", radius.radius);

                float effectWidth = Fonts.montserrat.getWidth(effectText, 5.0F);
                float radiusWidth = Fonts.montserrat.getWidth(radiusText, 5.0F);

                float posX = projection.x - Math.max(effectWidth, radiusWidth) / 2.0F;
                float posY = projection.y - 20.0F;

                Fonts.montserrat.drawText(e.getMatrixStack(), effectText, posX, posY, getTextColor(radius.potionType), 5.0F);
                Fonts.montserrat.drawText(e.getMatrixStack(), radiusText, posX, posY + 10.0F, -1, 5.0F);
            }
        }
    }

    @Subscribe
    public void onWorldRender(WorldEvent event) {
        GL11.glPushMatrix();
        GL11.glDisable(3553);
        GL11.glDisable(2929);
        GL11.glEnable(3042);
        GL11.glEnable(2848);
        Vector3d renderOffset = mc.getRenderManager().info.getProjectedView();
        GL11.glTranslated(-renderOffset.x, -renderOffset.y, -renderOffset.z);
        GL11.glLineWidth(this.lineWidth);
        buffer.begin(1, DefaultVertexFormats.POSITION);
        this.throwablePoints.clear();
        this.potionRadii.clear();

        for(Entity entity : mc.world.getAllEntities()) {
            if (entity instanceof PotionEntity potion) {
                if (isExplosivePotion(potion)) {
                    this.processPotion(potion);
                }
            }
        }

        tessellator.draw();

        if (!potionRadii.isEmpty()) {
            buffer.begin(6, DefaultVertexFormats.POSITION_COLOR);
            for (PotionRadius radius : potionRadii) {
                Vector3d pos = radius.position;
                float r = radius.radius;
                int baseColor = getPotionColor(radius.potionType);
                int darkColor = ColorUtils.rgba(0, 0, 0, 80);
                int edgeColor = ColorUtils.rgba(0, 0, 0, 30);

                buffer.pos(pos.x, pos.y, pos.z).color(darkColor).endVertex();
                for (int i = 0; i <= 360; i += 10) {
                    double x = pos.x + Math.sin(Math.toRadians(i)) * r;
                    double z = pos.z + Math.cos(Math.toRadians(i)) * r;
                    buffer.pos(x, pos.y, z).color(edgeColor).endVertex();
                }
            }
            tessellator.draw();


            buffer.begin(3, DefaultVertexFormats.POSITION_COLOR);
            for (PotionRadius radius : potionRadii) {
                Vector3d pos = radius.position;
                float r = radius.radius;
                int color = getPotionColor(radius.potionType);

                for (int i = 0; i <= 360; i += 5) {
                    double x = pos.x + Math.sin(Math.toRadians(i)) * r;
                    double z = pos.z + Math.cos(Math.toRadians(i)) * r;
                    buffer.pos(x, pos.y, z).color(color).endVertex();
                }
                buffer.pos(pos.x + Math.sin(0), pos.y, pos.z + Math.cos(0)).color(color).endVertex();

                if (radius.potionType.equals("HARMING") || radius.potionType.equals("POISON")) {
                    for (int i = 0; i <= 360; i += 10) {
                        double x = pos.x + Math.sin(Math.toRadians(i)) * (r * 0.7);
                        double z = pos.z + Math.cos(Math.toRadians(i)) * (r * 0.7);
                        buffer.pos(x, pos.y + 0.1, z).color(color).endVertex();
                    }
                }
            }
            tessellator.draw();
        }

        GL11.glDisable(3042);
        GL11.glDisable(2848);
        GL11.glEnable(3553);
        GL11.glEnable(2929);
        GL11.glPopMatrix();
    }

    private String getEffectText(String potionType) {
        if (potionType.equals("OTHER")) {
            return "Кастомное зелье";
        }
        switch(potionType) {
            case "HARMING": return "Урон";
            case "POISON": return "Яд";
            case "SLOWNESS": return "Замедление";
            case "WEAKNESS": return "Слабость";
            case "INSTANT_HEALTH": return "Мгновенное лечение";
            case "REGENERATION": return "Регенерация";
            case "FIRE_RESISTANCE": return "Огнестойкость";
            case "WATER_BREATHING": return "Подводное дыхание";
            case "INVISIBILITY": return "Невидимость";
            case "NIGHT_VISION": return "Ночное зрение";
            case "JUMP_BOOST": return "Прыжок";
            case "SPEED": return "Скорость";
            case "STRENGTH": return "Сила";
            default: return "Эффект";
        }
    }

    private int getTextColor(String potionType) {
        if (potionType.equals("OTHER")) {
            return ColorUtils.rgba(200, 0, 200, 255);
        }
        switch(potionType) {
            case "HARMING": return ColorUtils.rgba(255, 50, 50, 255);
            case "POISON": return ColorUtils.rgba(50, 255, 50, 255);
            case "SLOWNESS": return ColorUtils.rgba(50, 50, 255, 255);
            case "WEAKNESS": return ColorUtils.rgba(150, 150, 150, 255);
            case "INSTANT_HEALTH": return ColorUtils.rgba(0, 200, 0, 255);
            case "REGENERATION": return ColorUtils.rgba(255, 105, 180, 255);
            case "FIRE_RESISTANCE": return ColorUtils.rgba(255, 100, 0, 255);
            case "WATER_BREATHING": return ColorUtils.rgba(0, 150, 200, 255);
            case "INVISIBILITY": return ColorUtils.rgba(150, 150, 150, 255);
            case "NIGHT_VISION": return ColorUtils.rgba(100, 0, 200, 255);
            case "JUMP_BOOST": return ColorUtils.rgba(0, 255, 150, 255);
            case "SPEED": return ColorUtils.rgba(100, 255, 255, 255);
            case "STRENGTH": return ColorUtils.rgba(255, 100, 0, 255);
            default: return -1;
        }
    }

    private int getPotionColor(String potionType) {
        if (potionType.equals("OTHER")) {
            return ColorUtils.rgba(200, 0, 200, 100);
        }
        switch(potionType) {
            case "HARMING": return ColorUtils.rgba(255, 0, 0, 100);
            case "POISON": return ColorUtils.rgba(0, 255, 0, 100);
            case "SLOWNESS": return ColorUtils.rgba(0, 0, 255, 100);
            case "WEAKNESS": return ColorUtils.rgba(128, 128, 128, 100);
            case "INSTANT_HEALTH": return ColorUtils.rgba(0, 200, 0, 80);
            case "REGENERATION": return ColorUtils.rgba(255, 105, 180, 80);
            case "FIRE_RESISTANCE": return ColorUtils.rgba(255, 100, 0, 80);
            case "WATER_BREATHING": return ColorUtils.rgba(0, 150, 200, 80);
            case "INVISIBILITY": return ColorUtils.rgba(150, 150, 150, 80);
            case "NIGHT_VISION": return ColorUtils.rgba(100, 0, 200, 80);
            case "JUMP_BOOST": return ColorUtils.rgba(0, 255, 150, 80);
            case "SPEED": return ColorUtils.rgba(100, 255, 255, 80);
            case "STRENGTH": return ColorUtils.rgba(255, 100, 0, 80);
            default: return ColorUtils.rgba(255, 165, 0, 100);
        }
    }

    private void processPotion(PotionEntity potion) {
        Vector3d motion = potion.getMotion();
        Vector3d pos = potion.getPositionVec();
        int ticks = 0;
        ResourceLocation texture = new ResourceLocation("textures/item/potion_bottle_splash.png");
        String potionType = getPotionType(potion.getItem());

        for(int i = 0; i < 150; ++i) {
            Vector3d prevPos = pos;
            pos = pos.add(motion);
            motion = this.getNextMotion(potion, motion);
            ColorUtils.setAlpha(ColorUtils.getColor(0), 165);
            buffer.pos(prevPos.x, prevPos.y, prevPos.z).endVertex();
            RayTraceContext rayTraceContext = new RayTraceContext(prevPos, pos, BlockMode.COLLIDER, FluidMode.NONE, potion);
            BlockRayTraceResult blockHitResult = mc.world.rayTraceBlocks(rayTraceContext);
            boolean isLast = blockHitResult.getType() == Type.BLOCK;
            if (isLast) {
                pos = blockHitResult.getHitVec();
                potionRadii.add(new PotionRadius(pos, getRadiusForPotion(potionType), potionType));
            }

            buffer.pos(pos.x, pos.y, pos.z).endVertex();
            if (blockHitResult.getType() == Type.BLOCK || pos.y < -128.0) {
                if (!(motion.lengthSquared() < 0.04)) {
                    this.throwablePoints.add(new ThrowablePoint(pos, ticks, texture));
                }
                break;
            }

            ++ticks;
        }
    }

    private String getPotionType(ItemStack potionStack) {
        List<EffectInstance> effects = PotionUtils.getEffectsFromStack(potionStack);
        if (effects.isEmpty()) {
            return "OTHER";
        }
        EffectInstance mainEffect = effects.get(0);
        ResourceLocation effectId = Registry.EFFECTS.getKey(mainEffect.getPotion());
        if (effectId == null) {
            return "OTHER";
        }
        switch (effectId.getPath()) {
            case "instant_damage": return "HARMING";
            case "poison": return "POISON";
            case "slowness": return "SLOWNESS";
            case "weakness": return "WEAKNESS";
            case "instant_health": return "INSTANT_HEALTH";
            case "regeneration": return "REGENERATION";
            case "fire_resistance": return "FIRE_RESISTANCE";
            case "water_breathing": return "WATER_BREATHING";
            case "invisibility": return "INVISIBILITY";
            case "night_vision": return "NIGHT_VISION";
            case "jump_boost": return "JUMP_BOOST";
            case "speed": return "SPEED";
            case "strength": return "STRENGTH";
            default: return "OTHER";
        }
    }

    private float getRadiusForPotion(String potionType) {
        switch(potionType) {
            case "HARMING": return 4.0f;
            case "POISON": return 3.5f;
            case "SLOWNESS": return 3.0f;
            case "WEAKNESS": return 2.5f;
            case "INSTANT_HEALTH": return 3.0f;
            case "REGENERATION": return 3.0f;
            case "FIRE_RESISTANCE": return 2.5f;
            case "WATER_BREATHING": return 2.5f;
            case "INVISIBILITY": return 2.0f;
            case "NIGHT_VISION": return 2.0f;
            case "JUMP_BOOST": return 2.5f;
            case "SPEED": return 3.0f;
            case "STRENGTH": return 3.0f;
            default: return 3.0f;
        }
    }

    private boolean isExplosivePotion(PotionEntity potion) {
        ItemStack stack = potion.getItem();
        if (stack.getItem() != Items.SPLASH_POTION && stack.getItem() != Items.LINGERING_POTION) {
            return false;
        }
        return !PotionUtils.getEffectsFromStack(stack).isEmpty();
    }

    private Vector3d getNextMotion(Entity entity, Vector3d motion) {
        if (entity instanceof PotionEntity) {
            if (entity.isInWater()) {
                motion = motion.scale(0.8);
            } else {
                motion = motion.scale(0.99);
            }
            if (!entity.hasNoGravity()) {
                motion.y -= 0.05;
            }
        }
        return motion;
    }

    static record ThrowablePoint(Vector3d position, int ticks, ResourceLocation texture) {}
    static record PotionRadius(Vector3d position, float radius, String potionType) {}
}
 
Последнее редактирование модератором:
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
годно /up
 
щяс попробуем
 
проверил имба и автор поста не против если я солью апгрейд кода? там есть еще 1 модуль полезной
 
Функция улучшает визуал зелий при броске
Посмотреть вложение 306893Посмотреть вложение 306894
Отображает время до падения, Круг действия зелья, название эффекта

Код:
Expand Collapse Copy
package wtf.loft.functions.impl.render;

import com.google.common.eventbus.Subscribe;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity;
import net.minecraft.entity.projectile.PotionEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.potion.*;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.RayTraceContext;
import net.minecraft.util.math.RayTraceContext.BlockMode;
import net.minecraft.util.math.RayTraceContext.FluidMode;
import net.minecraft.util.math.RayTraceResult.Type;
import net.minecraft.util.math.vector.Vector2f;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.registry.Registry;
import org.lwjgl.opengl.GL11;
import wtf.loft.events.EventDisplay;
import wtf.loft.events.WorldEvent;
import wtf.loft.functions.api.Category;
import wtf.loft.functions.api.Function;
import wtf.loft.functions.api.FunctionRegister;
import wtf.loft.utils.projections.ProjectionUtil;
import wtf.loft.utils.render.ColorUtils;
import wtf.loft.utils.render.DisplayUtils;
import wtf.loft.utils.render.font.Fonts;

import java.util.ArrayList;
import java.util.List;

@FunctionRegister(
        name = "PotionProject",
        type = Category.Render
)
public class PotionProject extends Function {
    private float lineWidth = 1.5F;
    final List<ThrowablePoint> throwablePoints = new ArrayList<>();
    private final List<PotionRadius> potionRadii = new ArrayList<>();

    @Subscribe
    public void onRender(EventDisplay e) {
        for(ThrowablePoint throwablePoint : this.throwablePoints) {
            Vector3d pos = throwablePoint.position;
            Vector2f projection = ProjectionUtil.project(pos.x, pos.y - 0.3, pos.z);
            int ticks = throwablePoint.ticks;
            ResourceLocation texture = throwablePoint.texture;
            if (!projection.equals(new Vector2f(Float.MAX_VALUE, Float.MAX_VALUE))) {
                boolean isMoving = ticks > 0;
                if (isMoving) {
                    double time = (double)(ticks * 50) / 1000.0;
                    String text = String.format("%.1f сек.", time);
                    float width = Fonts.montserrat.getWidth(text, 5.0F);
                    float textWidth = width + 8.0F + 8.0F;
                    float posX = projection.x - textWidth / 2.0F;
                    float posY = projection.y;
                    DisplayUtils.drawRoundedRect(posX + 2.0F, posY + 2.0F - 3.0F, textWidth - 4.0F, 9.0F, 0.0F, ColorUtils.rgba(24, 24, 24, 80));
                    DisplayUtils.drawImage(texture, (float)((int)posX + 4), (float)((int)posY + 2 + 1 - 4), 8.0F, 8.0F, -1);
                    Fonts.montserrat.drawText(e.getMatrixStack(), text, posX + 14.0F, posY + 4.5F - 4.0F, -1, 5.0F);
                }
            }
        }

        for(PotionRadius radius : potionRadii) {
            Vector2f projection = ProjectionUtil.project(radius.position.x, radius.position.y, radius.position.z);
            if (!projection.equals(new Vector2f(Float.MAX_VALUE, Float.MAX_VALUE))) {
                String effectText = getEffectText(radius.potionType);
                String radiusText = String.format("%.1f блоков", radius.radius);

                float effectWidth = Fonts.montserrat.getWidth(effectText, 5.0F);
                float radiusWidth = Fonts.montserrat.getWidth(radiusText, 5.0F);

                float posX = projection.x - Math.max(effectWidth, radiusWidth) / 2.0F;
                float posY = projection.y - 20.0F;

                Fonts.montserrat.drawText(e.getMatrixStack(), effectText, posX, posY, getTextColor(radius.potionType), 5.0F);
                Fonts.montserrat.drawText(e.getMatrixStack(), radiusText, posX, posY + 10.0F, -1, 5.0F);
            }
        }
    }

    @Subscribe
    public void onWorldRender(WorldEvent event) {
        GL11.glPushMatrix();
        GL11.glDisable(3553);
        GL11.glDisable(2929);
        GL11.glEnable(3042);
        GL11.glEnable(2848);
        Vector3d renderOffset = mc.getRenderManager().info.getProjectedView();
        GL11.glTranslated(-renderOffset.x, -renderOffset.y, -renderOffset.z);
        GL11.glLineWidth(this.lineWidth);
        buffer.begin(1, DefaultVertexFormats.POSITION);
        this.throwablePoints.clear();
        this.potionRadii.clear();

        for(Entity entity : mc.world.getAllEntities()) {
            if (entity instanceof PotionEntity potion) {
                if (isExplosivePotion(potion)) {
                    this.processPotion(potion);
                }
            }
        }

        tessellator.draw();

        if (!potionRadii.isEmpty()) {
            buffer.begin(6, DefaultVertexFormats.POSITION_COLOR);
            for (PotionRadius radius : potionRadii) {
                Vector3d pos = radius.position;
                float r = radius.radius;
                int baseColor = getPotionColor(radius.potionType);
                int darkColor = ColorUtils.rgba(0, 0, 0, 80);
                int edgeColor = ColorUtils.rgba(0, 0, 0, 30);

                buffer.pos(pos.x, pos.y, pos.z).color(darkColor).endVertex();
                for (int i = 0; i <= 360; i += 10) {
                    double x = pos.x + Math.sin(Math.toRadians(i)) * r;
                    double z = pos.z + Math.cos(Math.toRadians(i)) * r;
                    buffer.pos(x, pos.y, z).color(edgeColor).endVertex();
                }
            }
            tessellator.draw();


            buffer.begin(3, DefaultVertexFormats.POSITION_COLOR);
            for (PotionRadius radius : potionRadii) {
                Vector3d pos = radius.position;
                float r = radius.radius;
                int color = getPotionColor(radius.potionType);

                for (int i = 0; i <= 360; i += 5) {
                    double x = pos.x + Math.sin(Math.toRadians(i)) * r;
                    double z = pos.z + Math.cos(Math.toRadians(i)) * r;
                    buffer.pos(x, pos.y, z).color(color).endVertex();
                }
                buffer.pos(pos.x + Math.sin(0), pos.y, pos.z + Math.cos(0)).color(color).endVertex();

                if (radius.potionType.equals("HARMING") || radius.potionType.equals("POISON")) {
                    for (int i = 0; i <= 360; i += 10) {
                        double x = pos.x + Math.sin(Math.toRadians(i)) * (r * 0.7);
                        double z = pos.z + Math.cos(Math.toRadians(i)) * (r * 0.7);
                        buffer.pos(x, pos.y + 0.1, z).color(color).endVertex();
                    }
                }
            }
            tessellator.draw();
        }

        GL11.glDisable(3042);
        GL11.glDisable(2848);
        GL11.glEnable(3553);
        GL11.glEnable(2929);
        GL11.glPopMatrix();
    }

    private String getEffectText(String potionType) {
        if (potionType.equals("OTHER")) {
            return "Кастомное зелье";
        }
        switch(potionType) {
            case "HARMING": return "Урон";
            case "POISON": return "Яд";
            case "SLOWNESS": return "Замедление";
            case "WEAKNESS": return "Слабость";
            case "INSTANT_HEALTH": return "Мгновенное лечение";
            case "REGENERATION": return "Регенерация";
            case "FIRE_RESISTANCE": return "Огнестойкость";
            case "WATER_BREATHING": return "Подводное дыхание";
            case "INVISIBILITY": return "Невидимость";
            case "NIGHT_VISION": return "Ночное зрение";
            case "JUMP_BOOST": return "Прыжок";
            case "SPEED": return "Скорость";
            case "STRENGTH": return "Сила";
            default: return "Эффект";
        }
    }

    private int getTextColor(String potionType) {
        if (potionType.equals("OTHER")) {
            return ColorUtils.rgba(200, 0, 200, 255);
        }
        switch(potionType) {
            case "HARMING": return ColorUtils.rgba(255, 50, 50, 255);
            case "POISON": return ColorUtils.rgba(50, 255, 50, 255);
            case "SLOWNESS": return ColorUtils.rgba(50, 50, 255, 255);
            case "WEAKNESS": return ColorUtils.rgba(150, 150, 150, 255);
            case "INSTANT_HEALTH": return ColorUtils.rgba(0, 200, 0, 255);
            case "REGENERATION": return ColorUtils.rgba(255, 105, 180, 255);
            case "FIRE_RESISTANCE": return ColorUtils.rgba(255, 100, 0, 255);
            case "WATER_BREATHING": return ColorUtils.rgba(0, 150, 200, 255);
            case "INVISIBILITY": return ColorUtils.rgba(150, 150, 150, 255);
            case "NIGHT_VISION": return ColorUtils.rgba(100, 0, 200, 255);
            case "JUMP_BOOST": return ColorUtils.rgba(0, 255, 150, 255);
            case "SPEED": return ColorUtils.rgba(100, 255, 255, 255);
            case "STRENGTH": return ColorUtils.rgba(255, 100, 0, 255);
            default: return -1;
        }
    }

    private int getPotionColor(String potionType) {
        if (potionType.equals("OTHER")) {
            return ColorUtils.rgba(200, 0, 200, 100);
        }
        switch(potionType) {
            case "HARMING": return ColorUtils.rgba(255, 0, 0, 100);
            case "POISON": return ColorUtils.rgba(0, 255, 0, 100);
            case "SLOWNESS": return ColorUtils.rgba(0, 0, 255, 100);
            case "WEAKNESS": return ColorUtils.rgba(128, 128, 128, 100);
            case "INSTANT_HEALTH": return ColorUtils.rgba(0, 200, 0, 80);
            case "REGENERATION": return ColorUtils.rgba(255, 105, 180, 80);
            case "FIRE_RESISTANCE": return ColorUtils.rgba(255, 100, 0, 80);
            case "WATER_BREATHING": return ColorUtils.rgba(0, 150, 200, 80);
            case "INVISIBILITY": return ColorUtils.rgba(150, 150, 150, 80);
            case "NIGHT_VISION": return ColorUtils.rgba(100, 0, 200, 80);
            case "JUMP_BOOST": return ColorUtils.rgba(0, 255, 150, 80);
            case "SPEED": return ColorUtils.rgba(100, 255, 255, 80);
            case "STRENGTH": return ColorUtils.rgba(255, 100, 0, 80);
            default: return ColorUtils.rgba(255, 165, 0, 100);
        }
    }

    private void processPotion(PotionEntity potion) {
        Vector3d motion = potion.getMotion();
        Vector3d pos = potion.getPositionVec();
        int ticks = 0;
        ResourceLocation texture = new ResourceLocation("textures/item/potion_bottle_splash.png");
        String potionType = getPotionType(potion.getItem());

        for(int i = 0; i < 150; ++i) {
            Vector3d prevPos = pos;
            pos = pos.add(motion);
            motion = this.getNextMotion(potion, motion);
            ColorUtils.setAlpha(ColorUtils.getColor(0), 165);
            buffer.pos(prevPos.x, prevPos.y, prevPos.z).endVertex();
            RayTraceContext rayTraceContext = new RayTraceContext(prevPos, pos, BlockMode.COLLIDER, FluidMode.NONE, potion);
            BlockRayTraceResult blockHitResult = mc.world.rayTraceBlocks(rayTraceContext);
            boolean isLast = blockHitResult.getType() == Type.BLOCK;
            if (isLast) {
                pos = blockHitResult.getHitVec();
                potionRadii.add(new PotionRadius(pos, getRadiusForPotion(potionType), potionType));
            }

            buffer.pos(pos.x, pos.y, pos.z).endVertex();
            if (blockHitResult.getType() == Type.BLOCK || pos.y < -128.0) {
                if (!(motion.lengthSquared() < 0.04)) {
                    this.throwablePoints.add(new ThrowablePoint(pos, ticks, texture));
                }
                break;
            }

            ++ticks;
        }
    }

    private String getPotionType(ItemStack potionStack) {
        List<EffectInstance> effects = PotionUtils.getEffectsFromStack(potionStack);
        if (effects.isEmpty()) {
            return "OTHER";
        }
        EffectInstance mainEffect = effects.get(0);
        ResourceLocation effectId = Registry.EFFECTS.getKey(mainEffect.getPotion());
        if (effectId == null) {
            return "OTHER";
        }
        switch (effectId.getPath()) {
            case "instant_damage": return "HARMING";
            case "poison": return "POISON";
            case "slowness": return "SLOWNESS";
            case "weakness": return "WEAKNESS";
            case "instant_health": return "INSTANT_HEALTH";
            case "regeneration": return "REGENERATION";
            case "fire_resistance": return "FIRE_RESISTANCE";
            case "water_breathing": return "WATER_BREATHING";
            case "invisibility": return "INVISIBILITY";
            case "night_vision": return "NIGHT_VISION";
            case "jump_boost": return "JUMP_BOOST";
            case "speed": return "SPEED";
            case "strength": return "STRENGTH";
            default: return "OTHER";
        }
    }

    private float getRadiusForPotion(String potionType) {
        switch(potionType) {
            case "HARMING": return 4.0f;
            case "POISON": return 3.5f;
            case "SLOWNESS": return 3.0f;
            case "WEAKNESS": return 2.5f;
            case "INSTANT_HEALTH": return 3.0f;
            case "REGENERATION": return 3.0f;
            case "FIRE_RESISTANCE": return 2.5f;
            case "WATER_BREATHING": return 2.5f;
            case "INVISIBILITY": return 2.0f;
            case "NIGHT_VISION": return 2.0f;
            case "JUMP_BOOST": return 2.5f;
            case "SPEED": return 3.0f;
            case "STRENGTH": return 3.0f;
            default: return 3.0f;
        }
    }

    private boolean isExplosivePotion(PotionEntity potion) {
        ItemStack stack = potion.getItem();
        if (stack.getItem() != Items.SPLASH_POTION && stack.getItem() != Items.LINGERING_POTION) {
            return false;
        }
        return !PotionUtils.getEffectsFromStack(stack).isEmpty();
    }

    private Vector3d getNextMotion(Entity entity, Vector3d motion) {
        if (entity instanceof PotionEntity) {
            if (entity.isInWater()) {
                motion = motion.scale(0.8);
            } else {
                motion = motion.scale(0.99);
            }
            if (!entity.hasNoGravity()) {
                motion.y -= 0.05;
            }
        }
        return motion;
    }

    static record ThrowablePoint(Vector3d position, int ticks, ResourceLocation texture) {}
    static record PotionRadius(Vector3d position, float radius, String potionType) {}
}
Ну пастерам сойдёт. Ну а так то прикольно.
 
Функция улучшает визуал зелий при броске
Посмотреть вложение 306893Посмотреть вложение 306894
Отображает время до падения, Круг действия зелья, название эффекта

Код:
Expand Collapse Copy
package wtf.loft.functions.impl.render;

import com.google.common.eventbus.Subscribe;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity;
import net.minecraft.entity.projectile.PotionEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.potion.*;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.RayTraceContext;
import net.minecraft.util.math.RayTraceContext.BlockMode;
import net.minecraft.util.math.RayTraceContext.FluidMode;
import net.minecraft.util.math.RayTraceResult.Type;
import net.minecraft.util.math.vector.Vector2f;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.registry.Registry;
import org.lwjgl.opengl.GL11;
import wtf.loft.events.EventDisplay;
import wtf.loft.events.WorldEvent;
import wtf.loft.functions.api.Category;
import wtf.loft.functions.api.Function;
import wtf.loft.functions.api.FunctionRegister;
import wtf.loft.utils.projections.ProjectionUtil;
import wtf.loft.utils.render.ColorUtils;
import wtf.loft.utils.render.DisplayUtils;
import wtf.loft.utils.render.font.Fonts;

import java.util.ArrayList;
import java.util.List;

@FunctionRegister(
        name = "PotionProject",
        type = Category.Render
)
public class PotionProject extends Function {
    private float lineWidth = 1.5F;
    final List<ThrowablePoint> throwablePoints = new ArrayList<>();
    private final List<PotionRadius> potionRadii = new ArrayList<>();

    @Subscribe
    public void onRender(EventDisplay e) {
        for(ThrowablePoint throwablePoint : this.throwablePoints) {
            Vector3d pos = throwablePoint.position;
            Vector2f projection = ProjectionUtil.project(pos.x, pos.y - 0.3, pos.z);
            int ticks = throwablePoint.ticks;
            ResourceLocation texture = throwablePoint.texture;
            if (!projection.equals(new Vector2f(Float.MAX_VALUE, Float.MAX_VALUE))) {
                boolean isMoving = ticks > 0;
                if (isMoving) {
                    double time = (double)(ticks * 50) / 1000.0;
                    String text = String.format("%.1f сек.", time);
                    float width = Fonts.montserrat.getWidth(text, 5.0F);
                    float textWidth = width + 8.0F + 8.0F;
                    float posX = projection.x - textWidth / 2.0F;
                    float posY = projection.y;
                    DisplayUtils.drawRoundedRect(posX + 2.0F, posY + 2.0F - 3.0F, textWidth - 4.0F, 9.0F, 0.0F, ColorUtils.rgba(24, 24, 24, 80));
                    DisplayUtils.drawImage(texture, (float)((int)posX + 4), (float)((int)posY + 2 + 1 - 4), 8.0F, 8.0F, -1);
                    Fonts.montserrat.drawText(e.getMatrixStack(), text, posX + 14.0F, posY + 4.5F - 4.0F, -1, 5.0F);
                }
            }
        }

        for(PotionRadius radius : potionRadii) {
            Vector2f projection = ProjectionUtil.project(radius.position.x, radius.position.y, radius.position.z);
            if (!projection.equals(new Vector2f(Float.MAX_VALUE, Float.MAX_VALUE))) {
                String effectText = getEffectText(radius.potionType);
                String radiusText = String.format("%.1f блоков", radius.radius);

                float effectWidth = Fonts.montserrat.getWidth(effectText, 5.0F);
                float radiusWidth = Fonts.montserrat.getWidth(radiusText, 5.0F);

                float posX = projection.x - Math.max(effectWidth, radiusWidth) / 2.0F;
                float posY = projection.y - 20.0F;

                Fonts.montserrat.drawText(e.getMatrixStack(), effectText, posX, posY, getTextColor(radius.potionType), 5.0F);
                Fonts.montserrat.drawText(e.getMatrixStack(), radiusText, posX, posY + 10.0F, -1, 5.0F);
            }
        }
    }

    @Subscribe
    public void onWorldRender(WorldEvent event) {
        GL11.glPushMatrix();
        GL11.glDisable(3553);
        GL11.glDisable(2929);
        GL11.glEnable(3042);
        GL11.glEnable(2848);
        Vector3d renderOffset = mc.getRenderManager().info.getProjectedView();
        GL11.glTranslated(-renderOffset.x, -renderOffset.y, -renderOffset.z);
        GL11.glLineWidth(this.lineWidth);
        buffer.begin(1, DefaultVertexFormats.POSITION);
        this.throwablePoints.clear();
        this.potionRadii.clear();

        for(Entity entity : mc.world.getAllEntities()) {
            if (entity instanceof PotionEntity potion) {
                if (isExplosivePotion(potion)) {
                    this.processPotion(potion);
                }
            }
        }

        tessellator.draw();

        if (!potionRadii.isEmpty()) {
            buffer.begin(6, DefaultVertexFormats.POSITION_COLOR);
            for (PotionRadius radius : potionRadii) {
                Vector3d pos = radius.position;
                float r = radius.radius;
                int baseColor = getPotionColor(radius.potionType);
                int darkColor = ColorUtils.rgba(0, 0, 0, 80);
                int edgeColor = ColorUtils.rgba(0, 0, 0, 30);

                buffer.pos(pos.x, pos.y, pos.z).color(darkColor).endVertex();
                for (int i = 0; i <= 360; i += 10) {
                    double x = pos.x + Math.sin(Math.toRadians(i)) * r;
                    double z = pos.z + Math.cos(Math.toRadians(i)) * r;
                    buffer.pos(x, pos.y, z).color(edgeColor).endVertex();
                }
            }
            tessellator.draw();


            buffer.begin(3, DefaultVertexFormats.POSITION_COLOR);
            for (PotionRadius radius : potionRadii) {
                Vector3d pos = radius.position;
                float r = radius.radius;
                int color = getPotionColor(radius.potionType);

                for (int i = 0; i <= 360; i += 5) {
                    double x = pos.x + Math.sin(Math.toRadians(i)) * r;
                    double z = pos.z + Math.cos(Math.toRadians(i)) * r;
                    buffer.pos(x, pos.y, z).color(color).endVertex();
                }
                buffer.pos(pos.x + Math.sin(0), pos.y, pos.z + Math.cos(0)).color(color).endVertex();

                if (radius.potionType.equals("HARMING") || radius.potionType.equals("POISON")) {
                    for (int i = 0; i <= 360; i += 10) {
                        double x = pos.x + Math.sin(Math.toRadians(i)) * (r * 0.7);
                        double z = pos.z + Math.cos(Math.toRadians(i)) * (r * 0.7);
                        buffer.pos(x, pos.y + 0.1, z).color(color).endVertex();
                    }
                }
            }
            tessellator.draw();
        }

        GL11.glDisable(3042);
        GL11.glDisable(2848);
        GL11.glEnable(3553);
        GL11.glEnable(2929);
        GL11.glPopMatrix();
    }

    private String getEffectText(String potionType) {
        if (potionType.equals("OTHER")) {
            return "Кастомное зелье";
        }
        switch(potionType) {
            case "HARMING": return "Урон";
            case "POISON": return "Яд";
            case "SLOWNESS": return "Замедление";
            case "WEAKNESS": return "Слабость";
            case "INSTANT_HEALTH": return "Мгновенное лечение";
            case "REGENERATION": return "Регенерация";
            case "FIRE_RESISTANCE": return "Огнестойкость";
            case "WATER_BREATHING": return "Подводное дыхание";
            case "INVISIBILITY": return "Невидимость";
            case "NIGHT_VISION": return "Ночное зрение";
            case "JUMP_BOOST": return "Прыжок";
            case "SPEED": return "Скорость";
            case "STRENGTH": return "Сила";
            default: return "Эффект";
        }
    }

    private int getTextColor(String potionType) {
        if (potionType.equals("OTHER")) {
            return ColorUtils.rgba(200, 0, 200, 255);
        }
        switch(potionType) {
            case "HARMING": return ColorUtils.rgba(255, 50, 50, 255);
            case "POISON": return ColorUtils.rgba(50, 255, 50, 255);
            case "SLOWNESS": return ColorUtils.rgba(50, 50, 255, 255);
            case "WEAKNESS": return ColorUtils.rgba(150, 150, 150, 255);
            case "INSTANT_HEALTH": return ColorUtils.rgba(0, 200, 0, 255);
            case "REGENERATION": return ColorUtils.rgba(255, 105, 180, 255);
            case "FIRE_RESISTANCE": return ColorUtils.rgba(255, 100, 0, 255);
            case "WATER_BREATHING": return ColorUtils.rgba(0, 150, 200, 255);
            case "INVISIBILITY": return ColorUtils.rgba(150, 150, 150, 255);
            case "NIGHT_VISION": return ColorUtils.rgba(100, 0, 200, 255);
            case "JUMP_BOOST": return ColorUtils.rgba(0, 255, 150, 255);
            case "SPEED": return ColorUtils.rgba(100, 255, 255, 255);
            case "STRENGTH": return ColorUtils.rgba(255, 100, 0, 255);
            default: return -1;
        }
    }

    private int getPotionColor(String potionType) {
        if (potionType.equals("OTHER")) {
            return ColorUtils.rgba(200, 0, 200, 100);
        }
        switch(potionType) {
            case "HARMING": return ColorUtils.rgba(255, 0, 0, 100);
            case "POISON": return ColorUtils.rgba(0, 255, 0, 100);
            case "SLOWNESS": return ColorUtils.rgba(0, 0, 255, 100);
            case "WEAKNESS": return ColorUtils.rgba(128, 128, 128, 100);
            case "INSTANT_HEALTH": return ColorUtils.rgba(0, 200, 0, 80);
            case "REGENERATION": return ColorUtils.rgba(255, 105, 180, 80);
            case "FIRE_RESISTANCE": return ColorUtils.rgba(255, 100, 0, 80);
            case "WATER_BREATHING": return ColorUtils.rgba(0, 150, 200, 80);
            case "INVISIBILITY": return ColorUtils.rgba(150, 150, 150, 80);
            case "NIGHT_VISION": return ColorUtils.rgba(100, 0, 200, 80);
            case "JUMP_BOOST": return ColorUtils.rgba(0, 255, 150, 80);
            case "SPEED": return ColorUtils.rgba(100, 255, 255, 80);
            case "STRENGTH": return ColorUtils.rgba(255, 100, 0, 80);
            default: return ColorUtils.rgba(255, 165, 0, 100);
        }
    }

    private void processPotion(PotionEntity potion) {
        Vector3d motion = potion.getMotion();
        Vector3d pos = potion.getPositionVec();
        int ticks = 0;
        ResourceLocation texture = new ResourceLocation("textures/item/potion_bottle_splash.png");
        String potionType = getPotionType(potion.getItem());

        for(int i = 0; i < 150; ++i) {
            Vector3d prevPos = pos;
            pos = pos.add(motion);
            motion = this.getNextMotion(potion, motion);
            ColorUtils.setAlpha(ColorUtils.getColor(0), 165);
            buffer.pos(prevPos.x, prevPos.y, prevPos.z).endVertex();
            RayTraceContext rayTraceContext = new RayTraceContext(prevPos, pos, BlockMode.COLLIDER, FluidMode.NONE, potion);
            BlockRayTraceResult blockHitResult = mc.world.rayTraceBlocks(rayTraceContext);
            boolean isLast = blockHitResult.getType() == Type.BLOCK;
            if (isLast) {
                pos = blockHitResult.getHitVec();
                potionRadii.add(new PotionRadius(pos, getRadiusForPotion(potionType), potionType));
            }

            buffer.pos(pos.x, pos.y, pos.z).endVertex();
            if (blockHitResult.getType() == Type.BLOCK || pos.y < -128.0) {
                if (!(motion.lengthSquared() < 0.04)) {
                    this.throwablePoints.add(new ThrowablePoint(pos, ticks, texture));
                }
                break;
            }

            ++ticks;
        }
    }

    private String getPotionType(ItemStack potionStack) {
        List<EffectInstance> effects = PotionUtils.getEffectsFromStack(potionStack);
        if (effects.isEmpty()) {
            return "OTHER";
        }
        EffectInstance mainEffect = effects.get(0);
        ResourceLocation effectId = Registry.EFFECTS.getKey(mainEffect.getPotion());
        if (effectId == null) {
            return "OTHER";
        }
        switch (effectId.getPath()) {
            case "instant_damage": return "HARMING";
            case "poison": return "POISON";
            case "slowness": return "SLOWNESS";
            case "weakness": return "WEAKNESS";
            case "instant_health": return "INSTANT_HEALTH";
            case "regeneration": return "REGENERATION";
            case "fire_resistance": return "FIRE_RESISTANCE";
            case "water_breathing": return "WATER_BREATHING";
            case "invisibility": return "INVISIBILITY";
            case "night_vision": return "NIGHT_VISION";
            case "jump_boost": return "JUMP_BOOST";
            case "speed": return "SPEED";
            case "strength": return "STRENGTH";
            default: return "OTHER";
        }
    }

    private float getRadiusForPotion(String potionType) {
        switch(potionType) {
            case "HARMING": return 4.0f;
            case "POISON": return 3.5f;
            case "SLOWNESS": return 3.0f;
            case "WEAKNESS": return 2.5f;
            case "INSTANT_HEALTH": return 3.0f;
            case "REGENERATION": return 3.0f;
            case "FIRE_RESISTANCE": return 2.5f;
            case "WATER_BREATHING": return 2.5f;
            case "INVISIBILITY": return 2.0f;
            case "NIGHT_VISION": return 2.0f;
            case "JUMP_BOOST": return 2.5f;
            case "SPEED": return 3.0f;
            case "STRENGTH": return 3.0f;
            default: return 3.0f;
        }
    }

    private boolean isExplosivePotion(PotionEntity potion) {
        ItemStack stack = potion.getItem();
        if (stack.getItem() != Items.SPLASH_POTION && stack.getItem() != Items.LINGERING_POTION) {
            return false;
        }
        return !PotionUtils.getEffectsFromStack(stack).isEmpty();
    }

    private Vector3d getNextMotion(Entity entity, Vector3d motion) {
        if (entity instanceof PotionEntity) {
            if (entity.isInWater()) {
                motion = motion.scale(0.8);
            } else {
                motion = motion.scale(0.99);
            }
            if (!entity.hasNoGravity()) {
                motion.y -= 0.05;
            }
        }
        return motion;
    }

    static record ThrowablePoint(Vector3d position, int ticks, ResourceLocation texture) {}
    static record PotionRadius(Vector3d position, float radius, String potionType) {}
}
воронеж добавит мега в апдейте это
 
Функция улучшает визуал зелий при броске
Посмотреть вложение 306893Посмотреть вложение 306894
Отображает время до падения, Круг действия зелья, название эффекта

Код:
Expand Collapse Copy
package wtf.loft.functions.impl.render;

import com.google.common.eventbus.Subscribe;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity;
import net.minecraft.entity.projectile.PotionEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.potion.*;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.RayTraceContext;
import net.minecraft.util.math.RayTraceContext.BlockMode;
import net.minecraft.util.math.RayTraceContext.FluidMode;
import net.minecraft.util.math.RayTraceResult.Type;
import net.minecraft.util.math.vector.Vector2f;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.registry.Registry;
import org.lwjgl.opengl.GL11;
import wtf.loft.events.EventDisplay;
import wtf.loft.events.WorldEvent;
import wtf.loft.functions.api.Category;
import wtf.loft.functions.api.Function;
import wtf.loft.functions.api.FunctionRegister;
import wtf.loft.utils.projections.ProjectionUtil;
import wtf.loft.utils.render.ColorUtils;
import wtf.loft.utils.render.DisplayUtils;
import wtf.loft.utils.render.font.Fonts;

import java.util.ArrayList;
import java.util.List;

@FunctionRegister(
        name = "PotionProject",
        type = Category.Render
)
public class PotionProject extends Function {
    private float lineWidth = 1.5F;
    final List<ThrowablePoint> throwablePoints = new ArrayList<>();
    private final List<PotionRadius> potionRadii = new ArrayList<>();

    @Subscribe
    public void onRender(EventDisplay e) {
        for(ThrowablePoint throwablePoint : this.throwablePoints) {
            Vector3d pos = throwablePoint.position;
            Vector2f projection = ProjectionUtil.project(pos.x, pos.y - 0.3, pos.z);
            int ticks = throwablePoint.ticks;
            ResourceLocation texture = throwablePoint.texture;
            if (!projection.equals(new Vector2f(Float.MAX_VALUE, Float.MAX_VALUE))) {
                boolean isMoving = ticks > 0;
                if (isMoving) {
                    double time = (double)(ticks * 50) / 1000.0;
                    String text = String.format("%.1f сек.", time);
                    float width = Fonts.montserrat.getWidth(text, 5.0F);
                    float textWidth = width + 8.0F + 8.0F;
                    float posX = projection.x - textWidth / 2.0F;
                    float posY = projection.y;
                    DisplayUtils.drawRoundedRect(posX + 2.0F, posY + 2.0F - 3.0F, textWidth - 4.0F, 9.0F, 0.0F, ColorUtils.rgba(24, 24, 24, 80));
                    DisplayUtils.drawImage(texture, (float)((int)posX + 4), (float)((int)posY + 2 + 1 - 4), 8.0F, 8.0F, -1);
                    Fonts.montserrat.drawText(e.getMatrixStack(), text, posX + 14.0F, posY + 4.5F - 4.0F, -1, 5.0F);
                }
            }
        }

        for(PotionRadius radius : potionRadii) {
            Vector2f projection = ProjectionUtil.project(radius.position.x, radius.position.y, radius.position.z);
            if (!projection.equals(new Vector2f(Float.MAX_VALUE, Float.MAX_VALUE))) {
                String effectText = getEffectText(radius.potionType);
                String radiusText = String.format("%.1f блоков", radius.radius);

                float effectWidth = Fonts.montserrat.getWidth(effectText, 5.0F);
                float radiusWidth = Fonts.montserrat.getWidth(radiusText, 5.0F);

                float posX = projection.x - Math.max(effectWidth, radiusWidth) / 2.0F;
                float posY = projection.y - 20.0F;

                Fonts.montserrat.drawText(e.getMatrixStack(), effectText, posX, posY, getTextColor(radius.potionType), 5.0F);
                Fonts.montserrat.drawText(e.getMatrixStack(), radiusText, posX, posY + 10.0F, -1, 5.0F);
            }
        }
    }

    @Subscribe
    public void onWorldRender(WorldEvent event) {
        GL11.glPushMatrix();
        GL11.glDisable(3553);
        GL11.glDisable(2929);
        GL11.glEnable(3042);
        GL11.glEnable(2848);
        Vector3d renderOffset = mc.getRenderManager().info.getProjectedView();
        GL11.glTranslated(-renderOffset.x, -renderOffset.y, -renderOffset.z);
        GL11.glLineWidth(this.lineWidth);
        buffer.begin(1, DefaultVertexFormats.POSITION);
        this.throwablePoints.clear();
        this.potionRadii.clear();

        for(Entity entity : mc.world.getAllEntities()) {
            if (entity instanceof PotionEntity potion) {
                if (isExplosivePotion(potion)) {
                    this.processPotion(potion);
                }
            }
        }

        tessellator.draw();

        if (!potionRadii.isEmpty()) {
            buffer.begin(6, DefaultVertexFormats.POSITION_COLOR);
            for (PotionRadius radius : potionRadii) {
                Vector3d pos = radius.position;
                float r = radius.radius;
                int baseColor = getPotionColor(radius.potionType);
                int darkColor = ColorUtils.rgba(0, 0, 0, 80);
                int edgeColor = ColorUtils.rgba(0, 0, 0, 30);

                buffer.pos(pos.x, pos.y, pos.z).color(darkColor).endVertex();
                for (int i = 0; i <= 360; i += 10) {
                    double x = pos.x + Math.sin(Math.toRadians(i)) * r;
                    double z = pos.z + Math.cos(Math.toRadians(i)) * r;
                    buffer.pos(x, pos.y, z).color(edgeColor).endVertex();
                }
            }
            tessellator.draw();


            buffer.begin(3, DefaultVertexFormats.POSITION_COLOR);
            for (PotionRadius radius : potionRadii) {
                Vector3d pos = radius.position;
                float r = radius.radius;
                int color = getPotionColor(radius.potionType);

                for (int i = 0; i <= 360; i += 5) {
                    double x = pos.x + Math.sin(Math.toRadians(i)) * r;
                    double z = pos.z + Math.cos(Math.toRadians(i)) * r;
                    buffer.pos(x, pos.y, z).color(color).endVertex();
                }
                buffer.pos(pos.x + Math.sin(0), pos.y, pos.z + Math.cos(0)).color(color).endVertex();

                if (radius.potionType.equals("HARMING") || radius.potionType.equals("POISON")) {
                    for (int i = 0; i <= 360; i += 10) {
                        double x = pos.x + Math.sin(Math.toRadians(i)) * (r * 0.7);
                        double z = pos.z + Math.cos(Math.toRadians(i)) * (r * 0.7);
                        buffer.pos(x, pos.y + 0.1, z).color(color).endVertex();
                    }
                }
            }
            tessellator.draw();
        }

        GL11.glDisable(3042);
        GL11.glDisable(2848);
        GL11.glEnable(3553);
        GL11.glEnable(2929);
        GL11.glPopMatrix();
    }

    private String getEffectText(String potionType) {
        if (potionType.equals("OTHER")) {
            return "Кастомное зелье";
        }
        switch(potionType) {
            case "HARMING": return "Урон";
            case "POISON": return "Яд";
            case "SLOWNESS": return "Замедление";
            case "WEAKNESS": return "Слабость";
            case "INSTANT_HEALTH": return "Мгновенное лечение";
            case "REGENERATION": return "Регенерация";
            case "FIRE_RESISTANCE": return "Огнестойкость";
            case "WATER_BREATHING": return "Подводное дыхание";
            case "INVISIBILITY": return "Невидимость";
            case "NIGHT_VISION": return "Ночное зрение";
            case "JUMP_BOOST": return "Прыжок";
            case "SPEED": return "Скорость";
            case "STRENGTH": return "Сила";
            default: return "Эффект";
        }
    }

    private int getTextColor(String potionType) {
        if (potionType.equals("OTHER")) {
            return ColorUtils.rgba(200, 0, 200, 255);
        }
        switch(potionType) {
            case "HARMING": return ColorUtils.rgba(255, 50, 50, 255);
            case "POISON": return ColorUtils.rgba(50, 255, 50, 255);
            case "SLOWNESS": return ColorUtils.rgba(50, 50, 255, 255);
            case "WEAKNESS": return ColorUtils.rgba(150, 150, 150, 255);
            case "INSTANT_HEALTH": return ColorUtils.rgba(0, 200, 0, 255);
            case "REGENERATION": return ColorUtils.rgba(255, 105, 180, 255);
            case "FIRE_RESISTANCE": return ColorUtils.rgba(255, 100, 0, 255);
            case "WATER_BREATHING": return ColorUtils.rgba(0, 150, 200, 255);
            case "INVISIBILITY": return ColorUtils.rgba(150, 150, 150, 255);
            case "NIGHT_VISION": return ColorUtils.rgba(100, 0, 200, 255);
            case "JUMP_BOOST": return ColorUtils.rgba(0, 255, 150, 255);
            case "SPEED": return ColorUtils.rgba(100, 255, 255, 255);
            case "STRENGTH": return ColorUtils.rgba(255, 100, 0, 255);
            default: return -1;
        }
    }

    private int getPotionColor(String potionType) {
        if (potionType.equals("OTHER")) {
            return ColorUtils.rgba(200, 0, 200, 100);
        }
        switch(potionType) {
            case "HARMING": return ColorUtils.rgba(255, 0, 0, 100);
            case "POISON": return ColorUtils.rgba(0, 255, 0, 100);
            case "SLOWNESS": return ColorUtils.rgba(0, 0, 255, 100);
            case "WEAKNESS": return ColorUtils.rgba(128, 128, 128, 100);
            case "INSTANT_HEALTH": return ColorUtils.rgba(0, 200, 0, 80);
            case "REGENERATION": return ColorUtils.rgba(255, 105, 180, 80);
            case "FIRE_RESISTANCE": return ColorUtils.rgba(255, 100, 0, 80);
            case "WATER_BREATHING": return ColorUtils.rgba(0, 150, 200, 80);
            case "INVISIBILITY": return ColorUtils.rgba(150, 150, 150, 80);
            case "NIGHT_VISION": return ColorUtils.rgba(100, 0, 200, 80);
            case "JUMP_BOOST": return ColorUtils.rgba(0, 255, 150, 80);
            case "SPEED": return ColorUtils.rgba(100, 255, 255, 80);
            case "STRENGTH": return ColorUtils.rgba(255, 100, 0, 80);
            default: return ColorUtils.rgba(255, 165, 0, 100);
        }
    }

    private void processPotion(PotionEntity potion) {
        Vector3d motion = potion.getMotion();
        Vector3d pos = potion.getPositionVec();
        int ticks = 0;
        ResourceLocation texture = new ResourceLocation("textures/item/potion_bottle_splash.png");
        String potionType = getPotionType(potion.getItem());

        for(int i = 0; i < 150; ++i) {
            Vector3d prevPos = pos;
            pos = pos.add(motion);
            motion = this.getNextMotion(potion, motion);
            ColorUtils.setAlpha(ColorUtils.getColor(0), 165);
            buffer.pos(prevPos.x, prevPos.y, prevPos.z).endVertex();
            RayTraceContext rayTraceContext = new RayTraceContext(prevPos, pos, BlockMode.COLLIDER, FluidMode.NONE, potion);
            BlockRayTraceResult blockHitResult = mc.world.rayTraceBlocks(rayTraceContext);
            boolean isLast = blockHitResult.getType() == Type.BLOCK;
            if (isLast) {
                pos = blockHitResult.getHitVec();
                potionRadii.add(new PotionRadius(pos, getRadiusForPotion(potionType), potionType));
            }

            buffer.pos(pos.x, pos.y, pos.z).endVertex();
            if (blockHitResult.getType() == Type.BLOCK || pos.y < -128.0) {
                if (!(motion.lengthSquared() < 0.04)) {
                    this.throwablePoints.add(new ThrowablePoint(pos, ticks, texture));
                }
                break;
            }

            ++ticks;
        }
    }

    private String getPotionType(ItemStack potionStack) {
        List<EffectInstance> effects = PotionUtils.getEffectsFromStack(potionStack);
        if (effects.isEmpty()) {
            return "OTHER";
        }
        EffectInstance mainEffect = effects.get(0);
        ResourceLocation effectId = Registry.EFFECTS.getKey(mainEffect.getPotion());
        if (effectId == null) {
            return "OTHER";
        }
        switch (effectId.getPath()) {
            case "instant_damage": return "HARMING";
            case "poison": return "POISON";
            case "slowness": return "SLOWNESS";
            case "weakness": return "WEAKNESS";
            case "instant_health": return "INSTANT_HEALTH";
            case "regeneration": return "REGENERATION";
            case "fire_resistance": return "FIRE_RESISTANCE";
            case "water_breathing": return "WATER_BREATHING";
            case "invisibility": return "INVISIBILITY";
            case "night_vision": return "NIGHT_VISION";
            case "jump_boost": return "JUMP_BOOST";
            case "speed": return "SPEED";
            case "strength": return "STRENGTH";
            default: return "OTHER";
        }
    }

    private float getRadiusForPotion(String potionType) {
        switch(potionType) {
            case "HARMING": return 4.0f;
            case "POISON": return 3.5f;
            case "SLOWNESS": return 3.0f;
            case "WEAKNESS": return 2.5f;
            case "INSTANT_HEALTH": return 3.0f;
            case "REGENERATION": return 3.0f;
            case "FIRE_RESISTANCE": return 2.5f;
            case "WATER_BREATHING": return 2.5f;
            case "INVISIBILITY": return 2.0f;
            case "NIGHT_VISION": return 2.0f;
            case "JUMP_BOOST": return 2.5f;
            case "SPEED": return 3.0f;
            case "STRENGTH": return 3.0f;
            default: return 3.0f;
        }
    }

    private boolean isExplosivePotion(PotionEntity potion) {
        ItemStack stack = potion.getItem();
        if (stack.getItem() != Items.SPLASH_POTION && stack.getItem() != Items.LINGERING_POTION) {
            return false;
        }
        return !PotionUtils.getEffectsFromStack(stack).isEmpty();
    }

    private Vector3d getNextMotion(Entity entity, Vector3d motion) {
        if (entity instanceof PotionEntity) {
            if (entity.isInWater()) {
                motion = motion.scale(0.8);
            } else {
                motion = motion.scale(0.99);
            }
            if (!entity.hasNoGravity()) {
                motion.y -= 0.05;
            }
        }
        return motion;
    }

    static record ThrowablePoint(Vector3d position, int ticks, ResourceLocation texture) {}
    static record PotionRadius(Vector3d position, float radius, String potionType) {}
}
рил годно /up + реп
 
норм, но нужно доработать
 
Функция улучшает визуал зелий при броске
Посмотреть вложение 306893Посмотреть вложение 306894
Отображает время до падения, Круг действия зелья, название эффекта

Код:
Expand Collapse Copy
package wtf.loft.functions.impl.render;

import com.google.common.eventbus.Subscribe;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity;
import net.minecraft.entity.projectile.PotionEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.potion.*;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.RayTraceContext;
import net.minecraft.util.math.RayTraceContext.BlockMode;
import net.minecraft.util.math.RayTraceContext.FluidMode;
import net.minecraft.util.math.RayTraceResult.Type;
import net.minecraft.util.math.vector.Vector2f;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.registry.Registry;
import org.lwjgl.opengl.GL11;
import wtf.loft.events.EventDisplay;
import wtf.loft.events.WorldEvent;
import wtf.loft.functions.api.Category;
import wtf.loft.functions.api.Function;
import wtf.loft.functions.api.FunctionRegister;
import wtf.loft.utils.projections.ProjectionUtil;
import wtf.loft.utils.render.ColorUtils;
import wtf.loft.utils.render.DisplayUtils;
import wtf.loft.utils.render.font.Fonts;

import java.util.ArrayList;
import java.util.List;

@FunctionRegister(
        name = "PotionProject",
        type = Category.Render
)
public class PotionProject extends Function {
    private float lineWidth = 1.5F;
    final List<ThrowablePoint> throwablePoints = new ArrayList<>();
    private final List<PotionRadius> potionRadii = new ArrayList<>();

    @Subscribe
    public void onRender(EventDisplay e) {
        for(ThrowablePoint throwablePoint : this.throwablePoints) {
            Vector3d pos = throwablePoint.position;
            Vector2f projection = ProjectionUtil.project(pos.x, pos.y - 0.3, pos.z);
            int ticks = throwablePoint.ticks;
            ResourceLocation texture = throwablePoint.texture;
            if (!projection.equals(new Vector2f(Float.MAX_VALUE, Float.MAX_VALUE))) {
                boolean isMoving = ticks > 0;
                if (isMoving) {
                    double time = (double)(ticks * 50) / 1000.0;
                    String text = String.format("%.1f сек.", time);
                    float width = Fonts.montserrat.getWidth(text, 5.0F);
                    float textWidth = width + 8.0F + 8.0F;
                    float posX = projection.x - textWidth / 2.0F;
                    float posY = projection.y;
                    DisplayUtils.drawRoundedRect(posX + 2.0F, posY + 2.0F - 3.0F, textWidth - 4.0F, 9.0F, 0.0F, ColorUtils.rgba(24, 24, 24, 80));
                    DisplayUtils.drawImage(texture, (float)((int)posX + 4), (float)((int)posY + 2 + 1 - 4), 8.0F, 8.0F, -1);
                    Fonts.montserrat.drawText(e.getMatrixStack(), text, posX + 14.0F, posY + 4.5F - 4.0F, -1, 5.0F);
                }
            }
        }

        for(PotionRadius radius : potionRadii) {
            Vector2f projection = ProjectionUtil.project(radius.position.x, radius.position.y, radius.position.z);
            if (!projection.equals(new Vector2f(Float.MAX_VALUE, Float.MAX_VALUE))) {
                String effectText = getEffectText(radius.potionType);
                String radiusText = String.format("%.1f блоков", radius.radius);

                float effectWidth = Fonts.montserrat.getWidth(effectText, 5.0F);
                float radiusWidth = Fonts.montserrat.getWidth(radiusText, 5.0F);

                float posX = projection.x - Math.max(effectWidth, radiusWidth) / 2.0F;
                float posY = projection.y - 20.0F;

                Fonts.montserrat.drawText(e.getMatrixStack(), effectText, posX, posY, getTextColor(radius.potionType), 5.0F);
                Fonts.montserrat.drawText(e.getMatrixStack(), radiusText, posX, posY + 10.0F, -1, 5.0F);
            }
        }
    }

    @Subscribe
    public void onWorldRender(WorldEvent event) {
        GL11.glPushMatrix();
        GL11.glDisable(3553);
        GL11.glDisable(2929);
        GL11.glEnable(3042);
        GL11.glEnable(2848);
        Vector3d renderOffset = mc.getRenderManager().info.getProjectedView();
        GL11.glTranslated(-renderOffset.x, -renderOffset.y, -renderOffset.z);
        GL11.glLineWidth(this.lineWidth);
        buffer.begin(1, DefaultVertexFormats.POSITION);
        this.throwablePoints.clear();
        this.potionRadii.clear();

        for(Entity entity : mc.world.getAllEntities()) {
            if (entity instanceof PotionEntity potion) {
                if (isExplosivePotion(potion)) {
                    this.processPotion(potion);
                }
            }
        }

        tessellator.draw();

        if (!potionRadii.isEmpty()) {
            buffer.begin(6, DefaultVertexFormats.POSITION_COLOR);
            for (PotionRadius radius : potionRadii) {
                Vector3d pos = radius.position;
                float r = radius.radius;
                int baseColor = getPotionColor(radius.potionType);
                int darkColor = ColorUtils.rgba(0, 0, 0, 80);
                int edgeColor = ColorUtils.rgba(0, 0, 0, 30);

                buffer.pos(pos.x, pos.y, pos.z).color(darkColor).endVertex();
                for (int i = 0; i <= 360; i += 10) {
                    double x = pos.x + Math.sin(Math.toRadians(i)) * r;
                    double z = pos.z + Math.cos(Math.toRadians(i)) * r;
                    buffer.pos(x, pos.y, z).color(edgeColor).endVertex();
                }
            }
            tessellator.draw();


            buffer.begin(3, DefaultVertexFormats.POSITION_COLOR);
            for (PotionRadius radius : potionRadii) {
                Vector3d pos = radius.position;
                float r = radius.radius;
                int color = getPotionColor(radius.potionType);

                for (int i = 0; i <= 360; i += 5) {
                    double x = pos.x + Math.sin(Math.toRadians(i)) * r;
                    double z = pos.z + Math.cos(Math.toRadians(i)) * r;
                    buffer.pos(x, pos.y, z).color(color).endVertex();
                }
                buffer.pos(pos.x + Math.sin(0), pos.y, pos.z + Math.cos(0)).color(color).endVertex();

                if (radius.potionType.equals("HARMING") || radius.potionType.equals("POISON")) {
                    for (int i = 0; i <= 360; i += 10) {
                        double x = pos.x + Math.sin(Math.toRadians(i)) * (r * 0.7);
                        double z = pos.z + Math.cos(Math.toRadians(i)) * (r * 0.7);
                        buffer.pos(x, pos.y + 0.1, z).color(color).endVertex();
                    }
                }
            }
            tessellator.draw();
        }

        GL11.glDisable(3042);
        GL11.glDisable(2848);
        GL11.glEnable(3553);
        GL11.glEnable(2929);
        GL11.glPopMatrix();
    }

    private String getEffectText(String potionType) {
        if (potionType.equals("OTHER")) {
            return "Кастомное зелье";
        }
        switch(potionType) {
            case "HARMING": return "Урон";
            case "POISON": return "Яд";
            case "SLOWNESS": return "Замедление";
            case "WEAKNESS": return "Слабость";
            case "INSTANT_HEALTH": return "Мгновенное лечение";
            case "REGENERATION": return "Регенерация";
            case "FIRE_RESISTANCE": return "Огнестойкость";
            case "WATER_BREATHING": return "Подводное дыхание";
            case "INVISIBILITY": return "Невидимость";
            case "NIGHT_VISION": return "Ночное зрение";
            case "JUMP_BOOST": return "Прыжок";
            case "SPEED": return "Скорость";
            case "STRENGTH": return "Сила";
            default: return "Эффект";
        }
    }

    private int getTextColor(String potionType) {
        if (potionType.equals("OTHER")) {
            return ColorUtils.rgba(200, 0, 200, 255);
        }
        switch(potionType) {
            case "HARMING": return ColorUtils.rgba(255, 50, 50, 255);
            case "POISON": return ColorUtils.rgba(50, 255, 50, 255);
            case "SLOWNESS": return ColorUtils.rgba(50, 50, 255, 255);
            case "WEAKNESS": return ColorUtils.rgba(150, 150, 150, 255);
            case "INSTANT_HEALTH": return ColorUtils.rgba(0, 200, 0, 255);
            case "REGENERATION": return ColorUtils.rgba(255, 105, 180, 255);
            case "FIRE_RESISTANCE": return ColorUtils.rgba(255, 100, 0, 255);
            case "WATER_BREATHING": return ColorUtils.rgba(0, 150, 200, 255);
            case "INVISIBILITY": return ColorUtils.rgba(150, 150, 150, 255);
            case "NIGHT_VISION": return ColorUtils.rgba(100, 0, 200, 255);
            case "JUMP_BOOST": return ColorUtils.rgba(0, 255, 150, 255);
            case "SPEED": return ColorUtils.rgba(100, 255, 255, 255);
            case "STRENGTH": return ColorUtils.rgba(255, 100, 0, 255);
            default: return -1;
        }
    }

    private int getPotionColor(String potionType) {
        if (potionType.equals("OTHER")) {
            return ColorUtils.rgba(200, 0, 200, 100);
        }
        switch(potionType) {
            case "HARMING": return ColorUtils.rgba(255, 0, 0, 100);
            case "POISON": return ColorUtils.rgba(0, 255, 0, 100);
            case "SLOWNESS": return ColorUtils.rgba(0, 0, 255, 100);
            case "WEAKNESS": return ColorUtils.rgba(128, 128, 128, 100);
            case "INSTANT_HEALTH": return ColorUtils.rgba(0, 200, 0, 80);
            case "REGENERATION": return ColorUtils.rgba(255, 105, 180, 80);
            case "FIRE_RESISTANCE": return ColorUtils.rgba(255, 100, 0, 80);
            case "WATER_BREATHING": return ColorUtils.rgba(0, 150, 200, 80);
            case "INVISIBILITY": return ColorUtils.rgba(150, 150, 150, 80);
            case "NIGHT_VISION": return ColorUtils.rgba(100, 0, 200, 80);
            case "JUMP_BOOST": return ColorUtils.rgba(0, 255, 150, 80);
            case "SPEED": return ColorUtils.rgba(100, 255, 255, 80);
            case "STRENGTH": return ColorUtils.rgba(255, 100, 0, 80);
            default: return ColorUtils.rgba(255, 165, 0, 100);
        }
    }

    private void processPotion(PotionEntity potion) {
        Vector3d motion = potion.getMotion();
        Vector3d pos = potion.getPositionVec();
        int ticks = 0;
        ResourceLocation texture = new ResourceLocation("textures/item/potion_bottle_splash.png");
        String potionType = getPotionType(potion.getItem());

        for(int i = 0; i < 150; ++i) {
            Vector3d prevPos = pos;
            pos = pos.add(motion);
            motion = this.getNextMotion(potion, motion);
            ColorUtils.setAlpha(ColorUtils.getColor(0), 165);
            buffer.pos(prevPos.x, prevPos.y, prevPos.z).endVertex();
            RayTraceContext rayTraceContext = new RayTraceContext(prevPos, pos, BlockMode.COLLIDER, FluidMode.NONE, potion);
            BlockRayTraceResult blockHitResult = mc.world.rayTraceBlocks(rayTraceContext);
            boolean isLast = blockHitResult.getType() == Type.BLOCK;
            if (isLast) {
                pos = blockHitResult.getHitVec();
                potionRadii.add(new PotionRadius(pos, getRadiusForPotion(potionType), potionType));
            }

            buffer.pos(pos.x, pos.y, pos.z).endVertex();
            if (blockHitResult.getType() == Type.BLOCK || pos.y < -128.0) {
                if (!(motion.lengthSquared() < 0.04)) {
                    this.throwablePoints.add(new ThrowablePoint(pos, ticks, texture));
                }
                break;
            }

            ++ticks;
        }
    }

    private String getPotionType(ItemStack potionStack) {
        List<EffectInstance> effects = PotionUtils.getEffectsFromStack(potionStack);
        if (effects.isEmpty()) {
            return "OTHER";
        }
        EffectInstance mainEffect = effects.get(0);
        ResourceLocation effectId = Registry.EFFECTS.getKey(mainEffect.getPotion());
        if (effectId == null) {
            return "OTHER";
        }
        switch (effectId.getPath()) {
            case "instant_damage": return "HARMING";
            case "poison": return "POISON";
            case "slowness": return "SLOWNESS";
            case "weakness": return "WEAKNESS";
            case "instant_health": return "INSTANT_HEALTH";
            case "regeneration": return "REGENERATION";
            case "fire_resistance": return "FIRE_RESISTANCE";
            case "water_breathing": return "WATER_BREATHING";
            case "invisibility": return "INVISIBILITY";
            case "night_vision": return "NIGHT_VISION";
            case "jump_boost": return "JUMP_BOOST";
            case "speed": return "SPEED";
            case "strength": return "STRENGTH";
            default: return "OTHER";
        }
    }

    private float getRadiusForPotion(String potionType) {
        switch(potionType) {
            case "HARMING": return 4.0f;
            case "POISON": return 3.5f;
            case "SLOWNESS": return 3.0f;
            case "WEAKNESS": return 2.5f;
            case "INSTANT_HEALTH": return 3.0f;
            case "REGENERATION": return 3.0f;
            case "FIRE_RESISTANCE": return 2.5f;
            case "WATER_BREATHING": return 2.5f;
            case "INVISIBILITY": return 2.0f;
            case "NIGHT_VISION": return 2.0f;
            case "JUMP_BOOST": return 2.5f;
            case "SPEED": return 3.0f;
            case "STRENGTH": return 3.0f;
            default: return 3.0f;
        }
    }

    private boolean isExplosivePotion(PotionEntity potion) {
        ItemStack stack = potion.getItem();
        if (stack.getItem() != Items.SPLASH_POTION && stack.getItem() != Items.LINGERING_POTION) {
            return false;
        }
        return !PotionUtils.getEffectsFromStack(stack).isEmpty();
    }

    private Vector3d getNextMotion(Entity entity, Vector3d motion) {
        if (entity instanceof PotionEntity) {
            if (entity.isInWater()) {
                motion = motion.scale(0.8);
            } else {
                motion = motion.scale(0.99);
            }
            if (!entity.hasNoGravity()) {
                motion.y -= 0.05;
            }
        }
        return motion;
    }

    static record ThrowablePoint(Vector3d position, int ticks, ResourceLocation texture) {}
    static record PotionRadius(Vector3d position, float radius, String potionType) {}
}
годно но нужно доработать
 
/up топ фича
 
Назад
Сверху Снизу