/*
* Copyright (C) 2022 NotEnoughUpdates contributors
*
* This file is part of NotEnoughUpdates.
*
* NotEnoughUpdates is free software: you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* NotEnoughUpdates is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with NotEnoughUpdates. If not, see .
*/
package io.github.moulberry.notenoughupdates.miscfeatures;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe;
import io.github.moulberry.notenoughupdates.util.ReverseWorldRenderer;
import io.github.moulberry.notenoughupdates.util.SpecialColour;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.renderer.vertex.VertexFormatElement;
import net.minecraft.entity.Entity;
import net.minecraft.entity.item.EntityArmorStand;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.BlockPos;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import org.lwjgl.opengl.GL11;
import java.awt.*;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@NEUAutoSubscribe
public class CrystalOverlay {
private enum CrystalType {
FARMING_MINION(8, 0xDAA520),
MINING_MINION(40, 0x6e5a49),
FORAGING_MINION(12, 0x01a552),
DESERT(16, 0xfff178),
FISHING(15, 0x1972a6),
WART(5, 0x821530),
WHEAT(6, 0xff9d00),
WINTER(16, 0x3ffcff);
CrystalType(int radius, int rgb) {
this.radius = radius;
this.rgb = rgb;
}
public Set getCircleOffsets() {
if (circleOffsets != null) return circleOffsets;
circleOffsets = new HashSet<>();
for (int x = -radius; x <= radius; x++) {
for (int y = -radius; y <= radius; y++) {
for (int z = -radius; z <= radius; z++) {
float distSq = (x - 0.5f) * (x - 0.5f) + y * y + (z - 0.5f) * (z - 0.5f);
if (distSq > (radius - 1) * (radius - 1) && distSq < radius * radius) {
circleOffsets.add(new BlockPos(x, y, z));
}
}
}
}
return circleOffsets;
}
public ReverseWorldRenderer getOverlayVBO() {
if (overlayVBO != null) return overlayVBO;
EntityPlayerSP p = Minecraft.getMinecraft().thePlayer;
if (p == null) return null;
if (!crystals.containsKey(this)) {
return null;
}
//per vertex = 6
//per size = 4
//per block = 8
//total per block = 196
ReverseWorldRenderer worldRenderer = new ReverseWorldRenderer(196 * getCircleOffsets().size());
worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR);
String col = SpecialColour.special(0, 180, rgb);
for (BlockPos offset : getCircleOffsets()) {
BlockPos overlayPos = new BlockPos(offset.getX(), offset.getY(), offset.getZ());
AxisAlignedBB bb = new AxisAlignedBB(
overlayPos.getX(),
overlayPos.getY(),
overlayPos.getZ(),
overlayPos.getX() + 1,
overlayPos.getY() + 1,
overlayPos.getZ() + 1
).expand(0.001f * (this.ordinal() + 1), 0.001f * (this.ordinal() + 1), 0.001f * (this.ordinal() + 1));
uploadFilledBoundingBox(bb, 1f, col, worldRenderer);
}
overlayVBO = worldRenderer;
return overlayVBO;
}
ReverseWorldRenderer overlayVBO = null;
Set circleOffsets = null;
int updates = 0;
int rgb;
int radius;
}
private static double posLastUpdateX;
private static double posLastUpdateY;
private static double posLastUpdateZ;
private static final HashMap skullId = new HashMap() {{
put("d9c3168a-8654-3dd8-b297-4d3b7e55b95a", CrystalType.FARMING_MINION);
put("949d100c-aa74-3b09-a642-af5529f808aa", CrystalType.MINING_MINION);
put("bd79a474-cf07-3f8c-b5a4-98657c33520a", CrystalType.FORAGING_MINION);
put("2e474ee3-5361-3218-84db-880eb1cface1", CrystalType.FISHING);
put("7a237e5c-ca9a-3dc1-b1d9-b385fc200aa7", CrystalType.WINTER);
}};
public static long displayMillis = 0;
public static long lastMiningUpdate = 0;
public static HashMap crystals = new HashMap<>();
public static void tick() {
if (!NotEnoughUpdates.INSTANCE.config.itemOverlays.enableCrystalOverlay) return;
if (Minecraft.getMinecraft().theWorld == null) return;
EntityPlayerSP p = Minecraft.getMinecraft().thePlayer;
if (p == null) return;
long currentTime = System.currentTimeMillis();
if (NotEnoughUpdates.INSTANCE.config.itemOverlays.alwaysShowCrystal) {
displayMillis = currentTime;
} else {
if (currentTime - displayMillis > 10 * 1000) {
crystals.clear();
displayMillis = -1;
}
ItemStack held = p.getHeldItem();
String internal = NotEnoughUpdates.INSTANCE.manager.getInternalNameForItem(held);
if (internal != null) {
if (internal.endsWith("_CRYSTAL") && !internal.equals("POWER_CRYSTAL")) {
displayMillis = currentTime;
}
}
if (displayMillis < 0) {
return;
}
}
Set foundTypes = new HashSet<>();
for (Entity entity : Minecraft.getMinecraft().theWorld.loadedEntityList) {
if (entity instanceof EntityArmorStand) {
EntityArmorStand armorStand = (EntityArmorStand) entity;
if (armorStand.isChild() && armorStand.getEquipmentInSlot(4) != null) {
ItemStack helmet = armorStand.getEquipmentInSlot(4);
if (helmet.getItem() == Items.skull && helmet.hasTagCompound()) {
NBTTagCompound tag = helmet.getTagCompound();
if (tag.hasKey("SkullOwner", 10)) {
NBTTagCompound skullOwner = tag.getCompoundTag("SkullOwner");
if (skullOwner.hasKey("Id", 8)) {
String id = skullOwner.getString("Id");
if (skullId.containsKey(id)) {
CrystalType type = skullId.get(id);
foundTypes.add(type);
BlockPos pos = new BlockPos(armorStand.posX, armorStand.posY + 0.5f, armorStand.posZ);
if (crystals.containsKey(type)) {
BlockPos old = crystals.get(type);
if (old.equals(pos)) {
type.updates = 0;
} else {
if (++type.updates >= 3) {
type.updates = 0;
crystals.put(type, pos);
}
}
} else {
crystals.put(type, pos);
}
}
}
}
}
}
}
}
crystals.keySet().retainAll(foundTypes);
}
@SubscribeEvent
public void onTick(TickEvent.ClientTickEvent event) {
if (!NotEnoughUpdates.INSTANCE.config.itemOverlays.enableCrystalOverlay) return;
if (displayMillis < 0) {
return;
}
EntityPlayerSP p = Minecraft.getMinecraft().thePlayer;
if (p == null) return;
if (event.phase == TickEvent.Phase.START) {
double dX = p.posX - posLastUpdateX;
double dY = p.posY - posLastUpdateY;
double dZ = p.posZ - posLastUpdateZ;
if (dX * dX + dY * dY + dZ * dZ < 1) {
return;
}
posLastUpdateX = p.posX;
posLastUpdateY = p.posY;
posLastUpdateZ = p.posZ;
for (CrystalType type : crystals.keySet()) {
if (type == CrystalType.MINING_MINION) {
long currentTime = System.currentTimeMillis();
if (currentTime - lastMiningUpdate < 1000) {
continue;
}
lastMiningUpdate = currentTime;
}
ReverseWorldRenderer worldRenderer = type.getOverlayVBO();
if (worldRenderer != null) {
BlockPos crystal = crystals.get(type);
worldRenderer.setTranslation(0, 0, 0);
worldRenderer.sortVertexData(
(float) p.posX - crystal.getX(),
(float) p.posY - crystal.getY(),
(float) p.posZ - crystal.getZ()
);
/*es.submit(() -> worldRenderer.sortVertexData(
(float)p.posX-crystal.getX(),
(float)p.posY-crystal.getY(),
(float)p.posZ-crystal.getZ()));*/
}
}
}
}
@SubscribeEvent
public void onRenderLast(RenderWorldLastEvent event) {
if (!NotEnoughUpdates.INSTANCE.config.itemOverlays.enableCrystalOverlay) return;
if (displayMillis < 0) {
return;
}
Entity viewer = Minecraft.getMinecraft().getRenderViewEntity();
double viewerX = viewer.lastTickPosX + (viewer.posX - viewer.lastTickPosX) * event.partialTicks;
double viewerY = viewer.lastTickPosY + (viewer.posY - viewer.lastTickPosY) * event.partialTicks;
double viewerZ = viewer.lastTickPosZ + (viewer.posZ - viewer.lastTickPosZ) * event.partialTicks;
GlStateManager.enableBlend();
GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0);
GlStateManager.disableTexture2D();
GlStateManager.translate(-viewerX, -viewerY, -viewerZ);
GL11.glPolygonOffset(5, 5);
for (CrystalType type : crystals.keySet()) {
ReverseWorldRenderer worldRenderer = type.getOverlayVBO();
if (worldRenderer != null && worldRenderer.getVertexCount() > 0) {
BlockPos crystal = crystals.get(type);
GlStateManager.translate(crystal.getX(), crystal.getY(), crystal.getZ());
VertexFormat vertexformat = worldRenderer.getVertexFormat();
int stride = vertexformat.getNextOffset();
ByteBuffer bytebuffer = worldRenderer.getByteBuffer();
List list = vertexformat.getElements();
for (int index = 0; index < list.size(); index++) {
VertexFormatElement vertexformatelement = list.get(index);
vertexformatelement.getUsage().preDraw(vertexformat, index, stride, bytebuffer);
}
GL11.glDrawArrays(worldRenderer.getDrawMode(), 0, worldRenderer.getVertexCount());
for (int index = 0; index < list.size(); index++) {
VertexFormatElement vertexformatelement = list.get(index);
vertexformatelement.getUsage().postDraw(vertexformat, index, stride, bytebuffer);
}
GlStateManager.translate(-crystal.getX(), -crystal.getY(), -crystal.getZ());
}
}
GL11.glPolygonOffset(0, 0);
GlStateManager.translate(viewerX, viewerY, viewerZ);
GlStateManager.enableTexture2D();
}
@SubscribeEvent
public void onWorldUnload(WorldEvent.Unload event) {
crystals.clear();
}
public static void uploadFilledBoundingBox(
AxisAlignedBB p_181561_0_,
float alpha,
String special,
ReverseWorldRenderer worldrenderer
) {
Color c = new Color(SpecialColour.specialToChromaRGB(special), true);
//vertical
worldrenderer
.pos(p_181561_0_.minX, p_181561_0_.minY, p_181561_0_.minZ)
.color(c.getRed() / 255f, c.getGreen() / 255f, c.getBlue() / 255f, c.getAlpha() / 255f * alpha)
.endVertex();
worldrenderer
.pos(p_181561_0_.maxX, p_181561_0_.minY, p_181561_0_.minZ)
.color(c.getRed() / 255f, c.getGreen() / 255f, c.getBlue() / 255f, c.getAlpha() / 255f * alpha)
.endVertex();
worldrenderer
.pos(p_181561_0_.maxX, p_181561_0_.minY, p_181561_0_.maxZ)
.color(c.getRed() / 255f, c.getGreen() / 255f, c.getBlue() / 255f, c.getAlpha() / 255f * alpha)
.endVertex();
worldrenderer
.pos(p_181561_0_.minX, p_181561_0_.minY, p_181561_0_.maxZ)
.color(c.getRed() / 255f, c.getGreen() / 255f, c.getBlue() / 255f, c.getAlpha() / 255f * alpha)
.endVertex();
worldrenderer
.pos(p_181561_0_.minX, p_181561_0_.maxY, p_181561_0_.maxZ)
.color(c.getRed() / 255f, c.getGreen() / 255f, c.getBlue() / 255f, c.getAlpha() / 255f * alpha)
.endVertex();
worldrenderer
.pos(p_181561_0_.maxX, p_181561_0_.maxY, p_181561_0_.maxZ)
.color(c.getRed() / 255f, c.getGreen() / 255f, c.getBlue() / 255f, c.getAlpha() / 255f * alpha)
.endVertex();
worldrenderer
.pos(p_181561_0_.maxX, p_181561_0_.maxY, p_181561_0_.minZ)
.color(c.getRed() / 255f, c.getGreen() / 255f, c.getBlue() / 255f, c.getAlpha() / 255f * alpha)
.endVertex();
worldrenderer
.pos(p_181561_0_.minX, p_181561_0_.maxY, p_181561_0_.minZ)
.color(c.getRed() / 255f, c.getGreen() / 255f, c.getBlue() / 255f, c.getAlpha() / 255f * alpha)
.endVertex();
//x
worldrenderer.pos(p_181561_0_.minX, p_181561_0_.minY, p_181561_0_.maxZ)
.color(
c.getRed() / 255f * 0.8f,
c.getGreen() / 255f * 0.8f,
c.getBlue() / 255f * 0.8f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.minX, p_181561_0_.maxY, p_181561_0_.maxZ)
.color(
c.getRed() / 255f * 0.8f,
c.getGreen() / 255f * 0.8f,
c.getBlue() / 255f * 0.8f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.minX, p_181561_0_.maxY, p_181561_0_.minZ)
.color(
c.getRed() / 255f * 0.8f,
c.getGreen() / 255f * 0.8f,
c.getBlue() / 255f * 0.8f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.minX, p_181561_0_.minY, p_181561_0_.minZ)
.color(
c.getRed() / 255f * 0.8f,
c.getGreen() / 255f * 0.8f,
c.getBlue() / 255f * 0.8f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.minY, p_181561_0_.minZ)
.color(
c.getRed() / 255f * 0.8f,
c.getGreen() / 255f * 0.8f,
c.getBlue() / 255f * 0.8f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.maxY, p_181561_0_.minZ)
.color(
c.getRed() / 255f * 0.8f,
c.getGreen() / 255f * 0.8f,
c.getBlue() / 255f * 0.8f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.maxY, p_181561_0_.maxZ)
.color(
c.getRed() / 255f * 0.8f,
c.getGreen() / 255f * 0.8f,
c.getBlue() / 255f * 0.8f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.minY, p_181561_0_.maxZ)
.color(
c.getRed() / 255f * 0.8f,
c.getGreen() / 255f * 0.8f,
c.getBlue() / 255f * 0.8f,
c.getAlpha() / 255f * alpha
).endVertex();
//z
worldrenderer.pos(p_181561_0_.minX, p_181561_0_.maxY, p_181561_0_.minZ)
.color(
c.getRed() / 255f * 0.9f,
c.getGreen() / 255f * 0.9f,
c.getBlue() / 255f * 0.9f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.maxY, p_181561_0_.minZ)
.color(
c.getRed() / 255f * 0.9f,
c.getGreen() / 255f * 0.9f,
c.getBlue() / 255f * 0.9f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.minY, p_181561_0_.minZ)
.color(
c.getRed() / 255f * 0.9f,
c.getGreen() / 255f * 0.9f,
c.getBlue() / 255f * 0.9f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.minX, p_181561_0_.minY, p_181561_0_.minZ)
.color(
c.getRed() / 255f * 0.9f,
c.getGreen() / 255f * 0.9f,
c.getBlue() / 255f * 0.9f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.minX, p_181561_0_.minY, p_181561_0_.maxZ)
.color(
c.getRed() / 255f * 0.9f,
c.getGreen() / 255f * 0.9f,
c.getBlue() / 255f * 0.9f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.minY, p_181561_0_.maxZ)
.color(
c.getRed() / 255f * 0.9f,
c.getGreen() / 255f * 0.9f,
c.getBlue() / 255f * 0.9f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.maxY, p_181561_0_.maxZ)
.color(
c.getRed() / 255f * 0.9f,
c.getGreen() / 255f * 0.9f,
c.getBlue() / 255f * 0.9f,
c.getAlpha() / 255f * alpha
).endVertex();
worldrenderer.pos(p_181561_0_.minX, p_181561_0_.maxY, p_181561_0_.maxZ)
.color(
c.getRed() / 255f * 0.9f,
c.getGreen() / 255f * 0.9f,
c.getBlue() / 255f * 0.9f,
c.getAlpha() / 255f * alpha
).endVertex();
}
}