/*
* 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.dungeons;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
import io.github.moulberry.notenoughupdates.util.Utils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.ScaledResolution;
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.ResourceLocation;
import net.minecraftforge.client.event.ClientChatReceivedEvent;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DungeonWin {
private static class Confetti {
private float x;
private float y;
private float xLast;
private float yLast;
private int life = 0;
private float xVel;
private float yVel;
private final int id;
public Confetti(float x, float y, float xVel, float yVel) {
this.x = x;
this.xLast = x;
this.y = y;
this.yLast = y;
this.xVel = xVel;
this.yVel = yVel;
this.id = rand.nextInt(16);
this.life = 20 + rand.nextInt(10);
}
}
public static ResourceLocation CONFETTI = new ResourceLocation("notenoughupdates:dungeon_win/confetti.png");
public static ResourceLocation SPLUS = new ResourceLocation("notenoughupdates:dungeon_win/splus.png");
public static ResourceLocation S = new ResourceLocation("notenoughupdates:dungeon_win/s.png");
public static ResourceLocation A = new ResourceLocation("notenoughupdates:dungeon_win/a.png");
public static ResourceLocation B = new ResourceLocation("notenoughupdates:dungeon_win/b.png");
public static ResourceLocation C = new ResourceLocation("notenoughupdates:dungeon_win/c.png");
public static ResourceLocation D = new ResourceLocation("notenoughupdates:dungeon_win/d.png");
public static ResourceLocation TEAM_SCORE = SPLUS;
private static final int SCALE_FACTOR = 3;
private static final int WIDTH = 32 * SCALE_FACTOR;
private static final int HEIGHT = 16 * SCALE_FACTOR;
private static boolean hideChat = false;
private static long lastDungeonFinish = 0;
private static final Pattern TEAM_SCORE_REGEX = Pattern.compile("Team Score: [0-9]+ \\((S\\+|S|A|B|C|D)\\)");
private static final ScheduledExecutorService SES = Executors.newScheduledThreadPool(1);
public static Random rand = new Random();
public static List confetti = new ArrayList<>();
public static List text = new ArrayList<>();
public static long startTime = 0;
private static boolean seenDungeonWinOverlayThisRun = false;
static {
for (int i = 0; i < 10; i++) {
text.add("{PLACEHOLDER DUNGEON STAT #" + i + "}");
}
}
public static void displayWin() {
if (NotEnoughUpdates.INSTANCE.config.dungeons.dungeonWinMillis < 100 ||
!NotEnoughUpdates.INSTANCE.config.dungeons.enableDungeonWin)
return;
startTime = System.currentTimeMillis();
confetti.clear();
}
public static void tick() {
if (NotEnoughUpdates.INSTANCE.config.dungeons.dungeonWinMillis < 100 ||
!NotEnoughUpdates.INSTANCE.config.dungeons.enableDungeonWin)
return;
if (System.currentTimeMillis() - startTime > 5000) return;
int deltaTime = (int) (System.currentTimeMillis() - startTime);
if (deltaTime < 1000) {
ScaledResolution sr = Utils.pushGuiScale(2);
int cap = 0;
switch (TEAM_SCORE.getResourcePath()) {
case "dungeon_win/splus.png":
cap = 200;
break;
case "dungeon_win/s.png":
cap = 100;
break;
case "dungeon_win/a.png":
cap = 50;
break;
}
int maxConfetti = Math.min(cap, deltaTime / 5);
while (confetti.size() < maxConfetti) {
int y;
if (deltaTime < 500) {
y = sr.getScaledHeight() / 2 - (int) (Math.sin(deltaTime / 1000f * Math.PI) * sr.getScaledHeight() / 9);
} else {
y = sr.getScaledHeight() / 6 + (int) (Math.sin(deltaTime / 1000f * Math.PI) * sr.getScaledHeight() * 4 / 18);
}
int xOffset = -WIDTH / 2 + rand.nextInt(WIDTH);
int x = sr.getScaledWidth() / 2 + xOffset;
int xVel = xOffset / 2;
int yVel = -25 - rand.nextInt(10) + Math.abs(xVel) / 2;
confetti.add(new Confetti(x, y, xVel, yVel));
}
} else {
Set toRemove = new HashSet<>();
for (Confetti c : confetti) {
if (c.life <= 0) {
toRemove.add(c);
}
}
try {
confetti.removeAll(toRemove);
} catch (ConcurrentModificationException ignored) {
}
}
Utils.pushGuiScale(-1);
for (Confetti c : confetti) {
c.yVel += 1;
c.xVel /= 1.1f;
c.yVel /= 1.1f;
c.xLast = c.x;
c.yLast = c.y;
c.x += c.xVel;
c.y += c.yVel;
c.life--;
}
}
public static void onChatMessage(ClientChatReceivedEvent e) {
if (e.type == 2) return;
if (NotEnoughUpdates.INSTANCE.config.dungeons.dungeonWinMillis < 100 ||
!NotEnoughUpdates.INSTANCE.config.dungeons.enableDungeonWin)
return;
long currentTime = System.currentTimeMillis();
String unformatted = Utils.cleanColour(e.message.getUnformattedText());
//Added two more Resets, can't do Reset+Reset+Reset cause idk?
//hypixel please don't randomly add more
//Clueless
if (unformatted.trim().startsWith("Team Score:")) {
if (currentTime - lastDungeonFinish > 30000) {
Matcher matcher = TEAM_SCORE_REGEX.matcher(unformatted);
if (matcher.find()) {
lastDungeonFinish = currentTime;
String score = matcher.group(1);
switch (score.toUpperCase(Locale.ROOT)) {
case "S+":
TEAM_SCORE = SPLUS;
break;
case "S":
TEAM_SCORE = S;
break;
case "A":
TEAM_SCORE = A;
break;
case "B":
TEAM_SCORE = B;
break;
case "C":
TEAM_SCORE = C;
break;
default:
TEAM_SCORE = D;
break;
}
SES.schedule(() -> NotEnoughUpdates.INSTANCE.sendChatMessage("/showextrastats"), 100L, TimeUnit.MILLISECONDS);
seenDungeonWinOverlayThisRun = false;
}
}
}
if (currentTime - lastDungeonFinish > 100 && currentTime - lastDungeonFinish < 10000) {
if (hideChat) {
if (text.size() > 50) text.clear();
if (unformatted.contains("\u25AC")) {
e.setCanceled(true);
hideChat = false;
displayWin();
seenDungeonWinOverlayThisRun = true;
} else {
if (unformatted.trim().length() > 0 && !seenDungeonWinOverlayThisRun) {
if (unformatted.contains("The Catacombs") || unformatted.contains("Master Mode The Catacombs") ||
unformatted.contains("Team Score") || unformatted.contains("Defeated") || unformatted.contains(
"Total Damage")
|| unformatted.contains("Ally Healing") || unformatted.contains("Enemies Killed") || unformatted.contains(
"Deaths") || unformatted.contains("Secrets Found")) {
e.setCanceled(true);
text.add(e.message.getFormattedText().substring(6).trim());
} else if (unformatted.trim().length() > 6) {
System.out.println(
"These messages would of showed on neu dungeon overlay but didnt, They are either bugged or i missed them: \"" +
e.message.getFormattedText().substring(6).trim() + "\"");
}
} else {
e.setCanceled(true);
}
}
} else {
if (unformatted.contains("\u25AC") && !seenDungeonWinOverlayThisRun) {
hideChat = true;
text.clear();
e.setCanceled(true);
}
}
}
}
public static void render(float partialTicks) {
if (NotEnoughUpdates.INSTANCE.config.dungeons.dungeonWinMillis < 100 ||
!NotEnoughUpdates.INSTANCE.config.dungeons.enableDungeonWin)
return;
int maxTime = Math.min(30000, NotEnoughUpdates.INSTANCE.config.dungeons.dungeonWinMillis);
if (System.currentTimeMillis() - startTime > maxTime) return;
int deltaTime = (int) (System.currentTimeMillis() - startTime);
float alpha = Math.max(0, Math.min(1, 1 - (deltaTime - maxTime + 150) / 150f));
ScaledResolution sr = Utils.pushGuiScale(2);
if (deltaTime > 600) {
float bottom;
if (deltaTime < 1000) {
bottom = sr.getScaledHeight() / 6f + (float) Math.sin(deltaTime / 1000f * Math.PI) * sr.getScaledHeight() * 4 /
18 + HEIGHT / 2;
} else {
bottom = sr.getScaledHeight() / 6f + HEIGHT / 2;
}
for (int i = 0; i < text.size(); i++) {
String line = text.get(i);
float textCenterY = sr.getScaledHeight() / 6f + HEIGHT / 2 + 7 + i * 10;
if (textCenterY > bottom) {
int textAlpha = (int) (alpha * (deltaTime > 1000 ? 255 : Math.min(255, (textCenterY - bottom) / 30f * 255)));
GlStateManager.enableBlend();
if (textAlpha > 150) {
for (int xOff = -2; xOff <= 2; xOff++) {
for (int yOff = -2; yOff <= 2; yOff++) {
if (Math.abs(xOff) != Math.abs(yOff)) {
Utils.drawStringCentered(
Utils.cleanColourNotModifiers(line),
sr.getScaledWidth() / 2 + xOff / 2f,
textCenterY + yOff / 2f,
false,
((textAlpha / Math.max(Math.abs(xOff), Math.abs(yOff))) << 24)
);
}
}
}
}
Utils.drawStringCentered(line, sr.getScaledWidth() / 2, textCenterY, false, (textAlpha << 24) | 0x00FFFFFF);
}
}
}
for (Confetti c : confetti) {
Minecraft.getMinecraft().getTextureManager().bindTexture(CONFETTI);
GlStateManager.color(1, 1, 1, 1);
if (c.life >= 15) {
GlStateManager.color(1, 1, 1, Math.min(1, c.life / 4f));
Utils.drawTexturedRect(
c.xLast + (c.x - c.xLast) * partialTicks - 4,
c.yLast + (c.y - c.yLast) * partialTicks - 4,
8,
8,
(c.id % 4) / 4f,
(c.id % 4 + 1) / 4f,
(c.id / 4) / 4f,
(c.id / 4 + 1) / 4f,
GL11.GL_NEAREST
);
}
}
Minecraft.getMinecraft().getTextureManager().bindTexture(TEAM_SCORE);
GlStateManager.color(1, 1, 1, alpha);
GlStateManager.pushMatrix();
if (deltaTime < 1600) {
GlStateManager.translate(sr.getScaledWidth() / 2, 0, 0);
if (deltaTime < 500) {
GlStateManager.translate(
0,
sr.getScaledHeight() / 2f - Math.sin(deltaTime / 1000f * Math.PI) * sr.getScaledHeight() / 9,
0
);
} else if (deltaTime < 1000) {
GlStateManager.translate(
0,
sr.getScaledHeight() / 6f + Math.sin(deltaTime / 1000f * Math.PI) * sr.getScaledHeight() * 4 / 18,
0
);
} else {
GlStateManager.translate(0, sr.getScaledHeight() / 6f, 0);
}
if (deltaTime < 200) {
float scale = deltaTime / 200f;
GlStateManager.scale(scale, scale, 1);
} else if (deltaTime < 1000) {
float scale = 1 + (float) Math.sin((deltaTime - 200) / 800f * Math.PI) * 0.8f;
GlStateManager.scale(scale, scale, 1);
} else if (deltaTime < 1100) {
float scale = 1 + (float) Math.sin((deltaTime - 1000) / 100f * Math.PI) * 0.15f;
GlStateManager.scale(scale, scale, 1);
}
if (deltaTime < 600) {
GlStateManager.rotate(180 + deltaTime / 600f * 180, 0, 1, 0);
GlStateManager.rotate(180 - deltaTime / 600f * 180, 1, 0, 0);
GlStateManager.rotate(-180 - deltaTime / 600f * 165, 0, 0, 1);
} else if (deltaTime < 1000) {
GlStateManager.rotate(15 - (deltaTime - 600) / 400f * 11, 0, 0, 1);
} else {
float logFac = 1 - (float) Math.log((deltaTime - 1000) / 600f * 1.7f + 1);
logFac = logFac * logFac;
GlStateManager.rotate(4f * logFac, 0, 0, 1);
float x = (deltaTime - 1000) / 300f;
GlStateManager.rotate((float) (40 * (1 - Math.log(x * 0.85f + 1)) * Math.sin(10 * x * x)), 0, 1, 0);
}
} else {
GlStateManager.translate(sr.getScaledWidth() / 2, sr.getScaledHeight() / 6f, 0);
}
GlStateManager.disableCull();
Utils.drawTexturedRect(-WIDTH / 2, -HEIGHT / 2, WIDTH, HEIGHT, GL11.GL_NEAREST);
GlStateManager.translate(0, 0, -SCALE_FACTOR * 2);
Utils.drawTexturedRect(-WIDTH / 2, -HEIGHT / 2, WIDTH, HEIGHT, GL11.GL_NEAREST);
GlStateManager.translate(0, 0, SCALE_FACTOR * 2);
if (deltaTime < 1600) {
float epsilon = 0.01f;
for (int xIndex = 0; xIndex < 32; xIndex++) {
for (int yIndex = 0; yIndex < 16; yIndex++) {
float uMin = xIndex / 32f;
float uMax = (xIndex + 1) / 32f;
float vMin = yIndex / 16f;
float vMax = (yIndex + 1) / 16f;
int x = -WIDTH / 2 + xIndex * SCALE_FACTOR;
int y = -HEIGHT / 2 + yIndex * SCALE_FACTOR;
GlStateManager.enableTexture2D();
GlStateManager.enableBlend();
GL14.glBlendFuncSeparate(
GL11.GL_SRC_ALPHA,
GL11.GL_ONE_MINUS_SRC_ALPHA,
GL11.GL_ONE,
GL11.GL_ONE_MINUS_SRC_ALPHA
);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
Tessellator tessellator = Tessellator.getInstance();
WorldRenderer worldrenderer = tessellator.getWorldRenderer();
//Left
worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX);
worldrenderer
.pos(x + epsilon, y + SCALE_FACTOR, 0.0D + epsilon)
.tex(uMin, vMax).endVertex();
worldrenderer
.pos(x + epsilon, y, 0.0D + epsilon)
.tex(uMax, vMax).endVertex();
worldrenderer
.pos(x + epsilon, y, -SCALE_FACTOR * 2 - epsilon)
.tex(uMax, vMin).endVertex();
worldrenderer
.pos(x + epsilon, y + SCALE_FACTOR, -SCALE_FACTOR * 2 - epsilon)
.tex(uMin, vMin).endVertex();
tessellator.draw();
//Right
worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX);
worldrenderer
.pos(x + SCALE_FACTOR - epsilon, y + SCALE_FACTOR, 0.0D + epsilon)
.tex(uMin, vMax).endVertex();
worldrenderer
.pos(x + SCALE_FACTOR - epsilon, y, 0.0D + epsilon)
.tex(uMax, vMax).endVertex();
worldrenderer
.pos(x + SCALE_FACTOR - epsilon, y, -SCALE_FACTOR * 2 - epsilon)
.tex(uMax, vMin).endVertex();
worldrenderer
.pos(x + SCALE_FACTOR - epsilon, y + SCALE_FACTOR, -SCALE_FACTOR * 2 - epsilon)
.tex(uMin, vMin).endVertex();
tessellator.draw();
//Top
worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX);
worldrenderer
.pos(x + SCALE_FACTOR, y + epsilon, 0.0D + epsilon)
.tex(uMin, vMax).endVertex();
worldrenderer
.pos(x, y + epsilon, 0.0D + epsilon)
.tex(uMax, vMax).endVertex();
worldrenderer
.pos(x, y + epsilon, -SCALE_FACTOR * 2 - epsilon)
.tex(uMax, vMin).endVertex();
worldrenderer
.pos(x + SCALE_FACTOR, y + epsilon, -SCALE_FACTOR * 2 - epsilon)
.tex(uMin, vMin).endVertex();
tessellator.draw();
//Top
worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX);
worldrenderer
.pos(x + SCALE_FACTOR, y + SCALE_FACTOR - epsilon, 0.0D + epsilon)
.tex(uMin, vMax).endVertex();
worldrenderer
.pos(x, y + SCALE_FACTOR - epsilon, 0.0D + epsilon)
.tex(uMax, vMax).endVertex();
worldrenderer
.pos(x, y + SCALE_FACTOR - epsilon, -SCALE_FACTOR * 2 - epsilon)
.tex(uMax, vMin).endVertex();
worldrenderer
.pos(x + SCALE_FACTOR, y + SCALE_FACTOR - epsilon, -SCALE_FACTOR * 2 - epsilon)
.tex(uMin, vMin).endVertex();
tessellator.draw();
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GlStateManager.disableBlend();
}
}
}
GlStateManager.popMatrix();
for (Confetti c : confetti) {
Minecraft.getMinecraft().getTextureManager().bindTexture(CONFETTI);
GlStateManager.color(1, 1, 1, 1);
if (c.life > 0 && c.life < 15) {
GlStateManager.color(1, 1, 1, Math.min(1, c.life / 4f));
Utils.drawTexturedRect(
c.xLast + (c.x - c.xLast) * partialTicks - 4,
c.yLast + (c.y - c.yLast) * partialTicks - 4,
8,
8,
(c.id % 4) / 4f,
(c.id % 4 + 1) / 4f,
(c.id / 4) / 4f,
(c.id / 4 + 1) / 4f,
GL11.GL_NEAREST
);
}
}
Utils.pushGuiScale(-1);
GlStateManager.enableBlend();
}
}