cn.academy.ability.client.ui.CPBar.java Source code

Java tutorial

Introduction

Here is the source code for cn.academy.ability.client.ui.CPBar.java

Source

/**
* Copyright (c) Lambda Innovation, 2013-2016
* This file is part of the AcademyCraft mod.
* https://github.com/LambdaInnovation/AcademyCraft
* Licensed under GPLv3, see project root for more information.
*/
package cn.academy.ability.client.ui;

import cn.academy.ability.api.Category;
import cn.academy.ability.api.context.ClientRuntime;
import cn.academy.ability.api.context.ContextManager;
import cn.academy.ability.api.context.IConsumptionProvider;
import cn.academy.ability.api.data.AbilityData;
import cn.academy.ability.api.data.CPData;
import cn.academy.ability.api.data.PresetData;
import cn.academy.ability.api.event.PresetSwitchEvent;
import cn.academy.core.AcademyCraft;
import cn.academy.core.client.ACRenderingHelper;
import cn.academy.core.Resources;
import cn.academy.core.client.ui.ACHud;
import cn.lambdalib.annoreg.core.Registrant;
import cn.lambdalib.annoreg.mc.RegInitCallback;
import cn.lambdalib.cgui.gui.Widget;
import cn.lambdalib.cgui.gui.component.DrawTexture;
import cn.lambdalib.cgui.gui.component.Transform.WidthAlign;
import cn.lambdalib.cgui.gui.event.FrameEvent;
import cn.lambdalib.util.client.HudUtils;
import cn.lambdalib.util.client.RenderUtils;
import cn.lambdalib.util.client.font.IFont;
import cn.lambdalib.util.client.font.IFont.FontAlign;
import cn.lambdalib.util.client.font.IFont.FontOption;
import cn.lambdalib.util.client.shader.ShaderProgram;
import cn.lambdalib.util.generic.MathUtils;
import cn.lambdalib.util.generic.RandUtils;
import cn.lambdalib.util.helper.Color;
import cn.lambdalib.util.helper.GameTimer;
import cn.lambdalib.vis.curve.CubicCurve;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.MinecraftForge;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;

import javax.vecmath.Vector2d;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * @author WeAthFolD
 */
@SideOnly(Side.CLIENT)
@Registrant
public class CPBar extends Widget {

    public static final CPBar instance = new CPBar();

    static final double WIDTH = 964, HEIGHT = 147;
    static final double SCALE = 0.2;

    static final float CP_BALANCE_SPEED = 2.0f, O_BALANCE_SPEED = 2.0f;

    static double sin41 = Math.sin(Math.toRadians(44.0));

    static IConsumptionHintProvider chProvider;

    @RegInitCallback
    private static void init() {
        ACHud.instance.addElement(instance, () -> true, "cpbar",
                new Widget().size(WIDTH, HEIGHT).scale(SCALE).walign(WidthAlign.RIGHT)
                        .addComponent(new DrawTexture().setTex(Resources.getTexture("guis/edit_preview/cpbar"))));
    }

    /**
     * Please use IConsumptionProvider with {@link cn.academy.ability.api.context.Context} instead.
     */
    @Deprecated
    public static void setHintProvider(IConsumptionHintProvider provider) {
        chProvider = provider;
    }

    public static ResourceLocation TEX_BACK_NORMAL = tex("back_normal"), TEX_BACK_OVERLOAD = tex("back_overload"),
            TEX_CP = tex("cp"), TEX_FRONT_OVERLOAD = tex("front_overload"), TEX_OVERLOADED = tex("overloaded"),
            TEX_OVERLOAD_HIGHLIGHT = tex("highlight_overload"), TEX_MASK = tex("mask");

    List<ProgColor> cpColors = new ArrayList<>(), overrideColors = new ArrayList<>();

    @Deprecated
    public interface IConsumptionHintProvider {
        boolean alive();

        float getConsumption();
    }

    long presetChangeTime, lastPresetTime;

    boolean lastFrameActive;
    long lastDrawTime;
    long showTime;

    boolean showingNumbers;
    long lastShowValueChange;

    float mAlpha; //Master alpha, used for blending in.

    float bufferedCP;
    float bufferedOverload;

    boolean shaderLoaded = false;

    ResourceLocation overlayTexture;

    // Inteference display

    long maxtime;
    List<OffsetKeyframe> frames = new ArrayList<>();
    CubicCurve alphaCurve = new CubicCurve();

    class OffsetKeyframe {
        long time;
        Vector2d direction;
    }

