diff --git a/src/main/java/com/vinlanx/explosionoverhaul/client/FirstTimeScreen.java b/src/main/java/com/vinlanx/explosionoverhaul/client/FirstTimeScreen.java index da15a63..acc6b1f 100644 --- a/src/main/java/com/vinlanx/explosionoverhaul/client/FirstTimeScreen.java +++ b/src/main/java/com/vinlanx/explosionoverhaul/client/FirstTimeScreen.java @@ -2,101 +2,149 @@ package com.vinlanx.explosionoverhaul.client; import com.vinlanx.explosionoverhaul.Config; import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; public class FirstTimeScreen extends Screen { - private static final ResourceLocation REALISTIC = ResourceLocation.fromNamespaceAndPath("explosionoverhaul", "intro_gui/gui_screen_1.png"); - private static final ResourceLocation VANILLA = ResourceLocation.fromNamespaceAndPath("explosionoverhaul", "intro_gui/gui_screen_2.png"); - private static final ResourceLocation REALISTIC_2 = ResourceLocation.fromNamespaceAndPath("explosionoverhaul", "intro_gui/gui_screen_3.png"); - private static final ResourceLocation QUALITY_64 = ResourceLocation.fromNamespaceAndPath("explosionoverhaul", "intro_gui/gui_screen_4.png"); + private static final int FRAME_WIDTH = 854; + private static final int FRAME_HEIGHT = 480; + private static final int COLUMNS = 10; + private static final int ROWS = 10; + private static final int FRAME_COUNT = COLUMNS * ROWS; + private static final int BORDER = 0xFFFF7A70; + private static final int BORDER_HOVER = 0xFFFF9B91; + private static final Choice[] CHOICES = new Choice[] { + new Choice("gui_screen_1.png", 42, "REALISTIC", Config.Client.ParticleRenderMode.REALISTIC), + new Choice("gui_screen_2.png", 42, "VANILLA-LIKE", Config.Client.ParticleRenderMode.VANILA), + new Choice("gui_screen_3.png", 42, "REALISTIC 2", Config.Client.ParticleRenderMode.REALISTIC_2) + }; - private Config.Client.ParticleRenderMode selectedMode = Config.Client.ParticleRenderMode.REALISTIC; - private Config.Client.GlowTextureQuality selectedQuality = Config.Client.GlowTextureQuality.QUALITY_256; - private boolean choosingQuality; + private final Rect[] cardRects = new Rect[] { Rect.empty(), Rect.empty(), Rect.empty() }; public FirstTimeScreen() { super(Component.literal("Explosion Overhaul Setup")); } @Override - protected void init() { - this.rebuildButtons(); - } + public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { + graphics.fill(0, 0, this.width, this.height, 0xFF050506); + this.drawAmbient(graphics); - private void rebuildButtons() { - this.clearWidgets(); - int cardW = Math.max(160, this.width / 5); - int cardH = 28; - int y = this.height - 86; - if (!this.choosingQuality) { - int gap = 12; - int startX = (this.width - cardW * 3 - gap * 2) / 2; - this.addRenderableWidget(Button.builder(Component.literal("Realistic"), button -> { - this.selectedMode = Config.Client.ParticleRenderMode.REALISTIC; - this.choosingQuality = true; - this.rebuildButtons(); - }).bounds(startX, y, cardW, cardH).build()); - this.addRenderableWidget(Button.builder(Component.literal("Vanilla Performance"), button -> { - this.selectedMode = Config.Client.ParticleRenderMode.VANILA; - this.choosingQuality = true; - this.rebuildButtons(); - }).bounds(startX + cardW + gap, y, cardW, cardH).build()); - this.addRenderableWidget(Button.builder(Component.literal("Realistic 2"), button -> { - this.selectedMode = Config.Client.ParticleRenderMode.REALISTIC_2; - this.choosingQuality = true; - this.rebuildButtons(); - }).bounds(startX + (cardW + gap) * 2, y, cardW, cardH).build()); - } else { - int gap = 16; - int startX = (this.width - cardW * 2 - gap) / 2; - this.addRenderableWidget(Button.builder(Component.literal("256p Quality"), button -> this.finish(Config.Client.GlowTextureQuality.QUALITY_256)).bounds(startX, y, cardW, cardH).build()); - this.addRenderableWidget(Button.builder(Component.literal("64p Performance"), button -> this.finish(Config.Client.GlowTextureQuality.QUALITY_64)).bounds(startX + cardW + gap, y, cardW, cardH).build()); - this.addRenderableWidget(Button.builder(Component.literal("Back"), button -> { - this.choosingQuality = false; - this.rebuildButtons(); - }).bounds(16, this.height - 40, 80, 24).build()); + int top = Math.max(34, this.height / 6); + graphics.drawCenteredString(this.font, Component.literal("Choose Your Explosion Style."), this.width / 2, top, 0xFFFF9900); + graphics.drawCenteredString(this.font, Component.literal("You can change this anytime in the config"), this.width / 2, top + 25, 0xFFE5E5E5); + graphics.drawCenteredString(this.font, Component.literal("The game may differ from the animations because compression has altered them."), this.width / 2, top + 48, 0xFF9C9C9C); + + int cardW = Math.min(420, Math.max(220, (this.width - 170) / 2)); + int cardH = cardW * FRAME_HEIGHT / FRAME_WIDTH; + int gap = Math.max(34, this.width / 28); + int firstX = this.width / 2 - cardW - gap / 2; + int secondX = this.width / 2 + gap / 2; + int firstRowY = top + 88; + int secondRowY = firstRowY + cardH + 84; + + this.cardRects[0] = new Rect(firstX, firstRowY, cardW, cardH + 58); + this.cardRects[1] = new Rect(secondX, firstRowY, cardW, cardH + 58); + this.cardRects[2] = new Rect((this.width - cardW) / 2, secondRowY, cardW, cardH + 58); + + for (int i = 0; i < CHOICES.length; ++i) { + this.drawChoice(graphics, CHOICES[i], this.cardRects[i], this.cardRects[i].contains(mouseX, mouseY)); } } - private void finish(Config.Client.GlowTextureQuality quality) { - this.selectedQuality = quality; - Config.CLIENT.particleRenderMode.set(this.selectedMode); - Config.CLIENT.glowTextureQuality.set(this.selectedQuality); + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (button == 0) { + for (int i = 0; i < this.cardRects.length; ++i) { + if (this.cardRects[i].contains(mouseX, mouseY)) { + this.finish(CHOICES[i]); + return true; + } + } + } + return super.mouseClicked(mouseX, mouseY, button); + } + + private void finish(Choice choice) { + Config.CLIENT.particleRenderMode.set(choice.mode); + Config.CLIENT.glowTextureQuality.set(Config.Client.GlowTextureQuality.QUALITY_256); Config.CLIENT.firstLaunchComplete.set(true); Config.CLIENT.introSetupComplete.set(true); Config.CLIENT_SPEC.save(); this.onClose(); } - @Override - public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { - this.renderBackground(graphics, mouseX, mouseY, partialTick); - graphics.fill(0, 0, this.width, this.height, 0xE0101117); - graphics.drawCenteredString(this.font, Component.literal("Explosion Overhaul"), this.width / 2, 28, 0xFFFFFF); - graphics.drawCenteredString(this.font, this.choosingQuality ? Component.literal("Choose texture quality") : Component.literal("Choose explosion render mode"), this.width / 2, 48, 0xFFB35C); - - if (this.choosingQuality) { - this.drawPreview(graphics, REALISTIC, this.width / 2 - 270, 82, 250, 141, "256p Quality"); - this.drawPreview(graphics, QUALITY_64, this.width / 2 + 20, 82, 250, 141, "64p Performance"); - } else { - this.drawPreview(graphics, REALISTIC, this.width / 2 - 390, 82, 240, 135, "Realistic"); - this.drawPreview(graphics, VANILLA, this.width / 2 - 120, 82, 240, 135, "Vanilla Performance"); - this.drawPreview(graphics, REALISTIC_2, this.width / 2 + 150, 82, 240, 135, "Realistic 2"); - } - super.render(graphics, mouseX, mouseY, partialTick); + private void drawChoice(GuiGraphics graphics, Choice choice, Rect rect, boolean hovered) { + int imageX = rect.x; + int imageY = rect.y; + int imageW = rect.w; + int imageH = rect.w * FRAME_HEIGHT / FRAME_WIDTH; + graphics.fill(imageX - 6, imageY - 6, imageX + imageW + 6, imageY + imageH + 6, hovered ? BORDER_HOVER : BORDER); + this.drawSheetFrame(graphics, choice.texture, choice.frame, imageX, imageY, imageW, imageH); + graphics.drawCenteredString(this.font, Component.literal(choice.label), imageX + imageW / 2, imageY + imageH + 28, 0xFFFFFFFF); } - private void drawPreview(GuiGraphics graphics, ResourceLocation texture, int x, int y, int w, int h, String label) { - graphics.fill(x - 2, y - 2, x + w + 2, y + h + 20, 0xAA000000); - graphics.blit(texture, x, y, 0.0f, 0.0f, w, h, 854, 480); - graphics.drawCenteredString(this.font, Component.literal(label), x + w / 2, y + h + 7, 0xFFFFFF); + private void drawSheetFrame(GuiGraphics graphics, ResourceLocation texture, int frame, int x, int y, int width, int height) { + int clamped = Math.max(0, Math.min(frame, FRAME_COUNT - 1)); + int u = clamped % COLUMNS * FRAME_WIDTH; + int v = clamped / COLUMNS * FRAME_HEIGHT; + float scaleX = (float)width / (float)FRAME_WIDTH; + float scaleY = (float)height / (float)FRAME_HEIGHT; + graphics.pose().pushPose(); + graphics.pose().translate(x, y, 0.0F); + graphics.pose().scale(scaleX, scaleY, 1.0F); + graphics.blit(texture, 0, 0, (float)u, (float)v, FRAME_WIDTH, FRAME_HEIGHT, FRAME_WIDTH * COLUMNS, FRAME_HEIGHT * ROWS); + graphics.pose().popPose(); + } + + private void drawAmbient(GuiGraphics graphics) { + for (int i = 0; i < 96; ++i) { + int x = Math.floorMod(i * 173 + 19, Math.max(1, this.width)); + int y = Math.floorMod(i * 97 + 53, Math.max(1, this.height)); + int color = i % 7 == 0 ? 0xFFFFB347 : 0xFF414141; + graphics.fill(x, y, x + 2, y + 2, color); + } } @Override public boolean shouldCloseOnEsc() { return false; } + + private static final class Choice { + private final ResourceLocation texture; + private final int frame; + private final String label; + private final Config.Client.ParticleRenderMode mode; + + private Choice(String fileName, int frame, String label, Config.Client.ParticleRenderMode mode) { + this.texture = ResourceLocation.fromNamespaceAndPath("explosionoverhaul", "intro_gui/" + fileName); + this.frame = frame; + this.label = label; + this.mode = mode; + } + } + + private static final class Rect { + private final int x; + private final int y; + private final int w; + private final int h; + + private Rect(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + private static Rect empty() { + return new Rect(0, 0, 0, 0); + } + + private boolean contains(double mouseX, double mouseY) { + return mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.w && mouseY < this.y + this.h; + } + } } diff --git a/src/main/java/com/vinlanx/explosionoverhaul/client/FirstTimeSetupHandler.java b/src/main/java/com/vinlanx/explosionoverhaul/client/FirstTimeSetupHandler.java index d8538e5..b951167 100644 --- a/src/main/java/com/vinlanx/explosionoverhaul/client/FirstTimeSetupHandler.java +++ b/src/main/java/com/vinlanx/explosionoverhaul/client/FirstTimeSetupHandler.java @@ -5,7 +5,6 @@ package com.vinlanx.explosionoverhaul.client; import com.vinlanx.explosionoverhaul.Config; import com.vinlanx.explosionoverhaul.ExplosionOverhaul; -import com.vinlanx.explosionoverhaul.client.IntroSplashScreen; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.TitleScreen; @@ -31,7 +30,7 @@ public class FirstTimeSetupHandler { } if (pendingShowIntro && mc.screen instanceof TitleScreen) { pendingShowIntro = false; - mc.setScreen(new FirstTimeScreen()); + mc.setScreen(new GuideSlidesScreen()); } } diff --git a/src/main/java/com/vinlanx/explosionoverhaul/client/GuideSlidesScreen.java b/src/main/java/com/vinlanx/explosionoverhaul/client/GuideSlidesScreen.java index afecf59..defeb46 100644 --- a/src/main/java/com/vinlanx/explosionoverhaul/client/GuideSlidesScreen.java +++ b/src/main/java/com/vinlanx/explosionoverhaul/client/GuideSlidesScreen.java @@ -1,16 +1,179 @@ package com.vinlanx.explosionoverhaul.client; +import java.util.List; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FormattedCharSequence; public class GuideSlidesScreen extends Screen { + private static final int FRAME_WIDTH = 854; + private static final int FRAME_HEIGHT = 480; + private static final int COLUMNS = 14; + private static final int ROWS = 14; + private static final int FRAME_COUNT = COLUMNS * ROWS; + private static final int ACCENT = 0xFFFF9F3A; + private static final int RED = 0xFF561818; + private static final int RED_HOVER = 0xFF752020; + private static final Slide[] SLIDES = new Slide[] { + new Slide("claster_slide.png", 82, "Cluster TNT - If you place more than two TNT blocks next to each other, the explosion will be much stronger!"), + new Slide("cave_slide.png", 64, "Caves - If an explosion happens on the surface while you're underground, you'll hear a terrifying rumble, and the ground and dripstones will start to crumble!"), + new Slide("glass_slide.png", 56, "Glass Shattering - Explosions create shockwaves! Be careful when working with explosives near settlements."), + new Slide("lamp_slide.png", 74, "Lamp Flickering - If an explosion happens near lamps, the lights will start to flicker."), + new Slide("speed_of_sound_slide.png", 78, "Speed of Sound - Explosions travel at the speed of sound. You can also enable \"Epic Mode\" in the settings!") + }; + + private int slideIndex; + private Rect leftArrow = Rect.empty(); + private Rect rightArrow = Rect.empty(); + private Rect skipButton = Rect.empty(); + public GuideSlidesScreen() { super(Component.empty()); } @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { - super.render(graphics, mouseX, mouseY, partialTick); + graphics.fill(0, 0, this.width, this.height, 0xFF070708); + this.drawAmbient(graphics); + + int imageW = Math.min(this.width - 220, 1100); + int imageH = imageW * FRAME_HEIGHT / FRAME_WIDTH; + int maxImageH = Math.max(180, this.height / 3); + if (imageH > maxImageH) { + imageH = maxImageH; + imageW = imageH * FRAME_WIDTH / FRAME_HEIGHT; + } + int imageX = (this.width - imageW) / 2; + int imageY = Math.max(42, this.height / 8); + + graphics.fill(imageX - 8, imageY - 8, imageX + imageW + 8, imageY + imageH + 8, 0xFF1E1205); + graphics.fill(imageX - 4, imageY - 4, imageX + imageW + 4, imageY + imageH + 4, 0xFF3D270E); + this.drawSheetFrame(graphics, SLIDES[this.slideIndex].texture, SLIDES[this.slideIndex].frame, imageX, imageY, imageW, imageH); + + int textWidth = Math.min(this.width - 240, 1040); + int textY = imageY + imageH + 44; + this.drawCenteredWrappedText(graphics, SLIDES[this.slideIndex].text, this.width / 2, textY, textWidth, 0xFFFFFFFF); + + int arrowY = imageY + imageH / 2 - 46; + this.leftArrow = new Rect(Math.max(16, imageX / 2 - 46), arrowY, 92, 92); + this.rightArrow = new Rect(Math.min(this.width - 108, imageX + imageW + (this.width - imageX - imageW) / 2 - 46), arrowY, 92, 92); + if (this.slideIndex > 0) { + this.drawArrow(graphics, this.leftArrow, "<", this.leftArrow.contains(mouseX, mouseY)); + } + if (this.slideIndex < SLIDES.length - 1) { + this.drawArrow(graphics, this.rightArrow, ">", this.rightArrow.contains(mouseX, mouseY)); + } + + int skipW = 156; + int skipH = 44; + this.skipButton = new Rect((this.width - skipW) / 2, this.height - 84, skipW, skipH); + this.drawFlatButton(graphics, this.skipButton, "Skip Guide", this.skipButton.contains(mouseX, mouseY)); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (button == 0) { + if (this.skipButton.contains(mouseX, mouseY)) { + this.openSetup(); + return true; + } + if (this.slideIndex > 0 && this.leftArrow.contains(mouseX, mouseY)) { + --this.slideIndex; + return true; + } + if (this.slideIndex < SLIDES.length - 1 && this.rightArrow.contains(mouseX, mouseY)) { + ++this.slideIndex; + return true; + } + } + return super.mouseClicked(mouseX, mouseY, button); + } + + private void openSetup() { + if (this.minecraft != null) { + this.minecraft.setScreen(new FirstTimeScreen()); + } + } + + private void drawSheetFrame(GuiGraphics graphics, ResourceLocation texture, int frame, int x, int y, int width, int height) { + int clamped = Math.max(0, Math.min(frame, FRAME_COUNT - 1)); + int u = clamped % COLUMNS * FRAME_WIDTH; + int v = clamped / COLUMNS * FRAME_HEIGHT; + float scaleX = (float)width / (float)FRAME_WIDTH; + float scaleY = (float)height / (float)FRAME_HEIGHT; + graphics.pose().pushPose(); + graphics.pose().translate(x, y, 0.0F); + graphics.pose().scale(scaleX, scaleY, 1.0F); + graphics.blit(texture, 0, 0, (float)u, (float)v, FRAME_WIDTH, FRAME_HEIGHT, FRAME_WIDTH * COLUMNS, FRAME_HEIGHT * ROWS); + graphics.pose().popPose(); + } + + private void drawCenteredWrappedText(GuiGraphics graphics, String text, int centerX, int y, int maxWidth, int color) { + List lines = this.font.split(Component.literal(text), maxWidth); + int lineY = y; + for (FormattedCharSequence line : lines) { + graphics.drawCenteredString(this.font, line, centerX, lineY, color); + lineY += this.font.lineHeight + 5; + } + } + + private void drawArrow(GuiGraphics graphics, Rect rect, String text, boolean hovered) { + graphics.fill(rect.x, rect.y, rect.x + rect.w, rect.y + rect.h, hovered ? RED_HOVER : RED); + graphics.drawCenteredString(this.font, Component.literal(text), rect.x + rect.w / 2, rect.y + 36, 0xFFFFFFFF); + } + + private void drawFlatButton(GuiGraphics graphics, Rect rect, String text, boolean hovered) { + graphics.fill(rect.x, rect.y, rect.x + rect.w, rect.y + rect.h, hovered ? RED_HOVER : RED); + graphics.drawCenteredString(this.font, Component.literal(text), rect.x + rect.w / 2, rect.y + 15, 0xFFFFFFFF); + } + + private void drawAmbient(GuiGraphics graphics) { + for (int i = 0; i < 72; ++i) { + int x = Math.floorMod(i * 131 + 17, Math.max(1, this.width)); + int y = Math.floorMod(i * 71 + 41, Math.max(1, this.height)); + int color = i % 5 == 0 ? ACCENT : 0xFF444444; + graphics.fill(x, y, x + 2, y + 2, color); + } + } + + @Override + public boolean shouldCloseOnEsc() { + return false; + } + + private static final class Slide { + private final ResourceLocation texture; + private final int frame; + private final String text; + + private Slide(String fileName, int frame, String text) { + this.texture = ResourceLocation.fromNamespaceAndPath("explosionoverhaul", "intro_gui/" + fileName); + this.frame = frame; + this.text = text; + } + } + + private static final class Rect { + private final int x; + private final int y; + private final int w; + private final int h; + + private Rect(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + private static Rect empty() { + return new Rect(0, 0, 0, 0); + } + + private boolean contains(double mouseX, double mouseY) { + return mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.w && mouseY < this.y + this.h; + } } }