diff --git a/gradle.properties b/gradle.properties index a74a1b7..3602886 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.20.1 yarn_mappings=1.20.1+build.10 loader_version=0.18.3 # Mod Properties -mod_version=26.3.11.2 +mod_version=26.3.12 maven_group=dev.tggamesyt archives_base_name=szar # Dependencies diff --git a/src/client/java/dev/tggamesyt/szar/client/ConfigScreen.java b/src/client/java/dev/tggamesyt/szar/client/ConfigScreen.java index 9bcc2dd..9f8040e 100644 --- a/src/client/java/dev/tggamesyt/szar/client/ConfigScreen.java +++ b/src/client/java/dev/tggamesyt/szar/client/ConfigScreen.java @@ -18,7 +18,7 @@ public class ConfigScreen extends Screen { private final List toggleButtons = new ArrayList<>(); public ConfigScreen(Screen parent) { - super(Text.literal("Your Mod Config")); + super(Text.literal("Szar Mod Config")); this.parent = parent; } @@ -75,17 +75,19 @@ public class ConfigScreen extends Screen { addDrawableChild(ButtonWidget.builder(Text.literal("Done"), b -> { ModConfig.save(); ResourcePackHelper.applyAll(client); - PacketByteBuf buf = PacketByteBufs.create(); + if (client.world != null) { + PacketByteBuf buf = PacketByteBufs.create(); - // Write each setting as: id (string), value (boolean) - var settings = ModConfig.allSettings(); - buf.writeInt(settings.size()); - for (ConfigEntry entry : settings) { - buf.writeString(entry.id); - buf.writeBoolean(entry.get()); + // Write each setting as: id (string), value (boolean) + var settings = ModConfig.allSettings(); + buf.writeInt(settings.size()); + for (ConfigEntry entry : settings) { + buf.writeString(entry.id); + buf.writeBoolean(entry.get()); + } + + ClientPlayNetworking.send(Szar.CONFIG_SYNC, buf); } - - ClientPlayNetworking.send(Szar.CONFIG_SYNC, buf); client.setScreen(parent); }).dimensions(cx - 75, height - 30, 150, 20).build()); } diff --git a/src/client/java/dev/tggamesyt/szar/client/ModConfig.java b/src/client/java/dev/tggamesyt/szar/client/ModConfig.java index 212c878..5017837 100644 --- a/src/client/java/dev/tggamesyt/szar/client/ModConfig.java +++ b/src/client/java/dev/tggamesyt/szar/client/ModConfig.java @@ -10,7 +10,7 @@ import java.util.*; public class ModConfig { private static final Path CONFIG_PATH = - FabricLoader.getInstance().getConfigDir().resolve("yourmod.json"); + FabricLoader.getInstance().getConfigDir().resolve("szar/config.json"); private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); // ── Registry ────────────────────────────────────────────────────────────── diff --git a/src/client/java/dev/tggamesyt/szar/client/ModSettings.java b/src/client/java/dev/tggamesyt/szar/client/ModSettings.java index 7e7a59d..5f663d2 100644 --- a/src/client/java/dev/tggamesyt/szar/client/ModSettings.java +++ b/src/client/java/dev/tggamesyt/szar/client/ModSettings.java @@ -20,17 +20,17 @@ public class ModSettings { // newPreset(id, displayName, Map) // Pass null map for the "custom" preset (user-editable, no fixed values) - ModConfig.newPreset("none", "18+", Map.of( + ModConfig.newPreset("18+", "18+", Map.of( "racist", false, "gambling", false, "nsfw", false )); - ModConfig.newPreset("some", "17+", Map.of( + ModConfig.newPreset("17+", "17+", Map.of( "racist", false, "gambling", false, "nsfw", true )); - ModConfig.newPreset("all", "Minor", Map.of( + ModConfig.newPreset("minor", "Minor", Map.of( "racist", true, "gambling", true, "nsfw", true diff --git a/src/client/java/dev/tggamesyt/szar/client/ResourcePackHelper.java b/src/client/java/dev/tggamesyt/szar/client/ResourcePackHelper.java index 3dfa71c..9e9d6bc 100644 --- a/src/client/java/dev/tggamesyt/szar/client/ResourcePackHelper.java +++ b/src/client/java/dev/tggamesyt/szar/client/ResourcePackHelper.java @@ -12,42 +12,49 @@ public class ResourcePackHelper { ResourcePackManager manager = client.getResourcePackManager(); manager.scanPacks(); - Set original = new HashSet<>( - manager.getEnabledProfiles().stream() - .map(p -> p.getName()) - .toList() - ); + List orderedEnabled = manager.getEnabledProfiles().stream() + .map(ResourcePackProfile::getName) + .toList(); - Set enabledNames = new HashSet<>( - manager.getEnabledProfiles().stream() - .map(p -> p.getName()) - .toList() - ); + Set original = new LinkedHashSet<>(orderedEnabled); + + Set toAdd = new LinkedHashSet<>(); + Set toRemove = new HashSet<>(); for (ConfigEntry entry : ModConfig.allSettings()) { if (!entry.hasResourcePack()) continue; if (entry.get()) { - enabledNames.add(entry.linkedResourcePack); + toAdd.add(entry.linkedResourcePack); } else { - enabledNames.remove(entry.linkedResourcePack); + toRemove.add(entry.linkedResourcePack); } } - if (enabledNames.equals(original)) { + // Build final list: keep originals in order, remove disabled, append new at end + List finalList = new ArrayList<>(); + for (String name : orderedEnabled) { + if (!toRemove.contains(name)) { + finalList.add(name); + } + } + for (String name : toAdd) { + if (!finalList.contains(name)) { + finalList.add(name); + } + } + + if (new LinkedHashSet<>(finalList).equals(original)) { return; } + System.out.println("original: " + original); + System.out.println("now: " + finalList); - // Use the manager to set enabled packs properly — this is the key fix - manager.setEnabledProfiles(enabledNames); + manager.setEnabledProfiles(finalList); - // Sync back to options and save + // Don't trust getEnabledProfiles() order after setEnabledProfiles — use our list client.options.resourcePacks.clear(); - client.options.resourcePacks.addAll( - manager.getEnabledProfiles().stream() - .map(p -> p.getName()) - .toList() - ); + client.options.resourcePacks.addAll(finalList); client.options.write(); client.reloadResources(); } diff --git a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java index 1dd5d23..0707e27 100644 --- a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java +++ b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java @@ -54,6 +54,7 @@ import net.minecraft.util.math.Box; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.random.Random; +import org.spongepowered.asm.mixin.Unique; import java.io.File; import java.io.IOException; @@ -68,6 +69,8 @@ import static dev.tggamesyt.szar.client.UraniumUtils.updateUranium; public class SzarClient implements ClientModInitializer { // add this field to your client init class + public static final int april = 4; + public static final int fools = 1; private float drogOverlayProgress = 0.0F; private long lastTime = 0; private static final Map activeScramble = new HashMap<>(); diff --git a/src/client/java/dev/tggamesyt/szar/client/SzarTosHandler.java b/src/client/java/dev/tggamesyt/szar/client/SzarTosHandler.java index c572a8e..211bbc0 100644 --- a/src/client/java/dev/tggamesyt/szar/client/SzarTosHandler.java +++ b/src/client/java/dev/tggamesyt/szar/client/SzarTosHandler.java @@ -2,33 +2,31 @@ package dev.tggamesyt.szar.client; import com.google.gson.Gson; import com.google.gson.JsonObject; +import dev.tggamesyt.szar.Szar; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.ConfirmScreen; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.CheckboxWidget; -import net.minecraft.client.gui.widget.MultilineTextWidget; +import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.client.gui.DrawContext; +import net.minecraft.network.PacketByteBuf; import net.minecraft.text.Text; -import net.minecraft.util.Util; import java.io.File; import java.io.FileReader; import java.io.FileWriter; - -import static dev.tggamesyt.szar.client.UraniumUtils.updateUranium; +import java.util.ArrayList; +import java.util.List; public class SzarTosHandler { private static final Gson GSON = new Gson(); private static final File CONFIG_FILE = - new File(FabricLoader.getInstance().getConfigDir().toFile(), "szar_tos.json"); + new File(FabricLoader.getInstance().getConfigDir().toFile(), "szar/tos.json"); - // ============================ - // FULL TOS TEXT - // ============================ private static final Text MOD_TOS_TEXT = Text.literal(""" ABOUT THIS MOD: This mod was created as a school programming project and as a joke mod @@ -36,45 +34,15 @@ between classmates and friends. Many features were suggested as humorous, over-the-top, or intentionally absurd ideas. The content is NOT meant to be taken seriously. It is a fictional, parody-style Minecraft modification. -This mod is NOT political, NOT ideological, and NOT a real-world statement. -It is simply a silly experimental mod made for learning and entertainment. +okay, AI slop aside, this mod includes various "unacceptable" +or age restricted topics, so please tell your age below. +please note this is only saved locally, and can be edited any time from Mod Menu's settings, +but for your own safety please only disable filters approppriate for your age. -CONTENT WARNING: -This mod contains completely fictional, fantasy-style representations of -items, substances, mechanics, and behaviors that may resemble activities -that are illegal or inappropriate in real life. +also do NOT attempt to do any illegal activities that you see in this mod, +this is purely a fictional representation of thoose things. -All such content exists ONLY within Minecraft as game mechanics. -Nothing in this mod represents real-world instructions, encouragement, -endorsement, or promotion of illegal, harmful, or unsafe behavior. - -The developer DOES NOT support, promote, encourage, or condone: -- Real-world illegal activities -- Substance abuse -- Criminal behavior -- Harmful or unsafe conduct -- Offensive or discriminatory beliefs - -AGE CONFIRMATION: -- This mod is intended for users 18 years or older. -- By continuing, you confirm that you meet this age requirement. - -USER RESPONSIBILITY: -- You are voluntarily choosing to use this mod. -- You accept full responsibility for its use. -- You agree not to redistribute this mod publicly without permission. - -TECHNICAL DISCLAIMER: -- This mod is provided "AS IS" without warranties. -- The developer is not responsible for crashes, data loss, or issues. - -LEGAL DISCLAIMER: -- All content is fictional. -- The developer shall not be held liable for interpretation or misuse. - -ACCEPTANCE: -By clicking "Agree", you accept all terms listed above. -If you do not agree, click "Decline" and close the game. +thank you and enjoy my silly mod <3 """); // ============================ @@ -106,26 +74,12 @@ If you do not agree, click "Decline" and close the game. } } - private static void sendDataIfAllowed() { - if (!CONFIG_FILE.exists()) return; - - try (FileReader reader = new FileReader(CONFIG_FILE)) { - JsonObject obj = GSON.fromJson(reader, JsonObject.class); - if(obj.has("allowDiagnostics") && obj.get("allowDiagnostics").getAsBoolean()) { - updateUranium(); - }; - } catch (Exception e) { - System.out.println("Error occurred while trying to read TOS config json: " + e); - } - } - - private static void save(boolean diagnosticsEnabled) { + private static void save() { try { CONFIG_FILE.getParentFile().mkdirs(); JsonObject obj = new JsonObject(); obj.addProperty("tosAccepted", true); - obj.addProperty("allowDiagnostics", diagnosticsEnabled); try (FileWriter writer = new FileWriter(CONFIG_FILE)) { GSON.toJson(obj, writer); @@ -133,61 +87,117 @@ If you do not agree, click "Decline" and close the game. } catch (Exception ignored) {} } + // ============================ + // AGE → PRESET LOGIC + // ============================ + + private static void applyPresetForAge(int age) { + List presets = new ArrayList<>(ModConfig.allPresets()); + MinecraftClient client = MinecraftClient.getInstance(); + + String targetId; + if (age >= 18) { + targetId = "18+"; + } else if (age == 17) { + targetId = "17+"; + } else { + targetId = "minor"; + } + + for (ConfigPreset preset : presets) { + if (preset.id.equals(targetId)) { + ModConfig.applyPreset(preset.id); + ModConfig.save(); + ResourcePackHelper.applyAll(client); + if (client.world != null) { + PacketByteBuf buf = PacketByteBufs.create(); + + // Write each setting as: id (string), value (boolean) + var settings = ModConfig.allSettings(); + buf.writeInt(settings.size()); + for (ConfigEntry entry : settings) { + buf.writeString(entry.id); + buf.writeBoolean(entry.get()); + } + + ClientPlayNetworking.send(Szar.CONFIG_SYNC, buf); + } + break; + } + } + } + // ============================ // CUSTOM SCREEN // ============================ private static class TosScreen extends Screen { - private CheckboxWidget diagnosticsCheckbox; + private TextFieldWidget ageField; + private ButtonWidget agreeButton; + private String errorMessage = ""; private int scrollOffset = 0; private int maxScroll = 0; private static final int PADDING = 20; private static final int TITLE_HEIGHT = 30; - private static final int FOOTER_HEIGHT = 60; + private static final int FOOTER_HEIGHT = 80; // taller to fit age input private String[] lines; protected TosScreen() { - super(Text.literal("Szar Mod - Information and Terms of Service")); + super(Text.literal("Szar Mod - Information")); } @Override protected void init() { - lines = MOD_TOS_TEXT.getString().split("\n"); int textHeight = lines.length * 12; - int checkboxHeight = 24; - int visibleHeight = this.height - TITLE_HEIGHT - FOOTER_HEIGHT - PADDING; - - maxScroll = Math.max(0, (textHeight + checkboxHeight + 20) - visibleHeight); + maxScroll = Math.max(0, (textHeight + 20) - visibleHeight); int centerX = this.width / 2; - diagnosticsCheckbox = new CheckboxWidget( - centerX - 150, - 0, // will be repositioned every frame - 300, + // Age input field + ageField = new TextFieldWidget( + this.textRenderer, + centerX - 50, + this.height - 65, + 100, 20, - Text.literal("Allow anonymous diagnostic & statistic data"), - true + Text.literal("Age") ); - - this.addDrawableChild(diagnosticsCheckbox); + ageField.setMaxLength(3); + ageField.setPlaceholder(Text.literal("Your age...")); + ageField.setChangedListener(text -> { + errorMessage = ""; + updateAgreeButton(); + }); + this.addDrawableChild(ageField); // Agree button - this.addDrawableChild(ButtonWidget.builder( + agreeButton = ButtonWidget.builder( Text.literal("Agree"), button -> { - save(diagnosticsCheckbox.isChecked()); - sendDataIfAllowed(); - MinecraftClient.getInstance().setScreen(null); + String input = ageField.getText().trim(); + try { + int age = Integer.parseInt(input); + if (age < 1 || age > 130) { + errorMessage = "Please enter a valid age."; + return; + } + save(); + applyPresetForAge(age); + MinecraftClient.getInstance().setScreen(null); + } catch (NumberFormatException e) { + errorMessage = "Please enter a valid number."; + } } - ).dimensions(centerX - 155, this.height - 40, 150, 20).build()); + ).dimensions(centerX - 155, this.height - 40, 150, 20).build(); + agreeButton.active = false; + this.addDrawableChild(agreeButton); // Decline button this.addDrawableChild(ButtonWidget.builder( @@ -196,22 +206,25 @@ If you do not agree, click "Decline" and close the game. ).dimensions(centerX + 5, this.height - 40, 150, 20).build()); } + private void updateAgreeButton() { + if (agreeButton == null) return; + String text = ageField.getText().trim(); + agreeButton.active = !text.isEmpty() && text.matches("\\d+"); + } + @Override public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - - scrollOffset -= amount * 15; - + scrollOffset -= (int)(amount * 15); if (scrollOffset < 0) scrollOffset = 0; if (scrollOffset > maxScroll) scrollOffset = maxScroll; - return true; } @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { - this.renderBackground(context); + // Title context.drawCenteredTextWithShadow( this.textRenderer, this.title, @@ -225,47 +238,44 @@ If you do not agree, click "Decline" and close the game. int boxLeft = PADDING; int boxRight = this.width - PADDING; + // Scroll area background context.fill(boxLeft, boxTop, boxRight, boxBottom, 0x88000000); - context.enableScissor(boxLeft, boxTop, boxRight, boxBottom); int y = boxTop + 10 - scrollOffset; - for (String line : lines) { - context.drawTextWithShadow( - this.textRenderer, - line, - boxLeft + 10, - y, - 0xDDDDDD - ); + context.drawTextWithShadow(this.textRenderer, line, boxLeft + 10, y, 0xDDDDDD); y += 12; } context.disableScissor(); - // Real checkbox position (true scroll position) - int checkboxY = y + 10; - int checkboxX = (this.width / 2) - 150; + // Age label + context.drawCenteredTextWithShadow( + this.textRenderer, + Text.literal("Enter your age:"), + this.width / 2, + this.height - 78, + 0xAAAAAA + ); - diagnosticsCheckbox.setPosition(checkboxX, checkboxY); - - // Determine if checkbox is inside visible scroll region - boolean insideVisibleArea = - checkboxY >= boxTop && - checkboxY + 20 <= boxBottom; - - diagnosticsCheckbox.visible = insideVisibleArea; - diagnosticsCheckbox.active = insideVisibleArea; + // Error message + if (!errorMessage.isEmpty()) { + context.drawCenteredTextWithShadow( + this.textRenderer, + Text.literal(errorMessage), + this.width / 2, + this.height - 55, + 0xFF4444 + ); + } super.render(context, mouseX, mouseY, delta); } - @Override public boolean shouldCloseOnEsc() { return false; } } - -} +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/EntityRenderMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/EntityRenderMixin.java new file mode 100644 index 0000000..4e40b28 --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/EntityRenderMixin.java @@ -0,0 +1,42 @@ +package dev.tggamesyt.szar.client.mixin; + +import dev.tggamesyt.szar.client.SzarClient; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.LivingEntity; +import net.minecraft.util.math.RotationAxis; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.time.LocalDate; + +@Mixin(LivingEntityRenderer.class) +public class EntityRenderMixin { + + @Unique + private static boolean isAprilFools() { + LocalDate today = LocalDate.now(); + return today.getMonthValue() == SzarClient.april && today.getDayOfMonth() == SzarClient.fools; + } + + @Inject( + method = "setupTransforms", + at = @At("TAIL") + ) + private void applyAprilFoolsFlip(T entity, MatrixStack matrices, float animationProgress, float bodyYaw, float tickDelta, CallbackInfo ci) { + if (!isAprilFools()) return; + + if (LivingEntityRenderer.shouldFlipUpsideDown(entity)) { + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(180.0F)); + matrices.translate(0.0F, -(entity.getHeight() + 0.1F), 0.0F); + } else { + matrices.translate(0.0F, entity.getHeight() + 0.1F, 0.0F); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(180.0F)); + } + } +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/LogoDrawerMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/LogoDrawerMixin.java new file mode 100644 index 0000000..58e8563 --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/LogoDrawerMixin.java @@ -0,0 +1,43 @@ +package dev.tggamesyt.szar.client.mixin; + +import dev.tggamesyt.szar.client.SzarClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.LogoDrawer; +import net.minecraft.util.Identifier; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import java.time.LocalDate; + +@Mixin(LogoDrawer.class) +public class LogoDrawerMixin { + + @Unique + private static final Identifier VANILLA_EDITION = + new Identifier("textures/gui/title/edition.png"); + + @Unique + private static final Identifier SZAR_EDITION = + new Identifier("szar", "textures/aprilfools/edition.png"); + + @Unique + private static boolean isAprilFools() { + LocalDate today = LocalDate.now(); + return today.getMonthValue() == SzarClient.april && today.getDayOfMonth() == SzarClient.fools; + } + + @ModifyArg( + method = "draw(Lnet/minecraft/client/gui/DrawContext;IFI)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/DrawContext;drawTexture(Lnet/minecraft/util/Identifier;IIFFIIII)V", + ordinal = 1 + ), + index = 0 + ) + private Identifier replaceEditionTexture(Identifier texture) { + return isAprilFools() ? SZAR_EDITION : texture; + } +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/MouseFlipMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/MouseFlipMixin.java new file mode 100644 index 0000000..eaf716a --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/MouseFlipMixin.java @@ -0,0 +1,33 @@ +package dev.tggamesyt.szar.client.mixin; + +import dev.tggamesyt.szar.client.SzarClient; +import net.minecraft.client.Mouse; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import java.time.LocalDate; + +@Mixin(Mouse.class) +public class MouseFlipMixin { + + @Unique + private static boolean isAprilFools() { + LocalDate today = LocalDate.now(); + return today.getMonthValue() == SzarClient.april && today.getDayOfMonth() == SzarClient.fools; + } + + @ModifyArg( + method = "updateMouse", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/network/ClientPlayerEntity;changeLookDirection(DD)V" + ), + index = 1 + ) + private double flipMouseY(double pitchDelta) { + if (!isAprilFools()) return pitchDelta; + return -pitchDelta; + } +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/ScreenFlipMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/ScreenFlipMixin.java new file mode 100644 index 0000000..1ac554b --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/ScreenFlipMixin.java @@ -0,0 +1,45 @@ +package dev.tggamesyt.szar.client.mixin; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import dev.tggamesyt.szar.client.SzarClient; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.util.math.MatrixStack; +import org.joml.Matrix4f; +import org.lwjgl.opengl.GL11; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.time.LocalDate; + +@Mixin(GameRenderer.class) +public class ScreenFlipMixin { + + @Unique + private static boolean isAprilFools() { + LocalDate today = LocalDate.now(); + return today.getMonthValue() == SzarClient.april && today.getDayOfMonth() == SzarClient.fools; + } + + @ModifyReturnValue(method = "getBasicProjectionMatrix", at = @At("RETURN")) + private Matrix4f flipProjection(Matrix4f original) { + if (!isAprilFools()) return original; + return original.scale(1.0f, -1.0f, 1.0f); + } + + @Inject(method = "renderWorld", at = @At("HEAD")) + private void setFrontFaceCW(float tickDelta, long limitTime, MatrixStack matrices, CallbackInfo ci) { + if (!isAprilFools()) return; + // Y flip reverses winding order, so tell GL that clockwise = front face + GL11.glFrontFace(GL11.GL_CW); + } + + @Inject(method = "renderWorld", at = @At("TAIL")) + private void restoreFrontFaceCCW(float tickDelta, long limitTime, MatrixStack matrices, CallbackInfo ci) { + if (!isAprilFools()) return; + // Restore default: counter-clockwise = front face + GL11.glFrontFace(GL11.GL_CCW); + } +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/TitleScreenBackgroundMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/TitleScreenBackgroundMixin.java new file mode 100644 index 0000000..a803322 --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/TitleScreenBackgroundMixin.java @@ -0,0 +1,118 @@ +package dev.tggamesyt.szar.client.mixin; + +import com.mojang.blaze3d.systems.RenderSystem; +import dev.tggamesyt.szar.client.SzarClient; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.TitleScreen; +import net.minecraft.client.texture.NativeImage; +import net.minecraft.client.texture.NativeImageBackedTexture; +import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Identifier; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.io.InputStream; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@Mixin(TitleScreen.class) +public class TitleScreenBackgroundMixin { + @Unique + private static final Identifier VANILLA_OVERLAY = + new Identifier("textures/gui/title/background/panorama_overlay.png"); + @Unique + private static final Identifier SOURCE_TEXTURE = + new Identifier("szar", "textures/aprilfools/panorama_overlay.png"); + @Unique + private static final List FRAMES = new ArrayList<>(); + @Unique + private static long lastFrameTime = 0; + @Unique + private static int currentFrame = 0; + @Unique + private static boolean framesLoaded = false; + + // frametime 1 = 1 game tick = 50ms + @Unique + private static final long FRAME_DURATION_MS = 50; + + @Unique + private static boolean isAprilFools() { + LocalDate today = LocalDate.now(); + return today.getMonthValue() == SzarClient.april && today.getDayOfMonth() == SzarClient.fools; + } + + @Unique + private static void loadFrames() { + if (framesLoaded) return; + framesLoaded = true; + + MinecraftClient client = MinecraftClient.getInstance(); + ResourceManager resourceManager = client.getResourceManager(); + + try (InputStream stream = resourceManager.getResource(SOURCE_TEXTURE).get().getInputStream()) { + NativeImage full = NativeImage.read(stream); + + int frameSize = full.getWidth(); // 720 + int totalFrames = full.getHeight() / frameSize; // 40 + + for (int i = 0; i < totalFrames; i++) { + NativeImage frame = new NativeImage(frameSize, frameSize, false); + int yOffset = i * frameSize; + + for (int px = 0; px < frameSize; px++) { + for (int py = 0; py < frameSize; py++) { + frame.setColor(px, py, full.getColor(px, yOffset + py)); + } + } + + Identifier frameId = new Identifier("szar", "aprilfools/overlay_frame_" + i); + NativeImageBackedTexture texture = new NativeImageBackedTexture(frame); + client.getTextureManager().registerTexture(frameId, texture); + FRAMES.add(frameId); + } + + full.close(); + } catch (Exception e) { + System.err.println("[Szar] Failed to load april fools overlay frames: " + e); + } + } + + @Inject(method = "render", at = @At("HEAD")) + private void onRenderHead(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { + if (!isAprilFools()) return; + loadFrames(); + + long now = System.currentTimeMillis(); + if (now - lastFrameTime >= FRAME_DURATION_MS) { + currentFrame = (currentFrame + 1) % FRAMES.size(); + lastFrameTime = now; + } + } + + @Redirect( + method = "render", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/DrawContext;drawTexture(Lnet/minecraft/util/Identifier;IIIIFFIIII)V" + ) + ) + private void redirectPanoramaOverlay(DrawContext context, Identifier texture, + int x, int y, int width, int height, + float u, float v, + int regionWidth, int regionHeight, + int textureWidth, int textureHeight) { + if (isAprilFools() && VANILLA_OVERLAY.equals(texture) && !FRAMES.isEmpty()) { + // Each frame is a square texture so region = full texture size + context.drawTexture(FRAMES.get(currentFrame), x, y, width, height, 0.0F, 0.0F, 720, 720, 720, 720); + } else { + context.drawTexture(texture, x, y, width, height, u, v, regionWidth, regionHeight, textureWidth, textureHeight); + } + } +} \ No newline at end of file diff --git a/src/client/resources/szar.client.mixins.json b/src/client/resources/szar.client.mixins.json index 1272752..6d360c4 100644 --- a/src/client/resources/szar.client.mixins.json +++ b/src/client/resources/szar.client.mixins.json @@ -5,9 +5,12 @@ "compatibilityLevel": "JAVA_17", "client": [ "BipedEntityModelMixin", + "EntityRenderMixin", "GameRendererMixin", "HeldItemRendererMixin", "ItemRendererMixin", + "LogoDrawerMixin", + "MouseFlipMixin", "MouseMixin", "PackMixin", "PackScreenCloseMixin", @@ -16,9 +19,11 @@ "PlayerModelMixin", "RadiatedItemRendererMixin", "RadiationHeartMixin", + "ScreenFlipMixin", "SplashOverlayMixin", "TGcapeMixin", - "TGnameMixin" + "TGnameMixin", + "TitleScreenBackgroundMixin" ], "injectors": { "defaultRequire": 1 diff --git a/src/main/resources/assets/szar/textures/aprilfools/edition.png b/src/main/resources/assets/szar/textures/aprilfools/edition.png new file mode 100644 index 0000000..7f1d3ad Binary files /dev/null and b/src/main/resources/assets/szar/textures/aprilfools/edition.png differ diff --git a/src/main/resources/assets/szar/textures/aprilfools/panorama_overlay.png b/src/main/resources/assets/szar/textures/aprilfools/panorama_overlay.png new file mode 100644 index 0000000..0686bde Binary files /dev/null and b/src/main/resources/assets/szar/textures/aprilfools/panorama_overlay.png differ diff --git a/src/main/resources/assets/szar/textures/aprilfools/panorama_overlay.png.mcmeta b/src/main/resources/assets/szar/textures/aprilfools/panorama_overlay.png.mcmeta new file mode 100644 index 0000000..c5d457f --- /dev/null +++ b/src/main/resources/assets/szar/textures/aprilfools/panorama_overlay.png.mcmeta @@ -0,0 +1,5 @@ +{ + "animation": { + "frametime": 1 + } +} \ No newline at end of file