    {
        final double aspect = WIDTH / HEIGHT, offsetMax = 9;
        final int iteration = 60;

        alphaCurve.addPoint(0, RandUtils.ranged(0.2, 0.8));

        int sum = 0;
        for (int i = 0; i < iteration; ++i) {
            OffsetKeyframe frame = new OffsetKeyframe();
            int thistime = RandUtils.rangei(80, 400);
            float offsetNorm = RandUtils.rangef(0, 1);
            float theta = RandUtils.rangef(0, MathUtils.PI_F * 2);
            offsetNorm = offsetNorm * offsetNorm * offsetNorm;

            sum += thistime;

            frame.time = sum;
            frame.direction = new Vector2d(Math.sin(theta) * offsetNorm * offsetMax * aspect,
                    Math.cos(theta) * offsetNorm * offsetMax);
            frames.add(frame);

            alphaCurve.addPoint(sum, RandUtils.ranged(0.4, 0.7));
        }

        maxtime = sum;
    }

    OffsetKeyframe int_get() {
        long timeInput = GameTimer.getAbsTime() % maxtime;
        return frames.stream().filter(f -> f.time > timeInput).findFirst().get();
    }

    //

    private CPBar() {
        try { // Safety check. If loading failed, fallback to not using shader.
            this.shaderCPBar = new ShaderCPBar();
            this.shaderOverloaded = new ShaderOverloaded();
            shaderLoaded = true;
        } catch (Exception e) {
            AcademyCraft.log.error("Errow while loading CPBar shader", e);
        }

        transform.setSize(WIDTH, HEIGHT);
        transform.scale = SCALE;
        transform.alignWidth = WidthAlign.RIGHT;
        transform.setPos(-12, 12);

        initEvents();

        cpColors.add(new ProgColor(0.0, new Color(0xfff06767)));
        cpColors.add(new ProgColor(0.35, new Color(0xffffae44)));
        cpColors.add(new ProgColor(1.0, new Color(0xffffffff)));

        overrideColors.add(new ProgColor(0.0, new Color(0x0Adfdfdf)));
        overrideColors.add(new ProgColor(0.55, new Color(0x23f0d49d)));
        overrideColors.add(new ProgColor(1.0, new Color(0x50f56464)));

        MinecraftForge.EVENT_BUS.register(this);
    }

    @SubscribeEvent
    public void onSwitchPreset(PresetSwitchEvent event) {
        lastPresetTime = presetChangeTime;
        presetChangeTime = GameTimer.getTime();
    }

    public void startDisplayNumbers() {
        showingNumbers = true;
        lastShowValueChange = GameTimer.getTime();
    }

    public void stopDisplayNumbers() {
        showingNumbers = false;
        long time = GameTimer.getTime();
        if (time - lastShowValueChange > 400) {
            lastShowValueChange = time;
        } else
            lastShowValueChange = 0;
    }

