Java tutorial
/** * 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; }