summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorMoulberry <james.jenour@student.scotch.wa.edu.au>2020-07-14 10:23:12 +0800
committerMoulberry <james.jenour@student.scotch.wa.edu.au>2020-07-14 10:23:12 +0800
commitf90f0b2f1f234d08742a4f0dd8afcd4b80e26d05 (patch)
treea3b7def680964e2015afb3e3188b955e64679f9a /src/main
parent2254c0fac78dbca807a93648c60c93281b8fb686 (diff)
downloadNotEnoughUpdates-f90f0b2f1f234d08742a4f0dd8afcd4b80e26d05.tar.gz
NotEnoughUpdates-f90f0b2f1f234d08742a4f0dd8afcd4b80e26d05.tar.bz2
NotEnoughUpdates-f90f0b2f1f234d08742a4f0dd8afcd4b80e26d05.zip
something something capes
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/NEUCape.java192
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/NEUCape2.java1
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java15
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/auction/CustomAH.java3
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeManager.java103
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeNode.java371
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/cosmetics/NEUCape.java605
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/cosmetics/ShaderManager.java153
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/infopanes/CollectionLogInfoPane.java8
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/infopanes/CosmeticsInfoPane.java87
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java3
-rw-r--r--src/main/resources/assets/notenoughupdates/contrib.pngbin0 -> 189394 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/fade.pngbin0 -> 18572 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/gravy.pngbin0 -> 58330 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/nullzee.pngbin0 -> 108263 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/shaders/cape.frag16
-rw-r--r--src/main/resources/assets/notenoughupdates/shaders/cape.vert12
-rw-r--r--src/main/resources/assets/notenoughupdates/shaders/fade_cape.frag31
-rw-r--r--src/main/resources/assets/notenoughupdates/shaders/fade_cape.vert12
-rw-r--r--src/main/resources/assets/notenoughupdates/shaders/node.compute39
-rw-r--r--src/main/resources/assets/notenoughupdates/testcape.pngbin0 -> 21571 bytes
21 files changed, 1449 insertions, 202 deletions
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUCape.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUCape.java
deleted file mode 100644
index 1a33a798..00000000
--- a/src/main/java/io/github/moulberry/notenoughupdates/NEUCape.java
+++ /dev/null
@@ -1,192 +0,0 @@
-package io.github.moulberry.notenoughupdates;
-
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.renderer.GlStateManager;
-import net.minecraft.client.renderer.Tessellator;
-import net.minecraft.client.renderer.WorldRenderer;
-import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
-import net.minecraft.entity.Entity;
-import net.minecraft.entity.player.EntityPlayer;
-import net.minecraftforge.client.event.RenderPlayerEvent;
-import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
-import org.lwjgl.input.Keyboard;
-import org.lwjgl.opengl.GL11;
-import org.lwjgl.util.vector.Vector3f;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-public class NEUCape {
-
- private List<List<Node>> nodes = null;
-
- int horzNodes = 20;
- float targetDist = 1/30f;
-
- public void createNodes(EntityPlayer player) {
- nodes = new ArrayList<>();
- for(int i=0; i<50; i++) {
- List<Node> row = new ArrayList<>();
- for(int j=0; j<horzNodes; j++) {
- row.add(new Node(-1, 2-i*targetDist, ((double)j)/(horzNodes-1)));
- }
-
- nodes.add(row);
- }
- for(int y=0; y<nodes.size(); y++) {
- for(int x=0; x<nodes.get(y).size(); x++) {
- for(Direction dir : Direction.values()) {
- for(int i=1; i<=2; i++) {
- Offset offset = new Offset(dir, i);
-
- int xNeighbor = x+offset.getXOffset();
- int yNeighbor = y+offset.getYOffset();
-
- if(xNeighbor >= 0 && xNeighbor < nodes.get(y).size()
- && yNeighbor >= 0 && yNeighbor < nodes.size()) {
- Node neighbor = nodes.get(yNeighbor).get(xNeighbor);
- nodes.get(y).get(x).neighbors.put(offset, neighbor);
- }
- }
- }
- }
- }
- }
-
- public void ensureNodesCreated(EntityPlayer player) {
- if(nodes == null) createNodes(player);
- }
-
- public enum Direction {
- LEFT(-1, 0),
- UP(0, 1),
- RIGHT(1, 0),
- DOWN(0, -1),
- UPLEFT(-1, 1),
- UPRIGHT(1, 1),
- DOWNLEFT(-1, -1),
- DOWNRIGHT(1, -1);
-
- int xOff;
- int yOff;
-
- Direction(int xOff, int yOff) {
- this.xOff = xOff;
- this.yOff = yOff;
- }
- }
-
- public static class Offset {
- Direction direction;
- int steps;
-
- public Offset(Direction direction, int steps) {
- this.direction = direction;
- this.steps = steps;
- }
-
- public int getXOffset() {
- return direction.xOff*steps;
- }
-
- public int getYOffset() {
- return direction.yOff*steps;
- }
-
- public boolean equals(Object obj) {
- if(obj instanceof Offset) {
- Offset other = (Offset) obj;
- return other.direction == direction && other.steps == steps;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return 13*direction.ordinal() + 7*steps;
- }
- }
-
- public static class Node {
- private Vector3f position;
- private Vector3f acceleration = new Vector3f();
- private HashMap<Offset, Node> neighbors = new HashMap<>();
-
- public Node(double x, double y, double z) {
- this.position = new Vector3f((float)x, (float)y, (float)z);
- }
-
- public void updatePosition() {
-
- }
-
- public void renderNode() {
- //System.out.println(neighbors.size());
- if(neighbors.containsKey(new Offset(Direction.DOWNRIGHT, 1))) {
- //System.out.println("trying to render");
- Tessellator tessellator = Tessellator.getInstance();
- WorldRenderer worldrenderer = tessellator.getWorldRenderer();
- GlStateManager.color(1F, 1F, 1F, 1F);
- worldrenderer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION);
-
- Vector3f node2Pos = neighbors.get(new Offset(Direction.DOWN, 1)).position;
- Vector3f node3Pos = neighbors.get(new Offset(Direction.RIGHT, 1)).position;
- Vector3f node4Pos = neighbors.get(new Offset(Direction.DOWNRIGHT, 1)).position;
-
- worldrenderer.pos(position.x, position.y, position.z).endVertex();
- worldrenderer.pos(node2Pos.x, node2Pos.y, node2Pos.z).endVertex();
- worldrenderer.pos(node3Pos.x, node3Pos.y, node3Pos.z).endVertex();
- worldrenderer.pos(node4Pos.x, node4Pos.y, node4Pos.z).endVertex();
-
- tessellator.draw();
- }
- }
- }
-
- @SubscribeEvent
- public void onRenderPlayer(RenderPlayerEvent.Post e) {
- EntityPlayer player = e.entityPlayer;
-
- ensureNodesCreated(player);
- if(Keyboard.isKeyDown(Keyboard.KEY_R)) createNodes(player);
-
- Entity viewer = Minecraft.getMinecraft().getRenderViewEntity();
- double viewerX = viewer.lastTickPosX + (viewer.posX - viewer.lastTickPosX) * e.partialRenderTick;
- double viewerY = viewer.lastTickPosY + (viewer.posY - viewer.lastTickPosY) * e.partialRenderTick;
- double viewerZ = viewer.lastTickPosZ + (viewer.posZ - viewer.lastTickPosZ) * e.partialRenderTick;
-
- GlStateManager.pushMatrix();
- GlStateManager.enableBlend();
- GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA,
- GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
- GlStateManager.disableTexture2D();
- GlStateManager.disableCull();
-
- //GL11.glTranslatef(-(float)viewerX, -(float)viewerY, -(float)viewerZ);
-
- updateCape(player);
- renderCape(player);
-
- //GL11.glTranslatef((float)viewerX, (float)viewerY, (float)viewerZ);
-
- GL11.glEnable(GL11.GL_CULL_FACE);
- GlStateManager.enableTexture2D();
- GlStateManager.disableBlend();
- GlStateManager.popMatrix();
- GlStateManager.color(1F, 1F, 1F, 1F);
- }
-
- private void updateCape(EntityPlayer player) {
-
- }
-
- private void renderCape(EntityPlayer player) {
- for(int y=0; y<nodes.size()-1; y++) {
- for(int x=0; x<nodes.get(y).size()-1; x++) {
- nodes.get(y).get(x).renderNode();
- }
- }
- }
-
-}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUCape2.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUCape2.java
index 4b834cbb..996809da 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/NEUCape2.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUCape2.java
@@ -482,7 +482,6 @@ public class NEUCape2 {
Tessellator tessellator = Tessellator.getInstance();
WorldRenderer worldrenderer = tessellator.getWorldRenderer();
-
//Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText(node1.normalY + " " + node2.normalY + " " + node3.normalY + " " + node4.normalY));
if(offset) {
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java
index 8d01e9a9..0724a132 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java
@@ -8,7 +8,9 @@ import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication;
import io.github.moulberry.notenoughupdates.auction.CustomAHGui;
import io.github.moulberry.notenoughupdates.commands.SimpleCommand;
+import io.github.moulberry.notenoughupdates.cosmetics.CapeManager;
import io.github.moulberry.notenoughupdates.infopanes.CollectionLogInfoPane;
+import io.github.moulberry.notenoughupdates.infopanes.CosmeticsInfoPane;
import io.github.moulberry.notenoughupdates.util.Utils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
@@ -95,7 +97,6 @@ public class NotEnoughUpdates {
private GuiScreen openGui = null;
- ScheduledExecutorService guiDelaySES = Executors.newScheduledThreadPool(1);
SimpleCommand collectionLogCommand = new SimpleCommand("neucl", new SimpleCommand.ProcessCommandRunnable() {
public void processCommand(ICommandSender sender, String[] args) {
if(!OpenGlHelper.isFramebufferEnabled()) {
@@ -111,6 +112,15 @@ public class NotEnoughUpdates {
}
});
+ SimpleCommand cosmeticsCommand = new SimpleCommand("neucosmetics", new SimpleCommand.ProcessCommandRunnable() {
+ public void processCommand(ICommandSender sender, String[] args) {
+ if(!(Minecraft.getMinecraft().currentScreen instanceof GuiContainer)) {
+ openGui = new GuiInventory(Minecraft.getMinecraft().thePlayer);
+ }
+ overlay.displayInformationPane(new CosmeticsInfoPane(overlay, manager));
+ }
+ });
+
SimpleCommand neuAhCommand = new SimpleCommand("neuah", new SimpleCommand.ProcessCommandRunnable() {
public void processCommand(ICommandSender sender, String[] args) {
if(!hasSkyblockScoreboard()) {
@@ -136,11 +146,12 @@ public class NotEnoughUpdates {
public void preinit(FMLPreInitializationEvent event) {
INSTANCE = this;
MinecraftForge.EVENT_BUS.register(this);
- //MinecraftForge.EVENT_BUS.register(new NEUCape());
+ MinecraftForge.EVENT_BUS.register(CapeManager.getInstance());
File f = new File(event.getModConfigurationDirectory(), "notenoughupdates");
f.mkdirs();
ClientCommandHandler.instance.registerCommand(collectionLogCommand);
+ ClientCommandHandler.instance.registerCommand(cosmeticsCommand);
ClientCommandHandler.instance.registerCommand(neuAhCommand);
neuio = new NEUIO(getAccessToken());
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/auction/CustomAH.java b/src/main/java/io/github/moulberry/notenoughupdates/auction/CustomAH.java
index aad82b9f..dd02c887 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/auction/CustomAH.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/auction/CustomAH.java
@@ -1028,8 +1028,6 @@ public class CustomAH extends Gui {
return;
}
- System.out.println("Updating search:"+searchField.getText());
-
lastUpdateSearch = System.currentTimeMillis();
shouldUpdateSearch = false;
@@ -1345,7 +1343,6 @@ public class CustomAH extends Gui {
}
}
- System.out.println();
Utils.playPressSound();
} else if(mouseY > guiTop+126 && mouseY < guiTop+126+16 && !leftFiller) {
priceField.setFocused(true);
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeManager.java b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeManager.java
new file mode 100644
index 00000000..9afbd17f
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeManager.java
@@ -0,0 +1,103 @@
+package io.github.moulberry.notenoughupdates.cosmetics;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraftforge.client.event.RenderPlayerEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+import org.apache.commons.lang3.tuple.MutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+public class CapeManager {
+
+ public static final CapeManager INSTANCE = new CapeManager();
+
+ private HashMap<String, Pair<NEUCape, String>> capeMap = new HashMap<>();
+ private String[] capes = new String[]{"testcape", "nullzee", "gravy", "fade", "contrib"};
+
+ public static CapeManager getInstance() {
+ return INSTANCE;
+ }
+
+ public void setCape(String player, String capename) {
+ if(capename == null) {
+ capeMap.remove(player);
+ return;
+ }
+ if(capeMap.containsKey(player)) {
+ Pair<NEUCape, String> capePair = capeMap.get(player);
+ capePair.setValue(capename);
+ } else {
+ capeMap.put(player, new MutablePair<>(new NEUCape(capename), capename));
+ }
+ }
+
+ public String getCape(String player) {
+ if(capeMap.containsKey(player)) {
+ return capeMap.get(player).getRight();
+ }
+ return null;
+ }
+
+ public EntityPlayer getPlayerForName(String name) {
+ if(Minecraft.getMinecraft().theWorld != null) {
+ for(EntityPlayer player : Minecraft.getMinecraft().theWorld.playerEntities) {
+ if(player.getName().equals(name)) {
+ return player;
+ }
+ }
+ }
+ return null;
+ }
+
+ @SubscribeEvent
+ public void onRenderPlayer(RenderPlayerEvent.Post e) {
+ if(capeMap.containsKey(e.entityPlayer.getName())) {
+ capeMap.get(e.entityPlayer.getName()).getLeft().onRenderPlayer(e);
+ }
+ }
+
+ @SubscribeEvent
+ public void onTick(TickEvent.ClientTickEvent event) {
+ Set<String> toRemove = new HashSet<>();
+ for(String playerName : capeMap.keySet()) {
+ EntityPlayer player = getPlayerForName(playerName);
+ if(player == null) {
+ toRemove.add(playerName);
+ } else {
+ String capeName = capeMap.get(playerName).getRight();
+ if(capeName != null) {
+ capeMap.get(playerName).getLeft().setCapeTexture(capeName);
+ capeMap.get(playerName).getLeft().onTick(event, player);
+ } else {
+ toRemove.add(playerName);
+ }
+ }
+ }
+ for(String playerName : toRemove) {
+ capeMap.remove(playerName);
+ }
+ }
+
+ public String[] getCapes() {
+ return capes;
+ }
+
+ public boolean getPermissionForCape(String player, String capename) {
+ if(capename == null) {
+ return false;
+ } else if(player.equalsIgnoreCase("Moulberry")) {
+ return true; //Oh yeah gimme gimme
+ } else if(capename.equals("nullzee")) {
+ return player.equalsIgnoreCase("Nullzee");
+ } else if(capename.equals("gravy")) {
+ return player.equalsIgnoreCase("ThatGravyBoat");
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeNode.java b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeNode.java
new file mode 100644
index 00000000..1ec2dee3
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeNode.java
@@ -0,0 +1,371 @@
+package io.github.moulberry.notenoughupdates.cosmetics;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.util.BlockPos;
+import net.minecraft.util.MathHelper;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.util.vector.Vector2f;
+import org.lwjgl.util.vector.Vector3f;
+
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class CapeNode {
+
+ private static final NEUCape.Direction[] cardinals = new NEUCape.Direction[]{NEUCape.Direction.UP, NEUCape.Direction.RIGHT, NEUCape.Direction.DOWN, NEUCape.Direction.LEFT};
+
+ public Vector3f position;
+ public Vector3f lastPosition = new Vector3f();
+ public Vector3f renderPosition = new Vector3f();
+ public final Vector3f[] oldRenderPosition = new Vector3f[5];
+ public final Vector3f velocity = new Vector3f();
+ public Vector3f normal = null;
+ public Vector3f sideNormal = null;
+ public boolean fixed = false;
+
+ public HashMap<NEUCape.Offset, CapeNode> neighbors = new HashMap<>();
+
+ public float texU = 0;
+ public float texV = 0;
+
+ public float horzDistMult = 2f;
+ public float vertDistMult = 0.5f;
+
+ public float horzSideTexU = 0;
+ public float horzSideTexVTop = 0;
+
+ public float vertSideTexU = 0;
+ public float vertSideTexVTop = 0;
+
+ public static final float gravity = 0.1f;
+ public static final float resistance = 0.5f;
+
+ public static final int FLOAT_NUM = 20;
+
+ public CapeNode(float x, float y, float z) {
+ this.position = new Vector3f(x, y, z);
+ }
+
+ private List<Vector2f> getConstaints() {
+ List<Vector2f> constaints = new ArrayList<>();
+ for(NEUCape.Direction cardinal : cardinals) {
+ for(int i=1; i<=2; i++) {
+ NEUCape.Offset offset = new NEUCape.Offset(cardinal, i);
+ CapeNode other = neighbors.get(offset);
+ if(other != null) {
+ int iOffset = offset.getXOffset() + NEUCape.HORZ_NODES * offset.getYOffset();
+ constaints.add(new Vector2f(iOffset, i*NEUCape.targetDist*(cardinal.yOff==0?horzDistMult:vertDistMult)));
+ }
+ }
+
+ }
+ return constaints;
+ }
+
+ public void loadIntoBuffer(FloatBuffer buffer) {
+ loadVec3IntoBuffer(buffer, position);
+ List<Vector2f> containts = getConstaints();
+ buffer.put(containts.size());
+ for(int i=0; i<8; i++) {
+ if(i < containts.size()) {
+ loadVec2IntoBuffer(buffer, containts.get(i));
+ } else {
+ loadVec2IntoBuffer(buffer, new Vector2f());
+ }
+ }
+ }
+
+ public void readFromBuffer(FloatBuffer buffer) {
+ readVec3FromBuffer(buffer, position);
+ buffer.position(buffer.position()+17);
+ }
+
+ private void readVec3FromBuffer(FloatBuffer buffer, Vector3f vec) {
+ vec.x = buffer.get();
+ vec.y = buffer.get();
+ vec.z = buffer.get();
+ }
+
+ private void loadVec2IntoBuffer(FloatBuffer buffer, Vector2f vec) {
+ buffer.put(vec.x);
+ buffer.put(vec.y);
+ }
+
+ private void loadVec3IntoBuffer(FloatBuffer buffer, Vector3f vec) {
+ buffer.put(vec.x);
+ buffer.put(vec.y);
+ buffer.put(vec.z);
+ }
+
+ public void update() {
+ if(!fixed) {
+ velocity.y -= gravity * (resistance)/(1-resistance);
+
+ float actualResistance = resistance;
+ BlockPos pos = new BlockPos(
+ MathHelper.floor_double(position.x),
+ MathHelper.floor_double(position.y),
+ MathHelper.floor_double(position.z));
+ Block block = Minecraft.getMinecraft().theWorld.getBlockState(pos).getBlock();
+ if(block.getMaterial().isLiquid()) {
+ actualResistance = 0.8f;
+ }
+
+ velocity.scale(1-actualResistance);
+
+ Vector3f.add(position, velocity, position);
+ }
+ }
+
+ public final CapeNode getNeighbor(NEUCape.Offset offset) {
+ return neighbors.get(offset);
+ }
+
+ public final void setNeighbor(NEUCape.Offset offset, CapeNode node) {
+ neighbors.put(offset, node);
+ }
+
+ public void applyForce(float dX, float dY, float dZ) {
+ velocity.x += dX;
+ velocity.y += dY;
+ velocity.z += dZ;
+ }
+
+ public void move(float dX, float dY, float dZ) {
+ position.x += dX;
+ position.y += dY;
+ position.z += dZ;
+ lastPosition.x = position.x;
+ lastPosition.y = position.y;
+ lastPosition.z = position.z;
+ }
+
+ public void resetNormal() {
+ normal = null;
+ sideNormal = null;
+ }
+
+ public void resolveAll(float horzDistMult, boolean opt) {
+ resolveBend(horzDistMult, opt);
+ //resolveShear();
+ resolveStruct(horzDistMult, opt);
+ }
+
+ public void resolve(CapeNode other, float targetDist, float strength, boolean opt) {
+ if(other == null || Keyboard.isKeyDown(Keyboard.KEY_H)) return;
+
+ double dX = position.x - other.position.x;
+ double dY = position.y - other.position.y;
+ double dZ = position.z - other.position.z;
+
+ double distSq = dX*dX + dY*dY + dZ*dZ;
+
+ double factor = (distSq - targetDist*targetDist)/(40*distSq)*strength;
+
+ factor = Math.max(-0.5f, factor);
+ dX *= factor;
+ dY *= factor;
+ dZ *= factor;
+
+ if(fixed || other.fixed) {
+ dX *= 2;
+ dY *= 2;
+ dZ *= 2;
+ }
+
+ if(!fixed) {
+ position.x -= dX;
+ position.y -= dY;
+ position.z -= dZ;
+ }
+
+ if(!other.fixed) {
+ other.position.x += dX;
+ other.position.y += dY;
+ other.position.z += dZ;
+ }
+ }
+
+ public void resolveStruct(float horzDistMult, boolean opt) {
+ for(NEUCape.Direction cardinal : cardinals) {
+ NEUCape.Offset offset = new NEUCape.Offset(cardinal, 1);
+ CapeNode other = neighbors.get(offset);
+ if(other != null) {
+ resolve(other, NEUCape.targetDist*(cardinal.yOff==0?horzDistMult:1), 2f*7.5f, opt);
+ }
+ }
+ }
+
+ public void resolveShear(float horzDistMult, boolean opt) {
+ for(NEUCape.Direction d : new NEUCape.Direction[]{NEUCape.Direction.DOWNLEFT, NEUCape.Direction.UPLEFT, NEUCape.Direction.DOWNRIGHT, NEUCape.Direction.DOWNLEFT}) {
+ NEUCape.Offset o = new NEUCape.Offset(d, 1);
+ CapeNode neighbor = getNeighbor(o);
+ if(neighbor != null) {
+ if(!Keyboard.isKeyDown(Keyboard.KEY_H))resolve(neighbor, 1f*NEUCape.targetDist*(d.yOff==0?horzDistMult:1f), 0.5f*7.5f, opt);
+ }
+ }
+ }
+
+ public void resolveBend(float horzDistMult, boolean opt) {
+ for(NEUCape.Direction cardinal : cardinals) {
+ NEUCape.Offset offset = new NEUCape.Offset(cardinal, 2);
+ CapeNode other = neighbors.get(offset);
+ if(other != null) {
+ resolve(other, 2f*NEUCape.targetDist*(cardinal.yOff==0?horzDistMult:1), 1f*7.5f, opt);
+ }
+ }
+ }
+
+ public Vector3f normal() {
+ if(normal != null) return normal;
+
+ normal = new Vector3f();
+ for(int i=0; i<cardinals.length; i++) {
+ NEUCape.Direction dir1 = cardinals[i];
+ NEUCape.Direction dir2 = cardinals[(i+1)%cardinals.length];
+ CapeNode node1 = getNeighbor(new NEUCape.Offset(dir1, 1));
+ CapeNode node2 = getNeighbor(new NEUCape.Offset(dir2, 1));
+
+ if(node1 == null || node2 == null) continue;
+
+ Vector3f toCapeNode1 = Vector3f.sub(node1.renderPosition, renderPosition, null);
+ Vector3f toCapeNode2 = Vector3f.sub(node2.renderPosition, renderPosition, null);
+ Vector3f cross = Vector3f.cross(toCapeNode1, toCapeNode2, null);
+ Vector3f.add(normal, cross.normalise(null), normal);
+ }
+ float l = normal.length();
+ if(l != 0) {
+ normal.scale(1f/l);
+ }
+ return normal;
+ }
+
+ public Vector3f sideNormal() {
+ if(sideNormal != null) return sideNormal;
+
+ sideNormal = new Vector3f();
+ NEUCape.Direction[] cardinals = new NEUCape.Direction[]{NEUCape.Direction.UP, NEUCape.Direction.RIGHT, NEUCape.Direction.DOWN, NEUCape.Direction.LEFT};
+ for(NEUCape.Direction cardinal : cardinals) {
+ CapeNode nodeCardinal = getNeighbor(new NEUCape.Offset(cardinal, 1));
+ if(nodeCardinal == null) {
+ NEUCape.Direction dirLeft = cardinal.rotateLeft90();
+ NEUCape.Direction dirRight = cardinal.rotateRight90();
+ CapeNode nodeLeft = getNeighbor(new NEUCape.Offset(dirLeft, 1));
+ CapeNode nodeRight = getNeighbor(new NEUCape.Offset(dirRight, 1));
+
+ if(nodeRight != null) {
+ Vector3f toOther = Vector3f.sub(nodeRight.renderPosition, renderPosition, null);
+ Vector3f cross = Vector3f.cross(normal(), toOther, null); //Inverted
+ Vector3f.add(sideNormal, cross.normalise(null), sideNormal);
+ }
+ if(nodeLeft != null) {
+ Vector3f toOther = Vector3f.sub(nodeLeft.renderPosition, renderPosition, null);
+ Vector3f cross = Vector3f.cross(toOther, normal(), null);
+ Vector3f.add(sideNormal, cross.normalise(null), sideNormal);
+ }
+ }
+ }
+ float l = sideNormal.length();
+ if(l != 0) {
+ sideNormal.scale(0.05f/l);
+ }
+ return sideNormal;
+ }
+
+ public void renderNode() {
+ CapeNode nodeLeft = getNeighbor(new NEUCape.Offset(NEUCape.Direction.LEFT, 1));
+ CapeNode nodeUp = getNeighbor(new NEUCape.Offset(NEUCape.Direction.UP, 1));
+ CapeNode nodeDown = getNeighbor(new NEUCape.Offset(NEUCape.Direction.DOWN, 1));
+ CapeNode nodeRight = getNeighbor(new NEUCape.Offset(NEUCape.Direction.RIGHT, 1));
+ CapeNode nodeDownRight = getNeighbor(new NEUCape.Offset(NEUCape.Direction.DOWNRIGHT, 1));
+
+ Tessellator tessellator = Tessellator.getInstance();
+ WorldRenderer worldrenderer = tessellator.getWorldRenderer();
+
+ if(nodeDown != null && nodeRight != null && nodeDownRight != null) {
+ //Back
+ worldrenderer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_NORMAL);
+ for(CapeNode node : new CapeNode[]{this, nodeDown, nodeRight, nodeDownRight}) {
+ Vector3f nodeNorm = node.normal();
+ worldrenderer.pos(node.renderPosition.x, node.renderPosition.y, node.renderPosition.z)
+ .tex(1-node.texU, node.texV)
+ .normal(-nodeNorm.x, -nodeNorm.y, -nodeNorm.z).endVertex();
+ }
+ tessellator.draw();
+
+ //Front (Offset by normal)
+ worldrenderer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_NORMAL);
+ for(CapeNode node : new CapeNode[]{this, nodeDown, nodeRight, nodeDownRight}) {
+ Vector3f nodeNorm = node.normal();
+ worldrenderer.pos(node.renderPosition.x+nodeNorm.x*0.05f, node.renderPosition.y+nodeNorm.y*0.05f, node.renderPosition.z+nodeNorm.z*0.05f)
+ .tex(node.texU, node.texV)
+ .normal(nodeNorm.x, nodeNorm.y, nodeNorm.z).endVertex();
+ }
+ tessellator.draw();
+
+ /*for(CapeNode node : new CapeNode[]{this, nodeDown, nodeRight, nodeDownRight}) {
+ Vector3f nodeNorm = node.normal();
+ worldrenderer.begin(GL11.GL_LINES, DefaultVertexFormats.POSITION_TEX_NORMAL);
+ worldrenderer.pos(node.renderPosition.x+nodeNorm.x*0.05f, node.renderPosition.y+nodeNorm.y*0.05f, node.renderPosition.z+nodeNorm.z*0.05f)
+ .tex(node.texU, node.texV)
+ .normal(nodeNorm.x, nodeNorm.y, nodeNorm.z).endVertex();
+ worldrenderer.pos(node.renderPosition.x+nodeNorm.x*0.25f, node.renderPosition.y+nodeNorm.y*0.25f, node.renderPosition.z+nodeNorm.z*0.25f)
+ .tex(node.texU, node.texV)
+ .normal(nodeNorm.x, nodeNorm.y, nodeNorm.z).endVertex();
+ tessellator.draw();
+ }*/
+ }
+
+ if(nodeLeft == null || nodeRight == null) {
+ //Render left/right edge
+ if(nodeDown != null) {
+ renderEdge(nodeDown, true);
+ }
+ }
+ if(nodeUp == null || nodeDown == null) {
+ //Render up/down edge
+ if(nodeRight != null) {
+ renderEdge(nodeRight, false);
+ }
+ }
+ }
+
+ public void renderEdge(CapeNode other, boolean lr) {
+ float thisTexU = lr ? this.horzSideTexU : this.vertSideTexU;
+ float thisTexV = lr ? this.horzSideTexVTop : this.vertSideTexVTop;
+ float otherTexU = lr ? other.horzSideTexU : other.vertSideTexU;
+ float otherTexV = lr ? other.horzSideTexVTop : other.vertSideTexVTop;
+
+ Tessellator tessellator = Tessellator.getInstance();
+ WorldRenderer worldrenderer = tessellator.getWorldRenderer();
+
+ Vector3f thisNorm = normal();
+ Vector3f otherNorm = other.normal();
+
+ Vector3f thisSideNorm = sideNormal();
+ Vector3f otherSideNorm = other.sideNormal();
+
+ worldrenderer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_NORMAL);
+ worldrenderer.pos(this.renderPosition.x, this.renderPosition.y, this.renderPosition.z)
+ .tex(thisTexU, thisTexV+20/1024f)
+ .normal(thisSideNorm.x, thisSideNorm.y, thisSideNorm.z).endVertex();
+ worldrenderer.pos(other.renderPosition.x, other.renderPosition.y, other.renderPosition.z)
+ .tex(otherTexU, otherTexV+20/1024f)
+ .normal(otherSideNorm.x, otherSideNorm.y, otherSideNorm.z).endVertex();
+ worldrenderer.pos(this.renderPosition.x+thisNorm.x*0.05f, this.renderPosition.y+thisNorm.y*0.05f, this.renderPosition.z+thisNorm.z*0.05f)
+ .tex(thisTexU, thisTexV)
+ .normal(thisSideNorm.x, thisSideNorm.y, thisSideNorm.z).endVertex();
+ worldrenderer.pos(other.renderPosition.x+otherNorm.x*0.05f, other.renderPosition.y+otherNorm.y*0.05f, other.renderPosition.z+otherNorm.z*0.05f)
+ .tex(otherTexU, otherTexV)
+ .normal(otherSideNorm.x, otherSideNorm.y, otherSideNorm.z).endVertex();
+ tessellator.draw();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/NEUCape.java b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/NEUCape.java
new file mode 100644
index 00000000..8ca3a538
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/NEUCape.java
@@ -0,0 +1,605 @@
+package io.github.moulberry.notenoughupdates.cosmetics;
+
+import io.github.moulberry.notenoughupdates.util.TexLoc;
+import net.minecraft.block.Block;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.OpenGlHelper;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.util.BlockPos;
+import net.minecraft.util.MathHelper;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.world.gen.NoiseGeneratorSimplex;
+import net.minecraftforge.client.event.RenderPlayerEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+import org.lwjgl.BufferUtils;
+import org.lwjgl.Sys;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.opengl.*;
+import org.lwjgl.util.vector.Vector3f;
+
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.security.Key;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class NEUCape {
+
+ public ResourceLocation capeTex = null;
+
+ private List<List<CapeNode>> nodes = null;
+
+ private static double vertOffset = 1.4;
+ private static double shoulderLength = 0.24;
+ private static double shoulderWidth = 0.13;
+
+ public static final int HORZ_NODES = 6;
+ public static final int VERT_NODES = 22;
+
+ public static float targetDist = 1/20f;
+
+ private String shaderName = "cape";
+
+ public NEUCape(String capeName) {
+ setCapeTexture(capeName);
+ }
+
+ public void setCapeTexture(String capeName) {
+ if(capeTex == null || !capeTex.getResourcePath().equals(capeName+".png")) {
+ if(capeName.equalsIgnoreCase("fade")) {
+ shaderName = "fade_cape";
+ } else {
+ shaderName = "cape";
+ }
+ capeTex = new ResourceLocation("notenoughupdates:"+capeName+".png");
+ startTime = System.currentTimeMillis();
+ }
+ }
+
+ public void createCapeNodes(EntityPlayer player) {
+ nodes = new ArrayList<>();
+
+ float pX = (float)player.posX;
+ float pY = (float)player.posY;
+ float pZ = (float)player.posZ;
+
+ float uMinTop = 48/1024f;
+ float uMaxTop = 246/1024f;
+ float uMinBottom = 0/1024f;
+ float uMaxBottom = 293/1024f;
+
+ float vMaxSide = 404/1024f;
+ float vMaxCenter = 419/1024f;
+
+ for(int i=0; i<VERT_NODES; i++) {
+ float uMin = uMinTop + (uMinBottom - uMinTop) * i/(float)(VERT_NODES-1);
+ float uMax = uMaxTop + (uMaxBottom - uMaxTop) * i/(float)(VERT_NODES-1);
+
+ List<CapeNode> row = new ArrayList<>();
+ for(int j=0; j<HORZ_NODES; j++) {
+ float vMin = 0f;
+ float centerMult = 1-Math.abs(j-(HORZ_NODES-1)/2f)/((HORZ_NODES-1)/2f);//0-(horzCapeNodes) -> 0-1-0
+ float vMax = vMaxSide + (vMaxCenter - vMaxSide) * centerMult;
+
+ CapeNode node = new CapeNode(0, 0, 0);//pX-1, pY+2-i*targetDist, pZ+(j-(horzCapeNodes-1)/2f)*targetDist*2
+ node.texU = uMin + (uMax - uMin) * j/(float)(HORZ_NODES-1);
+ node.texV = vMin + (vMax - vMin) * i/(float)(VERT_NODES-1);
+
+ node.horzDistMult = 2f+1f*i/(float)(VERT_NODES-1);
+
+ if(j == 0 || j == HORZ_NODES-1) {
+ node.horzSideTexU = 406/1024f * i/(float)(VERT_NODES-1);
+ if(j == 0) {
+ node.horzSideTexVTop = 1 - 20/1024f;
+ } else {
+ node.horzSideTexVTop = 1 - 40/1024f;
+ }
+ }
+ if(i == 0) {
+ node.vertSideTexU = 198/1024f * j/(float)(HORZ_NODES-1);
+ node.vertSideTexVTop = 1 - 60/1024f;
+ } else if(i == VERT_NODES-1) {
+ node.vertSideTexU = 300/1024f * j/(float)(HORZ_NODES-1);
+ node.vertSideTexVTop = 1 - 80/1024f;
+ }
+ row.add(node);
+ }
+
+ nodes.add(row);
+ }
+ for(int y=0; y<nodes.size(); y++) {
+ int xSize = nodes.get(y).size();
+ for(int x=0; x<xSize; x++) {
+ CapeNode node = nodes.get(y).get(x);
+
+ for(Direction dir : Direction.values()) {
+ for(int i=1; i<=2; i++) {
+ Offset offset = new Offset(dir, i);
+
+ int xNeighbor = x+offset.getXOffset();
+ int yNeighbor = y+offset.getYOffset();
+
+ if(xNeighbor >= 0 && xNeighbor < nodes.get(y).size()
+ && yNeighbor >= 0 && yNeighbor < nodes.size()) {
+ CapeNode neighbor = nodes.get(yNeighbor).get(xNeighbor);
+ node.setNeighbor(offset, neighbor);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void ensureCapeNodesCreated(EntityPlayer player) {
+ if(nodes == null) createCapeNodes(player);
+ }
+
+ public enum Direction {
+ LEFT(-1, 0),
+ UP(0, 1),
+ RIGHT(1, 0),
+ DOWN(0, -1),
+ UPLEFT(-1, 1),
+ UPRIGHT(1, 1),
+ DOWNLEFT(-1, -1),
+ DOWNRIGHT(1, -1);
+
+ int xOff;
+ int yOff;
+
+ Direction(int xOff, int yOff) {
+ this.xOff = xOff;
+ this.yOff = yOff;
+ }
+
+ public Direction rotateRight90() {
+ int wantXOff = -yOff;
+ int wantYOff = xOff;
+ for(Direction dir : values()) {
+ if(dir.xOff == wantXOff && dir.yOff == wantYOff) {
+ return dir;
+ }
+ }
+ return this;
+ }
+
+ public Direction rotateLeft90() {
+ int wantXOff = yOff;
+ int wantYOff = -xOff;
+ for(Direction dir : values()) {
+ if(dir.xOff == wantXOff && dir.yOff == wantYOff) {
+ return dir;
+ }
+ }
+ return this;
+ }
+ }
+
+ public static class Offset {
+ Direction direction;
+ int steps;
+
+ public Offset(Direction direction, int steps) {
+ this.direction = direction;
+ this.steps = steps;
+ }
+
+ public int getXOffset() {
+ return direction.xOff*steps;
+ }
+
+ public int getYOffset() {
+ return direction.yOff*steps;
+ }
+
+ public boolean equals(Object obj) {
+ if(obj instanceof Offset) {
+ Offset other = (Offset) obj;
+ return other.direction == direction && other.steps == steps;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return 13*direction.ordinal() + 7*steps;
+ }
+ }
+
+ private void loadShaderUniforms(ShaderManager shaderManager) {
+ if(shaderName.equalsIgnoreCase("fade_cape")) {
+ shaderManager.loadData(shaderName, "millis", (int)(System.currentTimeMillis()-startTime));
+ }
+ }
+
+ long lastRender = 0;
+ public void onRenderPlayer(RenderPlayerEvent.Post e) {
+ EntityPlayer player = e.entityPlayer;
+
+ ensureCapeNodesCreated(player);
+
+ Entity viewer = Minecraft.getMinecraft().getRenderViewEntity();
+ double viewerX = viewer.lastTickPosX + (viewer.posX - viewer.lastTickPosX) * e.partialRenderTick;
+ double viewerY = viewer.lastTickPosY + (viewer.posY - viewer.lastTickPosY) * e.partialRenderTick;
+ double viewerZ = viewer.lastTickPosZ + (viewer.posZ - viewer.lastTickPosZ) * e.partialRenderTick;
+
+ GlStateManager.pushMatrix();
+ GlStateManager.enableBlend();
+ GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA,
+ GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
+ Minecraft.getMinecraft().getTextureManager().bindTexture(capeTex);
+ GlStateManager.enableTexture2D();
+ GlStateManager.disableCull();
+
+ GL11.glTranslatef(-(float)viewerX, -(float)viewerY, -(float)viewerZ);
+
+ ShaderManager shaderManager = ShaderManager.getInstance();
+ shaderManager.loadShader(shaderName);
+ loadShaderUniforms(shaderManager);
+
+ if(!Keyboard.isKeyDown(Keyboard.KEY_K)) renderCape(player, e.partialRenderTick);
+
+ GL11.glTranslatef((float)viewerX, (float)viewerY, (float)viewerZ);
+
+ GL20.glUseProgram(0);
+
+ GL11.glEnable(GL11.GL_CULL_FACE);
+ GlStateManager.enableTexture2D();
+ GlStateManager.disableBlend();
+ GlStateManager.popMatrix();
+
+ lastRender = System.currentTimeMillis();
+ }
+
+ public void onTick(TickEvent.ClientTickEvent event, EntityPlayer player) {
+ if(player != null && System.currentTimeMillis() - lastRender < 100) {
+ ensureCapeNodesCreated(Minecraft.getMinecraft().thePlayer);
+
+ for(int y=0; y<nodes.size(); y++) {
+ for(int x=0; x<nodes.get(y).size(); x++) {
+ CapeNode node = nodes.get(y).get(x);
+ node.lastPosition.x = node.position.x;
+ node.lastPosition.y = node.position.y;
+ node.lastPosition.z = node.position.z;
+ }
+ }
+ updateCape(player);
+ }
+ }
+
+ private Vector3f updateFixedCapeNodes(EntityPlayer player) {
+ double pX = player.posX;//player.lastTickPosX + (player.posX - player.lastTickPosX) * partialRenderTick;
+ double pY = player.posY;//player.lastTickPosY + (player.posY - player.lastTickPosY) * partialRenderTick;
+ double pZ = player.posZ;//player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * partialRenderTick;
+ double angle = Math.toRadians(player.renderYawOffset);
+
+ double vertOffset2 = vertOffset + (player.isSneaking() ? -0.22f : 0) + (player.getCurrentArmor(2) != null ? 0.06f : 0);
+ double shoulderWidth2 = shoulderWidth + (player.getCurrentArmor(2) != null ? 0.08f : 0);
+
+ float xOff = (float)(Math.cos(angle)*shoulderLength);
+ float zOff = (float)(Math.sin(angle)*shoulderLength);
+
+ float totalDX = 0;
+ float totalDY = 0;
+ float totalDZ = 0;
+ int totalMovements = 0;
+
+ int xSize = nodes.get(0).size();
+ for(int i=0; i<xSize; i++) {
+ float mult = 1 - 2f*i/(xSize-1); //1 -> -1
+ float widthMult = 1.25f-(1.414f*i/(xSize-1) - 0.707f)*(1.414f*i/(xSize-1) - 0.707f);
+ CapeNode node = nodes.get(0).get(i);
+ float x = (float)pX+(float)(xOff*mult-widthMult*Math.cos(angle+Math.PI/2)*shoulderWidth2);
+ float y = (float)pY+(float)(vertOffset2);
+ float z = (float)pZ+(float)(zOff*mult-widthMult*Math.sin(angle+Math.PI/2)*shoulderWidth2);
+ totalDX += x - node.position.x;
+ totalDY += y - node.position.y;
+ totalDZ += z - node.position.z;
+ totalMovements++;
+ node.position.x = x;
+ node.position.y = y;
+ node.position.z = z;
+ node.fixed = true;
+ }
+
+ float avgDX = totalDX/totalMovements;
+ float avgDY = totalDY/totalMovements;
+ float avgDZ = totalDZ/totalMovements;
+
+ return new Vector3f(avgDX, avgDY, avgDZ);
+ }
+
+ private void updateFixedCapeNodesPartial(EntityPlayer player, float partialRenderTick) {
+ double pX = player.lastTickPosX + (player.posX - player.lastTickPosX) * partialRenderTick;
+ double pY = player.lastTickPosY + (player.posY - player.lastTickPosY) * partialRenderTick;
+ double pZ = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * partialRenderTick;
+ double angle = Math.toRadians(player.renderYawOffset);
+
+ double vertOffset2 = vertOffset + (player.isSneaking() ? -0.22f : 0) + (player.getCurrentArmor(2) != null ? 0.06f : 0);
+ double shoulderWidth2 = shoulderWidth + (player.getCurrentArmor(2) != null ? 0.08f : 0);
+
+ float xOff = (float)(Math.cos(angle)*shoulderLength);
+ float zOff = (float)(Math.sin(angle)*shoulderLength);
+
+ int xSize = nodes.get(0).size();
+ for(int i=0; i<xSize; i++) {
+ float mult = 1 - 2f*i/(xSize-1); //1 -> -1
+ float widthMult = 1.25f-(1.414f*i/(xSize-1) - 0.707f)*(1.414f*i/(xSize-1) - 0.707f);
+ CapeNode node = nodes.get(0).get(i);
+ node.renderPosition.x = (float)pX+(float)(xOff*mult-widthMult*Math.cos(angle+Math.PI/2)*shoulderWidth2);
+ node.renderPosition.y = (float)pY+(float)(vertOffset2);
+ node.renderPosition.z = (float)pZ+(float)(zOff*mult-widthMult*Math.sin(angle+Math.PI/2)*shoulderWidth2);
+ node.fixed = true;
+ }
+ }
+
+ TexLoc tl = new TexLoc(10, 75, Keyboard.KEY_M);
+
+ private double deltaAngleAccum;
+ private double oldPlayerAngle;
+ private int crouchTicks = 0;
+ long startTime = 0;
+ long updateMillis = 0;
+ long renderMillis = 0;
+ private void updateCape(EntityPlayer player) {
+ Vector3f capeTranslation = updateFixedCapeNodes(player);
+
+ if(System.currentTimeMillis() - lastRender > 100) {
+ for (int y = 0; y < nodes.size(); y++) {
+ for (int x = 0; x < nodes.get(y).size(); x++) {
+ Vector3f.add(nodes.get(y).get(x).position, capeTranslation, nodes.get(y).get(x).position);
+ nodes.get(y).get(x).lastPosition.set(nodes.get(y).get(x).position);
+ nodes.get(y).get(x).renderPosition.set(nodes.get(y).get(x).position);
+ }
+ }
+ } else {
+ double playerAngle = Math.toRadians(player.renderYawOffset);
+ double deltaAngle = playerAngle - oldPlayerAngle;
+ if(deltaAngle > Math.PI) {
+ deltaAngle = 2*Math.PI - deltaAngle;
+ }
+ if(deltaAngle < -Math.PI) {
+ deltaAngle = 2*Math.PI + deltaAngle;
+ }
+ deltaAngleAccum *= 0.5f;
+ deltaAngleAccum += deltaAngle;
+
+ float dX = (float)Math.cos(playerAngle+Math.PI/2f);
+ float dZ = (float)Math.sin(playerAngle+Math.PI/2f);
+
+ float factor = (float)(deltaAngleAccum*deltaAngleAccum);
+
+ tl.handleKeyboardInput();
+
+ float capeTransLength = capeTranslation.length();
+
+ float capeTranslationFactor = 0f;
+ if(capeTransLength > 0.5f) {
+ capeTranslationFactor = (capeTransLength-0.5f)/capeTransLength;
+ }
+ Vector3f lookDir = new Vector3f(dX, 0, dZ);
+ Vector3f lookDirNorm = lookDir.normalise(null);
+ float dot = Vector3f.dot(capeTranslation, lookDirNorm);
+ if(dot < 0) { //Moving backwards
+ for(int y=0; y<nodes.size(); y++) {
+ for(int x=0; x<nodes.get(y).size(); x++) {
+ CapeNode node = nodes.get(y).get(x);
+ if(!node.fixed) {
+ node.position.x += lookDirNorm.x*dot;
+ node.position.y += lookDirNorm.y*dot;
+ node.position.z += lookDirNorm.z*dot;
+ }
+ }
+ }
+ //Apply small backwards force
+ factor = 0.05f;
+ }
+
+ if(factor > 0) {
+ for(int y=0; y<nodes.size(); y++) {
+ for(int x=0; x<nodes.get(y).size(); x++) {
+ nodes.get(y).get(x).applyForce(-dX*factor, 0, -dZ*factor);
+ }
+ }
+ }
+
+ if(capeTranslationFactor > 0f) {
+ float capeDX = capeTranslation.x*capeTranslationFactor;
+ float capeDY = capeTranslation.y*capeTranslationFactor;
+ float capeDZ = capeTranslation.z*capeTranslationFactor;
+
+ for(int y=0; y<nodes.size(); y++) {
+ for(int x=0; x<nodes.get(y).size(); x++) {
+ CapeNode node = nodes.get(y).get(x);
+ if(!node.fixed) {
+ node.position.x += capeDX;
+ node.position.y += capeDY;
+ node.position.z += capeDZ;
+ }
+ }
+ }
+ }
+
+ //Wind
+ float currTime = (System.currentTimeMillis()-startTime)/1000f;
+ float windRandom = Math.abs((float)(0.5f*Math.sin(0.22f*currTime)+Math.sin(0.44f*currTime)*Math.sin(0.47f*currTime)));
+ double windDir = playerAngle+Math.PI/4f*Math.sin(0.2f*currTime);
+
+ float windDX = (float)Math.cos(windDir+Math.PI/2f);
+ float windDZ = (float)Math.sin(windDir+Math.PI/2f);
+ for(int y=0; y<nodes.size(); y++) {
+ for(int x=0; x<nodes.get(y).size(); x++) {
+ nodes.get(y).get(x).applyForce(-windDX*windRandom*0.01f, 0, -windDZ*windRandom*0.01f);
+ }
+ }
+
+ if(player.isSneaking()) {
+ crouchTicks++;
+ float mult = 0.5f;
+ if(crouchTicks < 5) {
+ mult = 2f;
+ }
+ for(int y=0; y<8; y++) {
+ for(int x=0; x<nodes.get(y).size(); x++) {
+ nodes.get(y).get(x).applyForce(-dX*mult, 0, -dZ*mult);
+ }
+ }
+ } else {
+ crouchTicks = 0;
+ }
+
+ oldPlayerAngle = playerAngle;
+
+ for(int y=0; y<nodes.size(); y++) {
+ for(int x=0; x<nodes.get(y).size(); x++) {
+ nodes.get(y).get(x).update();
+ }
+ }
+ int updates = player == Minecraft.getMinecraft().thePlayer ? 50 : 25;
+ for(int i=0; i<updates; i++) {
+ for(int y=0; y<nodes.size(); y++) {
+ for(int x=0; x<nodes.get(y).size(); x++) {
+ nodes.get(y).get(x).resolveAll(2+1f*y/nodes.size(), false);
+ }
+ }
+ }
+ }
+ }
+
+ private int ssbo = -1;
+ private void generateSSBO() {
+ ssbo = GL15.glGenBuffers();
+ loadSBBO();
+ }
+
+ private void loadSBBO() {
+ FloatBuffer buff = BufferUtils.createFloatBuffer(CapeNode.FLOAT_NUM*HORZ_NODES*VERT_NODES);
+ for(int y=0; y<VERT_NODES; y++) {
+ for(int x=0; x<HORZ_NODES; x++) {
+ nodes.get(y).get(x).loadIntoBuffer(buff);
+ }
+ }
+ buff.flip();
+
+ GL15.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, ssbo);
+ GL15.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, buff, GL15.GL_DYNAMIC_DRAW);
+ GL15.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, 0);
+ }
+
+ private void resolveAllCompute() {
+ if(ssbo == -1) {
+ generateSSBO();
+ }
+ loadSBBO();
+
+ int program = ShaderManager.getInstance().getShader("node");
+
+ int block_index = GL43.glGetProgramResourceIndex(program, GL43.GL_SHADER_STORAGE_BLOCK, "nodes_buffer");
+ int ssbo_binding_point_index = 0;
+ GL43.glShaderStorageBlockBinding(program, block_index, ssbo_binding_point_index);
+ int binding_point_index = 0;
+ GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, binding_point_index, ssbo);
+
+ GL20.glUseProgram(program);
+
+ for(int i=0; i<30; i++) {
+ GL43.glDispatchCompute(VERT_NODES, 1, 1);
+ GL42.glMemoryBarrier(GL43.GL_SHADER_STORAGE_BARRIER_BIT);
+ }
+
+ GL20.glUseProgram(0);
+
+ FloatBuffer buff = BufferUtils.createFloatBuffer(CapeNode.FLOAT_NUM*HORZ_NODES*VERT_NODES);
+
+ GL15.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, ssbo);
+ GL15.glGetBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, 0, buff);
+ GL15.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, 0);
+
+ for(int y=0; y<VERT_NODES; y++) {
+ for(int x=0; x<HORZ_NODES; x++) {
+ nodes.get(y).get(x).readFromBuffer(buff);
+ }
+ }
+ }
+
+ private Vector3f avgFixedRenderPosition() {
+ Vector3f accum = new Vector3f();
+ int numFixed = 0;
+ for(int y=0; y<nodes.size(); y++) {
+ for(int x=0; x<nodes.get(y).size(); x++) {
+ CapeNode node = nodes.get(y).get(x);
+ if(node.fixed) {
+ Vector3f.add(accum, node.renderPosition, accum);
+ numFixed++;
+ }
+ }
+ }
+ if(numFixed != 0) {
+ accum.scale(1f/numFixed);
+ }
+ return accum;
+ }
+
+ private void renderCape(EntityPlayer player, float partialRenderTick) {
+ ensureCapeNodesCreated(player);
+ if(System.currentTimeMillis() - lastRender > 100) {
+ updateCape(player);
+ }
+ updateFixedCapeNodesPartial(player, partialRenderTick);
+
+ for(int y=0; y<nodes.size(); y++) {
+ for(int x=0; x<nodes.get(y).size(); x++) {
+ CapeNode node = nodes.get(y).get(x);
+
+ node.resetNormal();
+
+ if(node.fixed) continue;
+
+ Vector3f avgPositionFixed = avgFixedRenderPosition();
+
+ Vector3f newPosition = new Vector3f();
+ newPosition.x = node.lastPosition.x + (node.position.x - node.lastPosition.x) * partialRenderTick;
+ newPosition.y = node.lastPosition.y + (node.position.y - node.lastPosition.y) * partialRenderTick;
+ newPosition.z = node.lastPosition.z + (node.position.z - node.lastPosition.z) * partialRenderTick;
+
+ if(node.oldRenderPosition[node.oldRenderPosition.length-1] == null || Keyboard.isKeyDown(Keyboard.KEY_B)) {
+ node.renderPosition = newPosition;
+ } else {
+ Vector3f accum = new Vector3f();
+ for(int i=0; i<node.oldRenderPosition.length; i++) {
+ Vector3f.add(accum, node.oldRenderPosition[i], accum);
+ Vector3f.add(accum, avgPositionFixed, accum);
+ }
+ accum.scale(1/(float)node.oldRenderPosition.length);
+
+ float blendFactor = 0.5f+0.3f*y/(float)(nodes.size()-1); //0.5/0.5 -> 0.8/0.2 //0-1
+ accum.scale(blendFactor);
+ newPosition.scale(1-blendFactor);
+ Vector3f.add(accum, newPosition, accum);
+ node.renderPosition = accum;
+ }
+
+ for(int i=node.oldRenderPosition.length-1; i>=0; i--) {
+ if(i > 0) {
+ node.oldRenderPosition[i] = node.oldRenderPosition[i-1];
+ } else {
+ node.oldRenderPosition[i] = Vector3f.sub(node.renderPosition, avgPositionFixed, null);
+ }
+ }
+ }
+ }
+ for(int y=0; y<nodes.size(); y++) {
+ for(int x=0; x<nodes.get(y).size(); x++) {
+ nodes.get(y).get(x).renderNode();
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/ShaderManager.java b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/ShaderManager.java
new file mode 100644
index 00000000..7d74ae22
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/ShaderManager.java
@@ -0,0 +1,153 @@
+package io.github.moulberry.notenoughupdates.cosmetics;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.util.ResourceLocation;
+import org.lwjgl.opengl.GL20;
+import org.lwjgl.opengl.GL43;
+import org.lwjgl.util.vector.Vector3f;
+import org.lwjgl.util.vector.Vector4f;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+
+public class ShaderManager {
+
+ private ResourceLocation shaderLocation = new ResourceLocation("notenoughupdates:shaders");
+ private HashMap<String, Shader> shaderMap = new HashMap<>();
+
+ private static final ShaderManager INSTANCE = new ShaderManager();
+
+ public static ShaderManager getInstance() {
+ return INSTANCE;
+ }
+
+ public class Shader {
+ public final int program;
+
+ public Shader(int program) {
+ this.program = program;
+ }
+ }
+
+ public int getShader(String name) {
+ if(!shaderMap.containsKey(name)) {
+ reloadShader(name);
+ }
+ return shaderMap.get(name).program;
+ }
+
+ public int loadShader(String name) {
+ if(!shaderMap.containsKey(name)) {
+ reloadShader(name);
+ }
+ GL20.glUseProgram(shaderMap.get(name).program);
+ return shaderMap.get(name).program;
+ }
+
+ public void loadData(String name, String var, Object value) {
+ int location = GL20.glGetUniformLocation(shaderMap.get(name).program, var);
+
+ if(value instanceof Integer) {
+ GL20.glUniform1i(location, (Integer) value);
+ } else if(value instanceof Float) {
+ GL20.glUniform1f(location, (Float) value);
+ } else if(value instanceof Vector3f) {
+ Vector3f vec = (Vector3f) value;
+ GL20.glUniform3f(location, vec.x, vec.y, vec.z);
+ } else if(value instanceof Vector4f) {
+ Vector4f vec = (Vector4f) value;
+ GL20.glUniform4f(location, vec.x, vec.y, vec.z, vec.w);
+ } else {
+ throw new UnsupportedOperationException("Failed to load data into shader: Unsupported data type.");
+ }
+ }
+
+ private void reloadShader(String name) {
+ int vertex = -1;
+ String sourceVert = getShaderSource(name, GL20.GL_VERTEX_SHADER);
+ if(!sourceVert.isEmpty()) {
+ vertex = GL20.glCreateShader(GL20.GL_VERTEX_SHADER);
+ GL20.glShaderSource(vertex, sourceVert);
+ GL20.glCompileShader(vertex);
+
+ if (GL20.glGetShaderi(vertex, 35713) == 0) {
+ System.err.println(GL20.glGetShaderInfoLog(vertex, 100));
+ }
+ }
+
+ int fragment = -1;
+ String sourceFrag = getShaderSource(name, GL20.GL_FRAGMENT_SHADER);
+ if(!sourceFrag.isEmpty()) {
+ fragment = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER);
+ GL20.glShaderSource(fragment, sourceFrag);
+ GL20.glCompileShader(fragment);
+
+ if (GL20.glGetShaderi(fragment, 35713) == 0) {
+ System.err.println(GL20.glGetShaderInfoLog(fragment, 100));
+ }
+ }
+
+ int compute = -1;
+ String sourceCompute = getShaderSource(name, GL43.GL_COMPUTE_SHADER);
+ if(!sourceCompute.isEmpty()) {
+ compute = GL20.glCreateShader(GL43.GL_COMPUTE_SHADER);
+ GL20.glShaderSource(compute, sourceCompute);
+ GL20.glCompileShader(compute);
+
+ if (GL20.glGetShaderi(compute, 35713) == 0) {
+ System.err.println(GL20.glGetShaderInfoLog(compute, 100));
+ }
+ }
+
+ int program = GL20.glCreateProgram();
+ if(vertex != -1) GL20.glAttachShader(program, vertex);
+ if(fragment != -1) GL20.glAttachShader(program, fragment);
+ if(compute != -1) GL20.glAttachShader(program, compute);
+
+ GL20.glLinkProgram(program);
+
+ if(vertex != -1) GL20.glDeleteShader(vertex);
+ if(fragment != -1) GL20.glDeleteShader(fragment);
+ if(compute != -1) GL20.glDeleteShader(compute);
+
+ if (GL20.glGetProgrami(program, 35714) == 0) {
+ System.err.println(GL20.glGetProgramInfoLog(program, 100));
+ }
+ GL20.glValidateProgram(program);
+ if (GL20.glGetProgrami(program, 35715) == 0) {
+ System.err.println(GL20.glGetProgramInfoLog(program, 100));
+ }
+
+ shaderMap.put(name, new Shader(program));
+ }
+
+ public String getShaderSource(String name, int type) {
+ String ext = "";
+ if(type == GL20.GL_VERTEX_SHADER) {
+ ext = ".vert";
+ } else if(type == GL20.GL_FRAGMENT_SHADER) {
+ ext = ".frag";
+ } else if(type == GL43.GL_COMPUTE_SHADER) {
+ ext = ".compute";
+ } else {
+ return "";
+ }
+ ResourceLocation location = new ResourceLocation(shaderLocation.getResourceDomain(),
+ shaderLocation.getResourcePath()+"/"+name+ext);
+ try(InputStream is = Minecraft.getMinecraft().getResourceManager().getResource(location).getInputStream()) {
+ StringBuilder source = new StringBuilder();
+ BufferedReader br = new BufferedReader(new InputStreamReader(is));
+
+ String line;
+ while((line = br.readLine()) != null) {
+ source.append(line).append("\n");
+ }
+ return source.toString();
+ } catch(IOException e) {
+ }
+ return "";
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/CollectionLogInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/CollectionLogInfoPane.java
index 8d53a152..38edc0f9 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/CollectionLogInfoPane.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/CollectionLogInfoPane.java
@@ -392,10 +392,12 @@ public class CollectionLogInfoPane extends ScrollableInfoPane {
String[] items = getItemList();
iterateItemSlots(new ItemSlotConsumer() {
public void consume(int x, int y, int id) {
- String internalname = items[id];
+ if(id < items.length) {
+ String internalname = items[id];
- ItemStack stack = manager.jsonToStack(manager.getItemInformation().get(internalname));
- Utils.drawItemStack(stack, x, y);
+ ItemStack stack = manager.jsonToStack(manager.getItemInformation().get(internalname));
+ Utils.drawItemStack(stack, x, y);
+ }
}
}, left, right, top, bottom);
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/CosmeticsInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/CosmeticsInfoPane.java
new file mode 100644
index 00000000..e972e2c8
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/CosmeticsInfoPane.java
@@ -0,0 +1,87 @@
+package io.github.moulberry.notenoughupdates.infopanes;
+
+import io.github.moulberry.notenoughupdates.cosmetics.CapeManager;
+import io.github.moulberry.notenoughupdates.NEUManager;
+import io.github.moulberry.notenoughupdates.NEUOverlay;
+import io.github.moulberry.notenoughupdates.util.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.util.ResourceLocation;
+
+import java.awt.*;
+import java.util.HashMap;
+
+public class CosmeticsInfoPane extends InfoPane {
+
+ public CosmeticsInfoPane(NEUOverlay overlay, NEUManager manager) {
+ super(overlay, manager);
+ }
+
+ private HashMap<String, ResourceLocation> capeTextures = new HashMap<>();
+
+ private String selectedCape = null;
+
+ public void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX,
+ int mouseY) {
+ super.renderDefaultBackground(width, height, bg);
+
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+ int currentY = overlay.getBoxPadding()+10;
+ fr.drawString("NEU Capes", overlay.getBoxPadding()+10, currentY, Color.WHITE.getRGB(), true); currentY += 10;
+
+ selectedCape = null;
+ for(String cape : CapeManager.getInstance().getCapes()) {
+ if(CapeManager.getInstance().getPermissionForCape(Minecraft.getMinecraft().thePlayer.getName(), cape)) {
+ currentY += renderCapeSelector(cape, currentY, mouseX, mouseY);
+ currentY += 5;
+ }
+ }
+ }
+
+ public int renderCapeSelector(String capename, int y, int mouseX, int mouseY) {
+ if(mouseX > overlay.getBoxPadding()+5 && mouseX < overlay.getBoxPadding()+75) {
+ if(mouseY > y && mouseY < y+100) {
+ selectedCape = capename;
+ }
+ }
+ boolean selected = capename.equals(CapeManager.getInstance().getCape(Minecraft.getMinecraft().thePlayer.getName()));
+
+ if(selected) {
+ drawRect(overlay.getBoxPadding()+5, y, overlay.getBoxPadding()+75, y+100, Color.YELLOW.getRGB());
+ drawGradientRect(overlay.getBoxPadding()+10, y+5, overlay.getBoxPadding()+70, y+95, Color.GRAY.darker().getRGB(), Color.GRAY.getRGB());
+ } else {
+ drawGradientRect(overlay.getBoxPadding()+5, y, overlay.getBoxPadding()+75, y+100, Color.GRAY.darker().getRGB(), Color.GRAY.getRGB());
+ }
+
+ GlStateManager.color(1, 1, 1, 1);
+
+ ResourceLocation capeTex = capeTextures.computeIfAbsent(capename, k -> new ResourceLocation("notenoughupdates:"+capename+".png"));
+
+ Minecraft.getMinecraft().getTextureManager().bindTexture(capeTex);
+ Utils.drawTexturedRect(overlay.getBoxPadding()+10, y+10, 60, 80, 0, 300/1024f, 0, 425/1024f);
+ return 100;
+ }
+
+ public void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown) {
+ if(mouseDown && selectedCape != null) {
+ if(selectedCape.equals(CapeManager.getInstance().getCape(Minecraft.getMinecraft().thePlayer.getName()))) {
+ for(EntityPlayer player : Minecraft.getMinecraft().theWorld.playerEntities) {
+ CapeManager.getInstance().setCape(player.getName(), null);
+ }
+ } else {
+ for(EntityPlayer player : Minecraft.getMinecraft().theWorld.playerEntities) {
+ CapeManager.getInstance().setCape(player.getName(), selectedCape);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean keyboardInput() {
+ return false;
+ }
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java b/src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java
index 84d970d5..c271d63a 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java
@@ -9,6 +9,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
@@ -45,7 +46,7 @@ public class HypixelApi {
return null;
}
- try(BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
+ try(BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder builder = new StringBuilder();
int codePoint;
while((codePoint = reader.read()) != -1) {
diff --git a/src/main/resources/assets/notenoughupdates/contrib.png b/src/main/resources/assets/notenoughupdates/contrib.png
new file mode 100644
index 00000000..51699e6e
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/contrib.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/fade.png b/src/main/resources/assets/notenoughupdates/fade.png
new file mode 100644
index 00000000..d898ec4d
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/fade.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/gravy.png b/src/main/resources/assets/notenoughupdates/gravy.png
new file mode 100644
index 00000000..e43ba7d2
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/gravy.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/nullzee.png b/src/main/resources/assets/notenoughupdates/nullzee.png
new file mode 100644
index 00000000..5939034b
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/nullzee.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/shaders/cape.frag b/src/main/resources/assets/notenoughupdates/shaders/cape.frag
new file mode 100644
index 00000000..cbe00c70
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/shaders/cape.frag
@@ -0,0 +1,16 @@
+#version 120
+
+varying vec4 passColour;
+varying vec3 passNormal;
+uniform sampler2D textureIn;
+
+void main() {
+ vec4 texture = texture2D(textureIn, gl_TexCoord[0].st);
+ gl_FragColor = texture * passColour;
+
+ vec3 fakeSunNormal = normalize(vec3(0.2f,1f,-0.2f));
+ vec3 normNormal = normalize(passNormal);
+ float shading = max(0.6f, dot(fakeSunNormal, normNormal));
+
+ gl_FragColor = vec4(gl_FragColor.rgb*shading, gl_FragColor.a);//gl_FragColor.rgb*shading
+} \ No newline at end of file
diff --git a/src/main/resources/assets/notenoughupdates/shaders/cape.vert b/src/main/resources/assets/notenoughupdates/shaders/cape.vert
new file mode 100644
index 00000000..2b5c48f8
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/shaders/cape.vert
@@ -0,0 +1,12 @@
+#version 120
+
+varying vec4 passColour;
+varying vec3 passNormal;
+
+void main() {
+ gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+ gl_TexCoord[0] = gl_MultiTexCoord0;
+
+ passColour = gl_Color;
+ passNormal = normalize(gl_Normal);
+} \ No newline at end of file
diff --git a/src/main/resources/assets/notenoughupdates/shaders/fade_cape.frag b/src/main/resources/assets/notenoughupdates/shaders/fade_cape.frag
new file mode 100644
index 00000000..76738c31
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/shaders/fade_cape.frag
@@ -0,0 +1,31 @@
+#version 120
+
+varying vec4 passColour;
+varying vec3 passNormal;
+uniform sampler2D textureIn;
+
+uniform int millis;
+
+//Algorithm by hughsk
+vec3 hsv2rgb(vec3 c) {
+ vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+ vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+void main() {
+ vec4 texture = texture2D(textureIn, gl_TexCoord[0].st);
+
+ float hue = mod(millis/10000f+gl_TexCoord[0].t, 1f);
+ float sat = 0.5f;
+ float val = 0.5f;
+ vec3 fade = hsv2rgb(vec3(hue, sat, val));
+
+ gl_FragColor = vec4(texture.rgb*texture.a + fade*(1-texture.a), 1f) * passColour;
+
+ vec3 fakeSunNormal = normalize(vec3(0.2f,1f,-0.2f));
+ vec3 normNormal = normalize(passNormal);
+ float shading = max(0.6f, dot(fakeSunNormal, normNormal));
+
+ gl_FragColor = vec4(gl_FragColor.rgb*shading, gl_FragColor.a);
+} \ No newline at end of file
diff --git a/src/main/resources/assets/notenoughupdates/shaders/fade_cape.vert b/src/main/resources/assets/notenoughupdates/shaders/fade_cape.vert
new file mode 100644
index 00000000..2b5c48f8
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/shaders/fade_cape.vert
@@ -0,0 +1,12 @@
+#version 120
+
+varying vec4 passColour;
+varying vec3 passNormal;
+
+void main() {
+ gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+ gl_TexCoord[0] = gl_MultiTexCoord0;
+
+ passColour = gl_Color;
+ passNormal = normalize(gl_Normal);
+} \ No newline at end of file
diff --git a/src/main/resources/assets/notenoughupdates/shaders/node.compute b/src/main/resources/assets/notenoughupdates/shaders/node.compute
new file mode 100644
index 00000000..df60dfdc
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/shaders/node.compute
@@ -0,0 +1,39 @@
+#version 430 compatibility
+#extension GL_ARB_compute_shader : enable
+#extension GL_ARB_shader_storage_buffer_object : enable
+
+#define HORZ_NODES 6
+#define VERT_NODES 22
+
+#define INDEX gl_GlobalInvocationID.x
+
+#define TARGET_DIST 0.05f
+
+layout(local_size_x = HORZ_NODES, local_size_y = 1, local_size_z = 1) in;
+
+struct Node {
+ vec3 position;
+ float numInfluences;
+ vec2 influences[8];
+};
+
+layout(std430, binding=0) buffer nodes_buffer {
+ Node nodes[HORZ_NODES*VERT_NODES];
+};
+
+
+void resolve(vec2 influence, float strength) {
+ vec3 dist = nodes[INDEX+int(influence.x)].position - nodes[INDEX].position;
+ float l = length(dist);
+ float factor = strength*(l - influence.y)/(2f*l);
+
+ if(INDEX >= HORZ_NODES) nodes[INDEX].position = nodes[INDEX].position + dist * factor;
+}
+
+void main() {
+ float kPrime = 1.0 - pow(1-0.9f, 1.0/30f); //K = 0.9f
+ kPrime = 0.5f;
+
+ int influences = int(nodes[INDEX].numInfluences);
+ for(int i=0; i<influences; i++) resolve(nodes[INDEX].influences[i], kPrime);
+} \ No newline at end of file
diff --git a/src/main/resources/assets/notenoughupdates/testcape.png b/src/main/resources/assets/notenoughupdates/testcape.png
new file mode 100644
index 00000000..4b8ba499
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/testcape.png
Binary files differ