    private void initEvents() {
        listen(FrameEvent.class, (w, e) -> {
            EntityPlayer player = Minecraft.getMinecraft().thePlayer;
            CPData cpData = CPData.get(player);
            AbilityData aData = AbilityData.get(player);
            if (!aData.hasCategory())
                return;

            Category c = aData.getCategory();
            overlayTexture = c.getOverlayIcon();

            boolean active = cpData.isActivated();

            // Calculate alpha
            long time = GameTimer.getTime();
            if (!lastFrameActive && active) {
                showTime = time;
            }

            // Takes account of interference
            long deltaTime = Math.min(100L, time - lastDrawTime);

            final long BLENDIN_TIME = 200L;
            mAlpha = (time - showTime < BLENDIN_TIME) ? (float) (time - showTime) / BLENDIN_TIME
                    : (active ? 1.0f : Math.max(0.0f, 1 - (time - lastDrawTime) / 200.0f));

            boolean interf = cpData.isInterfering();
            boolean overloadRecovering = cpData.isOverloadRecovering();

            if (interf) {
                OffsetKeyframe frame = int_get();
                GL11.glTranslated(frame.direction.x, frame.direction.y, 0);
                long timeInput = GameTimer.getAbsTime() % maxtime;
                timeInput = (timeInput / 10) * 10; // Lower the precision to produce 'jagged' effect
                mAlpha *= alphaCurve.valueAt(timeInput);
            }

            GL11.glPushMatrix(); // PUSH 1

            float poverload = mAlpha > 0 ? cpData.getOverload() / cpData.getMaxOverload() : 0;
            bufferedOverload = balance(bufferedOverload, poverload, deltaTime * 1E-3f * O_BALANCE_SPEED);

            float pcp = mAlpha > 0 ? cpData.getCP() / cpData.getMaxCP() : 0;
            bufferedCP = balance(bufferedCP, pcp, deltaTime * 1E-3f * CP_BALANCE_SPEED);

            if (mAlpha > 0) {
                /* Draw CPBar */ {
                    if (!cpData.isOverloaded()) {
                        drawNormal(bufferedOverload);
                    } else {
                        drawOverload(bufferedOverload);
                    }

                    if (chProvider != null && !chProvider.alive())
                        chProvider = null;

                    float estmCons = getConsumptionHint();
                    boolean low = interf || overloadRecovering;

                    if (estmCons != 0) {
                        float ncp = Math.max(0, cpData.getCP() - estmCons);

                        float oldAlpha = mAlpha;
                        mAlpha *= 0.2f + 0.1f * (1 + Math.sin(time / 80.0f));

                        drawCPBar(pcp, low);

                        mAlpha = oldAlpha;

                        drawCPBar(ncp / cpData.getMaxCP(), low);
                    } else {
                        drawCPBar(bufferedCP, low);
                    }
                }

                /* Draw Preset Hint */ {
                    final long preset_wait = 2000L;
                    if (time - presetChangeTime < preset_wait)
                        drawPresetHint((double) (time - presetChangeTime) / preset_wait, time - lastPresetTime);
                }

                // Draw data
                {
                    float alpha;
                    long dt = lastShowValueChange == 0 ? Long.MAX_VALUE : time - lastShowValueChange;

                    if (cpData.isOverloaded()) {
                        alpha = 0.0f;
                    } else if (showingNumbers) {
                        alpha = MathUtils.clampf(0, 1, (dt - 200) / 400f); // Delay display by 200ms for visual pleasure
                    } else if (dt < 300f) {
                        alpha = 1 - dt / 300f;
                    } else {
                        alpha = 0.0f;
                    }

                    if (alpha > 0) {
                        final double x0 = 110;

                        IFont font = Resources.font();
                        FontOption option = new FontOption(40);
                        option.color.a = 0.6f * mAlpha * alpha;

                        String str10 = "CP ";
                        String str11 = String.format("%.0f", cpData.getCP());
                        String str12 = String.format("/%.0f", cpData.getMaxCP());

                        String str20 = "OL ";
                        String str21 = String.format("%.0f", cpData.getOverload());
                        String str22 = String.format("/%.0f", cpData.getMaxOverload());

                        double len10 = font.getTextWidth(str10, option), len11 = font.getTextWidth(str11, option),
                                len20 = font.getTextWidth(str20, option), len21 = font.getTextWidth(str21, option);

                        double len0 = Math.max(len10, len20);
                        double len1 = len0 + Math.max(len11, len21);

                        font.draw(str10, x0, 55, option);
                        font.draw(str12, x0 + len1, 55, option);
                        font.draw(str20, x0, 85, option);
                        font.draw(str22, x0 + len1, 85, option);

                        option.align = FontAlign.RIGHT;
                        font.draw(str11, x0 + len1, 55, option);
                        font.draw(str21, x0 + len1, 85, option);
                    }
                }

                drawActivateKeyHint();
            }

            if (active) {
                lastDrawTime = time;
            }

            lastFrameActive = active;

            GL11.glColor4d(1, 1, 1, 1);
            GL11.glPopMatrix(); // Pop 1
        });
    }

    private void drawOverload(float overload) {
        //Draw plain background
        color4d(1, 1, 1, 0.8);
        RenderUtils.loadTexture(TEX_BACK_OVERLOAD);
        HudUtils.rect(WIDTH, HEIGHT);

        // Draw back
        color4d(1, 1, 1, 1);

        if (shaderLoaded) {
            shaderOverloaded.useProgram();
            shaderOverloaded.updateTexOffset((GameTimer.getTime() % 10000L) / 10000.0f);
        }

        GL13.glActiveTexture(GL13.GL_TEXTURE0 + 4);
        GL11.glEnable(GL11.GL_TEXTURE_2D);
        RenderUtils.loadTexture(TEX_MASK);

        GL13.glActiveTexture(GL13.GL_TEXTURE0);
        RenderUtils.loadTexture(TEX_FRONT_OVERLOAD);

        final double x0 = 30, width2 = WIDTH - x0 - 20;
        HudUtils.rect(x0, 0, 0, 0, width2, HEIGHT, width2, HEIGHT);

        GL13.glActiveTexture(GL13.GL_TEXTURE0 + 4);
        GL11.glDisable(GL11.GL_TEXTURE_2D);
        GL13.glActiveTexture(GL13.GL_TEXTURE0);

        GL20.glUseProgram(0);

        // Highlight
        color4d(1, 1, 1, 0.3 + 0.35 * (Math.sin(GameTimer.getTime() / 200.0) + 1));
        RenderUtils.loadTexture(TEX_OVERLOAD_HIGHLIGHT);
        HudUtils.rect(WIDTH, HEIGHT);
    }

