april fools

This commit is contained in:
2026-03-12 10:33:11 +01:00
parent ce206409a5
commit f651a98418
16 changed files with 462 additions and 149 deletions

View File

@@ -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

View File

@@ -18,7 +18,7 @@ public class ConfigScreen extends Screen {
private final List<ButtonWidget> 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());
}

View File

@@ -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 ──────────────────────────────────────────────────────────────

View File

@@ -20,17 +20,17 @@ public class ModSettings {
// newPreset(id, displayName, Map<settingId, value>)
// 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

View File

@@ -12,42 +12,49 @@ public class ResourcePackHelper {
ResourcePackManager manager = client.getResourcePackManager();
manager.scanPacks();
Set<String> original = new HashSet<>(
manager.getEnabledProfiles().stream()
.map(p -> p.getName())
.toList()
);
List<String> orderedEnabled = manager.getEnabledProfiles().stream()
.map(ResourcePackProfile::getName)
.toList();
Set<String> enabledNames = new HashSet<>(
manager.getEnabledProfiles().stream()
.map(p -> p.getName())
.toList()
);
Set<String> original = new LinkedHashSet<>(orderedEnabled);
Set<String> toAdd = new LinkedHashSet<>();
Set<String> 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<String> 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();
}

View File

@@ -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<KeyBinding, KeyBinding> activeScramble = new HashMap<>();

View File

@@ -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<ConfigPreset> 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;
}
}
}
}

View File

@@ -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<T extends LivingEntity> {
@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));
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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<Identifier> 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);
}
}
}

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 MiB

View File

@@ -0,0 +1,5 @@
{
"animation": {
"frametime": 1
}
}