    private void drawNormal(float overload) {
        RenderUtils.loadTexture(TEX_BACK_NORMAL);

        color4d(1, 1, 1, .8);
        HudUtils.rect(WIDTH, HEIGHT);

        //Overload progress
        final double X0 = 0, Y0 = 21, WIDTH = 943, HEIGHT = 104;

        autoLerp(overrideColors, overload);
        double len = overload * WIDTH;

        RenderUtils.loadTexture(TEX_MASK);
        subHud(X0 + WIDTH - len, Y0, len, HEIGHT);
    }

    private float getConsumptionHint() {
        Optional<IConsumptionProvider> provider = ContextManager.instance.findLocal(IConsumptionProvider.class);

        if (provider.isPresent())
            return provider.get().getConsumptionHint();
        if (chProvider != null)
            return chProvider.getConsumption(); // Legacy fallback

        return 0;
    }

    private void drawCPBar(float prog, boolean cantuse) {
        float pre_mAlpha = mAlpha;
        if (cantuse) {
            mAlpha *= 0.3f;
        }

        //We need a cut-angle effect so this must be done manually
        autoLerp(cpColors, prog);

        prog = 0.16f + prog * 0.8f;

        final double OFF = 103 * sin41, X0 = 47, Y0 = 30, WIDTH = 883, HEIGHT = 84;
        Tessellator t = Tessellator.instance;
        double len = WIDTH * prog, len2 = len - OFF;

        if (shaderLoaded) {
            shaderCPBar.useProgram();
        }

        GL13.glActiveTexture(GL13.GL_TEXTURE0 + 4);
        GL11.glEnable(GL11.GL_TEXTURE_2D);
        RenderUtils.loadTexture(overlayTexture);

        GL13.glActiveTexture(GL13.GL_TEXTURE0);
        RenderUtils.loadTexture(TEX_CP);

        t.startDrawingQuads();
        addVertex(X0 + (WIDTH - len), Y0);
        addVertex(X0 + (WIDTH - len2), Y0 + HEIGHT);
        addVertex(X0 + WIDTH, Y0 + HEIGHT);
        addVertex(X0 + WIDTH, Y0);
        t.draw();

        GL20.glUseProgram(0);

        GL13.glActiveTexture(GL13.GL_TEXTURE0 + 4);
        GL11.glDisable(GL11.GL_TEXTURE_2D);
        GL13.glActiveTexture(GL13.GL_TEXTURE0);

        mAlpha = pre_mAlpha;
    }

    final Color CRL_P_BACK = new Color().setColor4i(48, 48, 48, 160),
            CRL_P_FORE = new Color().setColor4i(255, 255, 255, 200);
    final Color temp = new Color();

    FontOption fo_PresetHint = new FontOption(46, FontAlign.CENTER);

    private void drawPresetHint(double progress, long untilLast) {
        final double x0 = 580, y0 = 136;
        final double size = 52, step = size + 10;

        double x = x0, y = y0;

        int cur = PresetData.get(Minecraft.getMinecraft().thePlayer).getCurrentID();

        double alpha;
        if (untilLast > 3000 && progress < 0.2) {
            alpha = progress / 0.2;
        } else if (progress > 0.8) {
            alpha = (1 - progress) / 0.2;
        } else {
            alpha = 1;
        }
        alpha *= 0.75;

        for (int i = 0; i < 4; ++i) {
            CRL_P_BACK.a = alpha;
            CRL_P_BACK.bind();
            HudUtils.colorRect(x, y, size, size);

            temp.a = Math.max(0.05, alpha * 0.8);

            fo_PresetHint.color = temp;
            Resources.fontBold().draw(String.valueOf(i + 1), x + size / 2, y + 5, fo_PresetHint);

            temp.bind();
            if (i == cur) {
                ACRenderingHelper.drawGlow(x, y, size, size, 5, CRL_P_FORE);
            }

            x += step;
        }

    }

    static final Color CRL_KH_BACK = new Color().setColor4i(65, 65, 65, 70),
            CRL_KH_GLOW = new Color().setColor4i(255, 255, 255, 40);

    FontOption fo_ActivateHint = new FontOption(44, FontAlign.RIGHT, new Color(0xa0ffffff));

    private void drawActivateKeyHint() {
        Optional<String> hint = ClientRuntime.instance().getActivateHandler().getHintTranslated();

        if (hint.isPresent()) {
            String str = hint.get();

            final double x0 = 500, y0 = 140, MARGIN = 8;
            CRL_KH_BACK.bind();

            IFont font = Resources.font();
            double len = font.getTextWidth(str, fo_ActivateHint);
            HudUtils.colorRect(x0 - MARGIN - len, y0 - MARGIN, len + MARGIN * 2, 44 + MARGIN * 2);
            ACRenderingHelper.drawGlow(x0 - MARGIN - len, y0 - MARGIN, len + MARGIN * 2, 44 + MARGIN * 2, 5,
                    CRL_KH_GLOW);
            font.draw(str, x0, y0, fo_ActivateHint);
        }
    }

    private void color4d(double r, double g, double b, double a) {
        GL11.glColor4d(r, g, b, mAlpha * a);
    }

    private void subHud(double x, double y, double width, double height) {
        Tessellator t = Tessellator.instance;
        t.startDrawingQuads();
        addVertex(x, y);
        addVertex(x, y + height);
        addVertex(x + width, y + height);
        addVertex(x + width, y);
        t.draw();
    }

    private void addVertex(double x, double y) {
        double width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH),
                height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT);
        Tessellator.instance.addVertexWithUV(x, y, -90, x / width, y / height);
    }

    private void lerpBindColor(Color a, Color b, double factor) {
        color4d(lerp(a.r, b.r, factor), lerp(a.g, b.g, factor), lerp(a.b, b.b, factor), lerp(a.a, b.a, factor));
    }

    private void autoLerp(List<ProgColor> list, double prog) {
        for (int i = 0; i < list.size(); ++i) {
            ProgColor cur = list.get(i);
            if (cur.prog >= prog) {
                if (i == 0) {
                    list.get(i).color.bind();
                } else {
                    ProgColor last = list.get(i - 1);
                    lerpBindColor(last.color, cur.color, (prog - last.prog) / (cur.prog - last.prog));
                }
                return;
            }
        }
        throw new RuntimeException("bad progress: " + prog); //Should never reach here
    }

    private double lerp(double a, double b, double factor) {
        return a * (1 - factor) + b * factor;
    }

    private float balance(float from, float to, float max) {
        float delta = to - from;
        delta = Math.signum(delta) * Math.min(max, Math.abs(delta));

        return from + delta;
    }

    private static ResourceLocation tex(String name) {
        return new ResourceLocation("academy:textures/guis/cpbar/" + name + ".png");
    }

    private static class ProgColor {
        double prog;
        Color color;

        public ProgColor(double _p, Color _c) {
            prog = _p;
            color = _c;
        }
    }

    private static class ShaderOverloaded extends ShaderProgram {

        final int locTexOffset;

        private ShaderOverloaded() {
            this.linkShader(new ResourceLocation("lambdalib:shaders/simple.vert"), GL20.GL_VERTEX_SHADER);
            this.linkShader(new ResourceLocation("academy:shaders/cpbar_overload.frag"), GL20.GL_FRAGMENT_SHADER);
            this.compile();

            useProgram();
            GL20.glUniform1i(getUniformLocation("samplerTex"), 0);
            GL20.glUniform1i(getUniformLocation("samplerMask"), 4);
            GL20.glUseProgram(0);

            locTexOffset = getUniformLocation("texOffset");
        }

        public void updateTexOffset(float val) {
            GL20.glUniform1f(locTexOffset, val);
        }

    }

    private static class ShaderCPBar extends ShaderProgram {

        private ShaderCPBar() {
            this.linkShader(new ResourceLocation("lambdalib:shaders/simple.vert"), GL20.GL_VERTEX_SHADER);
            this.linkShader(new ResourceLocation("academy:shaders/cpbar_cp.frag"), GL20.GL_FRAGMENT_SHADER);
            this.compile();

            useProgram();
            GL20.glUniform1i(getUniformLocation("samplerTex"), 0);
            GL20.glUniform1i(getUniformLocation("samplerIcon"), 4);
            GL20.glUseProgram(0);
        }

    }

    ShaderCPBar shaderCPBar;
    ShaderOverloaded shaderOverloaded;

}