From ad0a5d57cb7d1903c873deeae27f8f771a03181c Mon Sep 17 00:00:00 2001 From: SashegDev Date: Thu, 27 Aug 2026 00:05:11 +0300 Subject: [PATCH] feat: rebuild JustASplash NeoForge 1.21.1 - hotkey Z splash, config screen, welcome, commands, vorbisspi ogg, jarJar --- build.gradle | 6 +- .../me/sashegdev/justasplash/JustASplash.java | 40 ++++ .../justasplash/JustASplashCommands.java | 30 +++ .../justasplash/client/AudioPlayer.java | 69 ++++++- .../justasplash/client/ConfigPackHandler.java | 33 +++ .../client/ConfigPackResources.java | 150 ++++++++++++++ .../justasplash/client/ConfigScreen.java | 193 ++++++++++++++++++ .../justasplash/client/HotkeyTick.java | 2 + .../justasplash/client/ImageLoader.java | 117 ++++++++++- .../client/ScreenSplashHandler.java | 16 ++ .../justasplash/client/SplashManager.java | 20 +- .../justasplash/client/SplashRenderer.java | 30 +-- .../client/TitleScreenHandler.java | 21 ++ .../justasplash/client/WelcomeScreen.java | 80 ++++++++ .../justasplash/config/JustASplashConfig.java | 10 +- .../justasplash/mixin/LibraryMixin.java | 30 +++ .../resources/META-INF/neoforge.mods.toml | 3 + .../assets/justasplash/lang/en_us.json | 32 +++ .../assets/justasplash/lang/ru_ru.json | 32 +++ .../resources/assets/justasplash/sounds.json | 5 + .../assets/justasplash/sounds/splash.ogg | Bin 0 -> 28166 bytes .../textures/gui/splashes/splash.jpg | Bin 0 -> 36983 bytes src/main/resources/justasplash.mixins.json | 13 ++ 23 files changed, 897 insertions(+), 35 deletions(-) create mode 100644 src/main/java/me/sashegdev/justasplash/JustASplashCommands.java create mode 100644 src/main/java/me/sashegdev/justasplash/client/ConfigPackHandler.java create mode 100644 src/main/java/me/sashegdev/justasplash/client/ConfigPackResources.java create mode 100644 src/main/java/me/sashegdev/justasplash/client/ConfigScreen.java create mode 100644 src/main/java/me/sashegdev/justasplash/client/ScreenSplashHandler.java create mode 100644 src/main/java/me/sashegdev/justasplash/client/TitleScreenHandler.java create mode 100644 src/main/java/me/sashegdev/justasplash/client/WelcomeScreen.java create mode 100644 src/main/java/me/sashegdev/justasplash/mixin/LibraryMixin.java create mode 100644 src/main/resources/assets/justasplash/lang/en_us.json create mode 100644 src/main/resources/assets/justasplash/lang/ru_ru.json create mode 100644 src/main/resources/assets/justasplash/sounds.json create mode 100755 src/main/resources/assets/justasplash/sounds/splash.ogg create mode 100755 src/main/resources/assets/justasplash/textures/gui/splashes/splash.jpg create mode 100644 src/main/resources/justasplash.mixins.json diff --git a/build.gradle b/build.gradle index 741e9b7..638a5ec 100644 --- a/build.gradle +++ b/build.gradle @@ -34,10 +34,8 @@ configurations { dependencies { implementation "net.neoforged:neoforge:${neo_version}" - implementation "javazoom:jlayer:1.0.1" - localRuntime "com.googlecode.soundlibs:mp3spi:1.9.5.4" - localRuntime "com.googlecode.soundlibs:tritonus-share:0.3.7.4" - localRuntime "com.googlecode.soundlibs:jorbis:0.0.17.4" + localRuntime "com.googlecode.soundlibs:vorbisspi:1.0.3.3" + jarJar "com.googlecode.soundlibs:vorbisspi:1.0.3.3" } tasks.named('processResources', Copy).configure { diff --git a/src/main/java/me/sashegdev/justasplash/JustASplash.java b/src/main/java/me/sashegdev/justasplash/JustASplash.java index 84f081c..be9e966 100644 --- a/src/main/java/me/sashegdev/justasplash/JustASplash.java +++ b/src/main/java/me/sashegdev/justasplash/JustASplash.java @@ -1,15 +1,55 @@ package me.sashegdev.justasplash; +import me.sashegdev.justasplash.client.ConfigScreen; import me.sashegdev.justasplash.config.JustASplashConfig; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModContainer; import net.neoforged.fml.common.Mod; import net.neoforged.fml.config.ModConfig; +import net.neoforged.neoforge.client.gui.IConfigScreenFactory; @Mod(value = "justasplash", dist = Dist.CLIENT) public class JustASplash { public JustASplash(IEventBus modBus, ModContainer container) { container.registerConfig(ModConfig.Type.CLIENT, JustASplashConfig.SPEC); + container.registerExtensionPoint(IConfigScreenFactory.class, (c, p) -> new ConfigScreen(p)); + ensureAssets(); + } + + private static void ensureAssets() { + try { + java.nio.file.Path dir = java.nio.file.Paths.get("config/justasplash/assets"); + java.nio.file.Files.createDirectories(dir); + String txt = "JustASplash assets:\n" + + "1) Кидай png/jpg/gif сюда и выбирай в конфиге 'следующий' (config/justasplash/assets/)\n" + + "2) Или укажи ResourceLocation вроде justasplash:textures/gui/splashes/splash.jpg (ресурспак)\n" + + "EN: Drop png/jpg/gif here and pick 'Next' in config, or use ResourceLocation\n" + + "Sounds: ogg/wav сюда. Пример: overlay.png, sound.ogg\n"; + java.nio.file.Files.writeString(dir.resolve("README.txt"), txt); + copyDefault("assets/justasplash/textures/gui/splashes/splash.jpg", dir.resolve("splash.jpg")); + copyDefault("assets/justasplash/sounds/splash.ogg", dir.resolve("splash.ogg")); + try { + String snd = JustASplashConfig.SPLASH_SOUND.get(); + if (snd != null && snd.toLowerCase().endsWith(".mp3")) { + String ogg = snd.substring(0, snd.length() - 4) + ".ogg"; + java.nio.file.Path oggFile = dir.resolve(ogg.substring(ogg.lastIndexOf('/') + 1)); + java.nio.file.Path wavFile = dir.resolve(ogg.substring(ogg.lastIndexOf('/') + 1).replace(".ogg", ".wav")); + if (java.nio.file.Files.exists(oggFile) || java.nio.file.Files.exists(wavFile)) { + JustASplashConfig.SPLASH_SOUND.set(ogg); + JustASplashConfig.SPEC.save(); + } + } + } catch (Exception ignored) {} + } catch (Exception ignored) {} + } + + private static void copyDefault(String res, java.nio.file.Path dst) { + try { + if (java.nio.file.Files.exists(dst)) return; + try (var in = JustASplash.class.getClassLoader().getResourceAsStream(res)) { + if (in != null) java.nio.file.Files.copy(in, dst); + } + } catch (Exception ignored) {} } } diff --git a/src/main/java/me/sashegdev/justasplash/JustASplashCommands.java b/src/main/java/me/sashegdev/justasplash/JustASplashCommands.java new file mode 100644 index 0000000..b65a464 --- /dev/null +++ b/src/main/java/me/sashegdev/justasplash/JustASplashCommands.java @@ -0,0 +1,30 @@ +package me.sashegdev.justasplash; + +import com.mojang.brigadier.CommandDispatcher; +import me.sashegdev.justasplash.client.ImageLoader; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.RegisterCommandsEvent; + +@EventBusSubscriber(modid = "justasplash", value = Dist.CLIENT) +public class JustASplashCommands { + @SubscribeEvent + public static void onRegister(RegisterCommandsEvent e) { + CommandDispatcher d = e.getDispatcher(); + d.register(Commands.literal("justasplash") + .then(Commands.literal("reload").executes(ctx -> { + ImageLoader.clearCache(); + ctx.getSource().sendSuccess(() -> Component.translatable("command.justasplash.reload.success"), false); + return 1; + })) + .then(Commands.literal("test").executes(ctx -> { + net.minecraft.client.Minecraft.getInstance().execute(() -> me.sashegdev.justasplash.client.SplashManager.trigger()); + ctx.getSource().sendSuccess(() -> Component.translatable("toast.justasplash.ok"), false); + return 1; + }))); + } +} diff --git a/src/main/java/me/sashegdev/justasplash/client/AudioPlayer.java b/src/main/java/me/sashegdev/justasplash/client/AudioPlayer.java index f389475..bfece23 100644 --- a/src/main/java/me/sashegdev/justasplash/client/AudioPlayer.java +++ b/src/main/java/me/sashegdev/justasplash/client/AudioPlayer.java @@ -3,7 +3,6 @@ package me.sashegdev.justasplash.client; import net.minecraft.client.Minecraft; import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.client.resources.sounds.SoundInstance; -import net.minecraft.client.sounds.SoundManager; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundSource; @@ -18,18 +17,35 @@ import java.io.InputStream; public class AudioPlayer { private static SoundInstance current; - public static double play(ResourceLocation loc, float vol) { + public static double play(ResourceLocation fileLoc, float vol) { try { var mc = Minecraft.getInstance(); var rm = mc.getResourceManager(); - var res = rm.getResource(loc).orElse(null); + var res = rm.getResource(fileLoc).orElse(null); double dur = 3.0; if (res != null) { try (InputStream in = res.open()) { - dur = probeDuration(in, loc.getPath()); + byte[] data = in.readAllBytes(); + try (InputStream in2 = new java.io.ByteArrayInputStream(data)) { dur = probeDuration(in2, fileLoc.getPath()); } catch (Exception ignored) {} } catch (Exception ignored) {} + ResourceLocation evLoc = toEventId(fileLoc); + SoundEvent ev = SoundEvent.createVariableRangeEvent(evLoc); + current = new SimpleSoundInstance(ev.getLocation(), SoundSource.MASTER, vol, 1.0f, SoundInstance.createUnseededRandom(), false, 0, SoundInstance.Attenuation.NONE, 0, 0, 0, true); + mc.getSoundManager().play(current); + return dur; + } else { + java.nio.file.Path fp = ImageLoader.resolveConfigFile(fileLoc); + if (fp != null && java.nio.file.Files.exists(fp)) { + try (InputStream in = java.nio.file.Files.newInputStream(fp)) { + byte[] data = in.readAllBytes(); + try (InputStream in2 = new java.io.ByteArrayInputStream(data)) { dur = probeDuration(in2, fp.toString()); } catch (Exception ignored) {} + } catch (Exception ignored) {} + playFileClip(fp, vol); + return dur; + } } - SoundEvent ev = SoundEvent.createVariableRangeEvent(loc); + ResourceLocation evLoc = toEventId(fileLoc); + SoundEvent ev = SoundEvent.createVariableRangeEvent(evLoc); current = new SimpleSoundInstance(ev.getLocation(), SoundSource.MASTER, vol, 1.0f, SoundInstance.createUnseededRandom(), false, 0, SoundInstance.Attenuation.NONE, 0, 0, 0, true); mc.getSoundManager().play(current); return dur; @@ -39,6 +55,47 @@ public class AudioPlayer { } } + private static void playFileClip(java.nio.file.Path fp, float vol) { + new Thread(() -> { + try { + javax.sound.sampled.AudioInputStream ais = AudioSystem.getAudioInputStream(fp.toFile()); + javax.sound.sampled.AudioFormat base = ais.getFormat(); + javax.sound.sampled.AudioFormat decoded = new javax.sound.sampled.AudioFormat(javax.sound.sampled.AudioFormat.Encoding.PCM_SIGNED, base.getSampleRate(), 16, base.getChannels(), base.getChannels() * 2, base.getSampleRate(), false); + javax.sound.sampled.AudioInputStream dais = AudioSystem.getAudioInputStream(decoded, ais); + javax.sound.sampled.Clip clip = AudioSystem.getClip(); + clip.open(dais); + if (clip.isControlSupported(javax.sound.sampled.FloatControl.Type.MASTER_GAIN)) { + float gain = 20f * (float) Math.log10(Math.max(0.0001, vol)); + gain = Math.max(-80f, Math.min(6f, gain)); + ((javax.sound.sampled.FloatControl) clip.getControl(javax.sound.sampled.FloatControl.Type.MASTER_GAIN)).setValue(gain); + } + clip.start(); + Thread.sleep(clip.getMicrosecondLength() / 1000 + 100); + clip.close(); + dais.close(); + ais.close(); + } catch (Exception e) { + try { + javax.sound.sampled.Clip clip2 = AudioSystem.getClip(); + javax.sound.sampled.AudioInputStream ais2 = AudioSystem.getAudioInputStream(fp.toFile()); + clip2.open(ais2); + clip2.start(); + } catch (Exception ex) { ex.printStackTrace(); } + } + }, "justasplash-audio").start(); + } + + private static ResourceLocation toEventId(ResourceLocation fileLoc) { + String p = fileLoc.getPath(); + if (p.contains("/")) { + String name = p.substring(p.lastIndexOf('/') + 1); + int dot = name.lastIndexOf('.'); + if (dot > 0) name = name.substring(0, dot); + return ResourceLocation.fromNamespaceAndPath(fileLoc.getNamespace(), name); + } + return fileLoc; + } + private static double probeDuration(InputStream in, String path) { try { in.mark(Integer.MAX_VALUE); @@ -48,7 +105,7 @@ public class AudioPlayer { double sec = frames / rate; if (sec > 0 && sec < 300) return sec; } catch (Exception ignored) {} - return path.endsWith(".mp3") ? 5.0 : 3.0; + return 3.0; } public static void stop() { diff --git a/src/main/java/me/sashegdev/justasplash/client/ConfigPackHandler.java b/src/main/java/me/sashegdev/justasplash/client/ConfigPackHandler.java new file mode 100644 index 0000000..68536e1 --- /dev/null +++ b/src/main/java/me/sashegdev/justasplash/client/ConfigPackHandler.java @@ -0,0 +1,33 @@ +package me.sashegdev.justasplash.client; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.packs.PackLocationInfo; +import net.minecraft.server.packs.PackResources; +import net.minecraft.server.packs.PackSelectionConfig; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.repository.Pack; +import net.minecraft.server.packs.repository.PackCompatibility; +import net.minecraft.server.packs.repository.PackSource; +import net.minecraft.world.flag.FeatureFlagSet; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.AddPackFindersEvent; + +import java.util.Optional; + +@EventBusSubscriber(value = Dist.CLIENT, modid = "justasplash", bus = EventBusSubscriber.Bus.MOD) +public class ConfigPackHandler { + @SubscribeEvent + public static void onAddPackFinders(AddPackFindersEvent e) { + if (e.getPackType() != PackType.CLIENT_RESOURCES) return; + PackLocationInfo info = new PackLocationInfo("justasplash_config", Component.literal("JustASplash Config Assets"), PackSource.BUILT_IN, Optional.empty()); + PackSelectionConfig sel = new PackSelectionConfig(true, Pack.Position.TOP, false); + Pack.Metadata meta = new Pack.Metadata(Component.literal("JustASplash Config Assets"), PackCompatibility.COMPATIBLE, FeatureFlagSet.of(), java.util.List.of(), false); + Pack pack = new Pack(info, new Pack.ResourcesSupplier() { + @Override public PackResources openPrimary(PackLocationInfo loc) { return new ConfigPackResources(loc); } + @Override public PackResources openFull(PackLocationInfo loc, Pack.Metadata m) { return new ConfigPackResources(loc); } + }, meta, sel); + e.addRepositorySource(c -> c.accept(pack)); + } +} diff --git a/src/main/java/me/sashegdev/justasplash/client/ConfigPackResources.java b/src/main/java/me/sashegdev/justasplash/client/ConfigPackResources.java new file mode 100644 index 0000000..562a983 --- /dev/null +++ b/src/main/java/me/sashegdev/justasplash/client/ConfigPackResources.java @@ -0,0 +1,150 @@ +package me.sashegdev.justasplash.client; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.PackLocationInfo; +import net.minecraft.server.packs.PackResources; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.metadata.MetadataSectionSerializer; +import net.minecraft.server.packs.resources.IoSupplier; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.Set; + +public class ConfigPackResources implements PackResources { + private final PackLocationInfo loc; + + public ConfigPackResources(PackLocationInfo loc) { + this.loc = loc; + } + + private Path configDir() { + return Paths.get("config/justasplash/assets"); + } + + @Override + public IoSupplier getRootResource(String... path) { + String joined = String.join("/", path); + if (joined.equals("pack.mcmeta")) { + String json = "{\"pack\":{\"pack_format\":34,\"description\":\"JustASplash Config Assets\"}}"; + return () -> new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + } + if (joined.equals("pack.png")) return null; + return null; + } + + @Override + public IoSupplier getResource(PackType type, ResourceLocation location) { + if (type != PackType.CLIENT_RESOURCES) return null; + if (!location.getNamespace().equals("justasplash")) return null; + String p = location.getPath(); + if (p.equals("sounds.json")) { + return this::openSoundsJson; + } + Path file = null; + if (p.startsWith("textures/gui/splashes/")) { + String name = p.substring("textures/gui/splashes/".length()); + file = configDir().resolve(name); + } else if (p.startsWith("sounds/")) { + String name = p.substring("sounds/".length()); + file = configDir().resolve(name); + } else if (p.startsWith("textures/")) { + String name = p.substring(p.lastIndexOf('/') + 1); + file = configDir().resolve(name); + } + if (file != null && Files.exists(file)) { + Path f2 = file; + return () -> Files.newInputStream(f2); + } + return null; + } + + private InputStream openSoundsJson() throws IOException { + StringBuilder sb = new StringBuilder("{\"splash\":{\"sounds\":[{\"name\":\"justasplash:splash\",\"stream\":true}]},"); + try { + Path dir = configDir(); + if (Files.exists(dir)) { + var files = Files.list(dir).filter(pp -> { + String n = pp.getFileName().toString().toLowerCase(); + return n.endsWith(".ogg") || n.endsWith(".wav"); + }).toList(); + for (Path f : files) { + String name = f.getFileName().toString(); + int dot = name.lastIndexOf('.'); + String base = dot > 0 ? name.substring(0, dot) : name; + if (base.equals("splash")) continue; + sb.append("\"").append(base).append("\":{\"sounds\":[{\"name\":\"justasplash:").append(base).append("\",\"stream\":true}]},"); + } + } + } catch (Exception ignored) {} + if (sb.charAt(sb.length() - 1) == ',') sb.setLength(sb.length() - 1); + sb.append("}"); + return new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8)); + } + + @Override + public void listResources(PackType type, String namespace, String path, ResourceOutput out) { + if (type != PackType.CLIENT_RESOURCES || !namespace.equals("justasplash")) return; + Path dir = configDir(); + if (!Files.exists(dir)) return; + try { + if (path.equals("textures/gui/splashes")) { + Files.list(dir).forEach(p -> { + String n = p.getFileName().toString().toLowerCase(); + if (n.endsWith(".png") || n.endsWith(".jpg") || n.endsWith(".jpeg") || n.endsWith(".gif")) { + ResourceLocation loc = ResourceLocation.fromNamespaceAndPath("justasplash", "textures/gui/splashes/" + p.getFileName().toString()); + out.accept(loc, () -> Files.newInputStream(p)); + } + }); + } else if (path.equals("sounds")) { + Files.list(dir).forEach(p -> { + String n = p.getFileName().toString().toLowerCase(); + if (n.endsWith(".ogg") || n.endsWith(".wav")) { + ResourceLocation loc = ResourceLocation.fromNamespaceAndPath("justasplash", "sounds/" + p.getFileName().toString()); + out.accept(loc, () -> Files.newInputStream(p)); + } + }); + } else if (path.equals("")) { + out.accept(ResourceLocation.fromNamespaceAndPath("justasplash", "sounds.json"), this::openSoundsJson); + out.accept(ResourceLocation.fromNamespaceAndPath("justasplash", "pack.mcmeta"), () -> new ByteArrayInputStream("{\"pack\":{\"pack_format\":34,\"description\":\"JustASplash Config Assets\"}}".getBytes(StandardCharsets.UTF_8))); + } + } catch (Exception ignored) {} + } + + @Override + public Set getNamespaces(PackType type) { + if (type == PackType.CLIENT_RESOURCES) { + Set s = new HashSet<>(); + s.add("justasplash"); + return s; + } + return Set.of(); + } + + @Override + public T getMetadataSection(MetadataSectionSerializer serializer) throws IOException { + if (serializer != null && "pack".equals(serializer.getMetadataSectionName())) { + net.minecraft.server.packs.metadata.pack.PackMetadataSection sec = new net.minecraft.server.packs.metadata.pack.PackMetadataSection(net.minecraft.network.chat.Component.literal("JustASplash Config Assets"), 34, java.util.Optional.empty()); + return (T) sec; + } + try { + if (serializer == net.minecraft.server.packs.metadata.pack.PackMetadataSection.TYPE) { + net.minecraft.server.packs.metadata.pack.PackMetadataSection sec = new net.minecraft.server.packs.metadata.pack.PackMetadataSection(net.minecraft.network.chat.Component.literal("JustASplash Config Assets"), 34, java.util.Optional.empty()); + return (T) sec; + } + } catch (Exception ignored) {} + return null; + } + + @Override + public PackLocationInfo location() { return loc; } + + @Override + public void close() {} +} diff --git a/src/main/java/me/sashegdev/justasplash/client/ConfigScreen.java b/src/main/java/me/sashegdev/justasplash/client/ConfigScreen.java new file mode 100644 index 0000000..218512e --- /dev/null +++ b/src/main/java/me/sashegdev/justasplash/client/ConfigScreen.java @@ -0,0 +1,193 @@ +package me.sashegdev.justasplash.client; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractSliderButton; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import me.sashegdev.justasplash.config.JustASplashConfig; +import net.minecraft.resources.ResourceLocation; + +import java.util.List; + +public class ConfigScreen extends Screen { + private final Screen parent; + private EditBox imageBox, soundBox, fadeBox, volBox; + private AbstractSliderButton fadeSlider, volSlider; + private Button imgNextBtn, sndNextBtn; + private double fade, volume; + private List imgFiles = List.of(); + private List sndFiles = List.of(); + + public ConfigScreen(Screen parent) { + super(Component.translatable("config.justasplash.title")); + this.parent = parent; + } + + @Override + protected void init() { + fade = JustASplashConfig.FADE.get(); + volume = JustASplashConfig.VOLUME.get(); + imgFiles = ImageLoader.listConfigImages(); + sndFiles = ImageLoader.listConfigSounds(); + int previewW = Math.min(280, width / 3); + int leftW = 300; + int gap = 20; + int leftX = width / 2 - leftW / 2 - (previewW + gap) / 2; + if (leftX < 10) leftX = 10; + int y = 35; + + imageBox = new EditBox(font, leftX, y, 200, 20, Component.translatable("config.justasplash.splashImage")); + imageBox.setMaxLength(512); + imageBox.setValue(JustASplashConfig.SPLASH_IMAGE.get()); + imageBox.setResponder(v -> { updatePreview(); }); + addRenderableWidget(imageBox); + imgNextBtn = Button.builder(Component.translatable("config.justasplash.nextImage"), b -> cycleImage()).bounds(leftX + 205, y, 95, 20).tooltip(Tooltip.create(Component.literal(pickLabel(imgFiles, imageBox.getValue(), "")))).build(); + addRenderableWidget(imgNextBtn); + y += 30; + soundBox = new EditBox(font, leftX, y, 200, 20, Component.translatable("config.justasplash.splashSound")); + soundBox.setMaxLength(512); + soundBox.setValue(JustASplashConfig.SPLASH_SOUND.get()); + addRenderableWidget(soundBox); + sndNextBtn = Button.builder(Component.translatable("config.justasplash.nextSound"), b -> cycleSound()).bounds(leftX + 205, y, 95, 20).tooltip(Tooltip.create(Component.literal(pickLabel(sndFiles, soundBox.getValue(), "")))).build(); + addRenderableWidget(sndNextBtn); + y += 30; + fadeBox = new EditBox(font, leftX, y, 145, 20, Component.translatable("config.justasplash.fade")); + fadeBox.setMaxLength(32); + fadeBox.setValue(String.valueOf(fade)); + fadeBox.setResponder(v -> { try { fade = Math.max(-1, Math.min(60, Double.parseDouble(v))); syncSlider(fadeSlider, (fade + 1) / 61.0); } catch (Exception ignored) {} }); + addRenderableWidget(fadeBox); + volBox = new EditBox(font, leftX + 155, y, 145, 20, Component.translatable("config.justasplash.volume")); + volBox.setMaxLength(32); + volBox.setValue(String.valueOf(volume)); + volBox.setResponder(v -> { try { volume = Math.max(0, Math.min(1, Double.parseDouble(v))); syncSlider(volSlider, volume); } catch (Exception ignored) {} }); + addRenderableWidget(volBox); + y += 25; + fadeSlider = new AbstractSliderButton(leftX, y, 145, 20, Component.literal("Fade"), (fade + 1) / 61.0) { + { updateMessage(); } + @Override protected void updateMessage() { setMessage(Component.literal("Fade: " + String.format("%.1f", fade) + "s")); } + @Override protected void applyValue() { fade = Math.round((value * 61.0 - 1) * 2) / 2.0; fadeBox.setValue(String.valueOf(fade)); updateMessage(); } + }; + volSlider = new AbstractSliderButton(leftX + 155, y, 145, 20, Component.literal("Vol"), volume) { + { updateMessage(); } + @Override protected void updateMessage() { setMessage(Component.literal("Vol: " + String.format("%.0f%%", volume * 100))); } + @Override protected void applyValue() { volume = Math.round(value * 100) / 100.0; volBox.setValue(String.valueOf(volume)); updateMessage(); } + }; + addRenderableWidget(fadeSlider); + addRenderableWidget(volSlider); + y += 30; + addRenderableWidget(Button.builder(Component.translatable("config.justasplash.test"), b -> { if (trySave(false)) SplashManager.trigger(); }).bounds(leftX, y, 145, 20).build()); + addRenderableWidget(Button.builder(Component.translatable("config.justasplash.save"), b -> { if (trySave(true)) onClose(); }).bounds(leftX + 155, y, 145, 20).build()); + y += 25; + addRenderableWidget(Button.builder(Component.translatable("config.justasplash.cancel"), b -> onClose()).bounds(leftX + 75, y, 150, 20).build()); + } + + private void syncSlider(AbstractSliderButton s, double v) { + if (s == null) return; + try { + var f = AbstractSliderButton.class.getDeclaredField("value"); + f.setAccessible(true); + f.set(s, Math.max(0, Math.min(1, v))); + var m = AbstractSliderButton.class.getDeclaredMethod("updateMessage"); + m.setAccessible(true); + m.invoke(s); + } catch (Exception ignored) {} + } + + private String pickLabel(List files, String cur, String def) { + if (files.isEmpty()) return def; + for (String f : files) if (cur.contains(f)) return f; + return files.get(0); + } + + private void cycleImage() { + if (imgFiles.isEmpty()) return; + String cur = imageBox.getValue(); + int idx = -1; + for (int i = 0; i < imgFiles.size(); i++) if (cur.contains(imgFiles.get(i))) idx = i; + idx = (idx + 1) % imgFiles.size(); + String f = imgFiles.get(idx); + imageBox.setValue("justasplash:textures/gui/splashes/" + f); + if (imgNextBtn != null) imgNextBtn.setTooltip(Tooltip.create(Component.literal(f))); + updatePreview(); + } + + private void cycleSound() { + if (sndFiles.isEmpty()) return; + String cur = soundBox.getValue(); + int idx = -1; + for (int i = 0; i < sndFiles.size(); i++) if (cur.contains(sndFiles.get(i))) idx = i; + idx = (idx + 1) % sndFiles.size(); + String f = sndFiles.get(idx); + soundBox.setValue("justasplash:sounds/" + f); + if (sndNextBtn != null) sndNextBtn.setTooltip(Tooltip.create(Component.literal(f))); + } + + private void updatePreview() { + try { + String v = imageBox.getValue(); + ResourceLocation loc = ResourceLocation.tryParse(v); + if (loc != null) { ImageLoader.preload(loc); } + } catch (Exception ignored) {} + } + + private boolean trySave(boolean persist) { + try { fade = Double.parseDouble(fadeBox.getValue()); } catch (Exception e) {} + try { volume = Double.parseDouble(volBox.getValue()); } catch (Exception e) {} + fade = Math.max(-1, Math.min(60, fade)); + volume = Math.max(0, Math.min(1, volume)); + JustASplashConfig.SPLASH_IMAGE.set(imageBox.getValue()); + JustASplashConfig.SPLASH_SOUND.set(soundBox.getValue()); + JustASplashConfig.FADE.set(fade); + JustASplashConfig.VOLUME.set(volume); + if (persist) { + JustASplashConfig.SPEC.save(); + ImageLoader.clearCache(); + Minecraft.getInstance().execute(() -> { + try { Minecraft.getInstance().reloadResourcePacks(); } catch (Exception ignored) {} + }); + } + return true; + } + + @Override + public void render(GuiGraphics g, int mx, int my, float d) { + g.fill(0, 0, width, height, 0xCC0F0F1A); + super.render(g, mx, my, d); + int previewW = Math.min(280, width / 3); + int leftW = 300; + int gap = 20; + int leftX = width / 2 - leftW / 2 - (previewW + gap) / 2; + if (leftX < 10) leftX = 10; + int previewX = leftX + leftW + gap; + int previewY = 35; + int previewH = 180; + g.drawCenteredString(font, title, width / 2, 12, 0xFFE0E0FF); + g.drawString(font, Component.translatable("config.justasplash.splashImage"), leftX, 23, 0xFFAAAAFF); + g.drawString(font, Component.translatable("config.justasplash.splashSound"), leftX, 53, 0xFFAAAAFF); + g.drawString(font, Component.translatable("config.justasplash.fade"), leftX, 83, 0xFFAAAAFF); + g.drawString(font, Component.translatable("config.justasplash.volume"), leftX + 155, 83, 0xFFAAAAFF); + g.drawString(font, Component.translatable("config.justasplash.hint1"), leftX, 205, 0xFF8A8AC0, false); + g.drawString(font, Component.translatable("config.justasplash.hint2"), leftX, 215, 0xFF8A8AC0, false); + g.fill(previewX - 1, previewY - 1, previewX + previewW + 1, previewY + previewH + 1, 0xFF3D3D5A); + g.fill(previewX, previewY, previewX + previewW, previewY + previewH, 0xFF0F0F1A); + g.drawString(font, Component.translatable("config.justasplash.preview"), previewX, previewY - 10, 0xFFAAAAFF); + ResourceLocation tex = ImageLoader.getTexture(); + int iw = ImageLoader.getWidth(), ih = ImageLoader.getHeight(); + if (tex != null && iw > 0) { + float scale = Math.min((float) previewW / iw, (float) previewH / ih); + int dw = (int) (iw * scale), dh = (int) (ih * scale); + int dx = previewX + (previewW - dw) / 2, dy = previewY + (previewH - dh) / 2; + g.blit(tex, dx, dy, 0, 0, dw, dh, dw, dh); + g.drawString(font, iw + "x" + ih, previewX + 4, previewY + previewH - 10, 0xFFE0E0FF); + } else { + g.drawCenteredString(font, Component.translatable("config.justasplash.noPreview"), previewX + previewW / 2, previewY + previewH / 2, 0xFF8A8AC0); + } + } + + @Override + public void onClose() { Minecraft.getInstance().setScreen(parent); } +} diff --git a/src/main/java/me/sashegdev/justasplash/client/HotkeyTick.java b/src/main/java/me/sashegdev/justasplash/client/HotkeyTick.java index bbee1ff..1f6f336 100644 --- a/src/main/java/me/sashegdev/justasplash/client/HotkeyTick.java +++ b/src/main/java/me/sashegdev/justasplash/client/HotkeyTick.java @@ -7,9 +7,11 @@ import net.neoforged.neoforge.client.event.ClientTickEvent; @EventBusSubscriber(value = Dist.CLIENT, modid = "justasplash") public class HotkeyTick { + private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger("justasplash"); @SubscribeEvent public static void onTick(ClientTickEvent.Post e) { while (Hotkey.SPLASH.consumeClick()) { + LOG.info("[justasplash] hotkey Z pressed"); SplashManager.trigger(); } } diff --git a/src/main/java/me/sashegdev/justasplash/client/ImageLoader.java b/src/main/java/me/sashegdev/justasplash/client/ImageLoader.java index 34bf984..c52a44e 100644 --- a/src/main/java/me/sashegdev/justasplash/client/ImageLoader.java +++ b/src/main/java/me/sashegdev/justasplash/client/ImageLoader.java @@ -4,6 +4,8 @@ import com.mojang.blaze3d.platform.NativeImage; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.resources.ResourceLocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; import javax.imageio.ImageReader; @@ -13,7 +15,9 @@ import java.util.ArrayList; import java.util.List; public class ImageLoader { + private static final Logger LOG = LoggerFactory.getLogger("justasplash"); private static ResourceLocation texLoc; + private static ResourceLocation cachedLoc; private static int w, h; private static List frames = List.of(); private static int frameIdx = 0; @@ -21,22 +25,114 @@ public class ImageLoader { record Frame(ResourceLocation loc, int delayMs) {} + public static void preload(ResourceLocation loc) { + if (loc != null && loc.equals(cachedLoc) && texLoc != null && w > 0) { + LOG.info("[justasplash] preload skip cached {}", loc); + return; + } + load(loc); + } + public static void load(ResourceLocation loc) { + LOG.info("[justasplash] ImageLoader.load {}", loc); try { + byte[] data = null; + String p = loc.getPath().toLowerCase(); var rm = Minecraft.getInstance().getResourceManager(); var res = rm.getResource(loc).orElse(null); - if (res == null) return; - try (InputStream in = res.open()) { - if (loc.getPath().endsWith(".gif")) loadGif(in); - else loadPng(in); + if (res != null) { + LOG.info("[justasplash] resource found: {}", loc); + try (InputStream in = res.open()) { data = in.readAllBytes(); } + } else { + java.nio.file.Path fp = resolveConfigFile(loc); + if (fp != null && java.nio.file.Files.exists(fp)) { + LOG.info("[justasplash] loading from config file {}", fp); + data = java.nio.file.Files.readAllBytes(fp); + p = fp.getFileName().toString().toLowerCase(); + } else { + LOG.warn("[justasplash] resource not found: {} (also checked config)", loc); + return; + } } + try (InputStream in2 = new java.io.ByteArrayInputStream(data)) { + if (p.endsWith(".gif")) loadGif(new java.io.ByteArrayInputStream(data)); + else if (p.endsWith(".png")) loadPng(new java.io.ByteArrayInputStream(data)); + else loadGeneric(new java.io.ByteArrayInputStream(data)); + } + cachedLoc = loc; + LOG.info("[justasplash] loaded tex={} w={} h={} frames={}", texLoc, w, h, frames.size()); } catch (Exception e) { + LOG.error("[justasplash] load failed {}", loc, e); e.printStackTrace(); } } + public static java.nio.file.Path resolveConfigFile(ResourceLocation loc) { + try { + String name = loc.getPath(); + if (name.contains("/")) name = name.substring(name.lastIndexOf('/') + 1); + java.nio.file.Path dir = java.nio.file.Paths.get("config/justasplash/assets"); + java.nio.file.Path direct = dir.resolve(name); + if (java.nio.file.Files.exists(direct)) return direct; + java.nio.file.Path byLoc = java.nio.file.Paths.get("config/justasplash/assets").resolve(loc.getPath()); + if (java.nio.file.Files.exists(byLoc)) return byLoc; + if (loc.getNamespace().equals("justasplash")) { + java.nio.file.Path alt = dir.resolve(loc.getPath().replace("textures/gui/splashes/", "").replace("sounds/", "")); + if (java.nio.file.Files.exists(alt)) return alt; + } + } catch (Exception ignored) {} + return null; + } + + public static java.util.List listConfigImages() { + try { + java.nio.file.Path dir = java.nio.file.Paths.get("config/justasplash/assets"); + if (!java.nio.file.Files.exists(dir)) return java.util.List.of(); + return java.nio.file.Files.list(dir).filter(p -> { + String n = p.getFileName().toString().toLowerCase(); + return n.endsWith(".png") || n.endsWith(".jpg") || n.endsWith(".jpeg") || n.endsWith(".gif"); + }).map(p -> p.getFileName().toString()).sorted().toList(); + } catch (Exception e) { return java.util.List.of(); } + } + + public static java.util.List listConfigSounds() { + try { + java.nio.file.Path dir = java.nio.file.Paths.get("config/justasplash/assets"); + if (!java.nio.file.Files.exists(dir)) return java.util.List.of(); + return java.nio.file.Files.list(dir).filter(p -> { + String n = p.getFileName().toString().toLowerCase(); + return n.endsWith(".ogg") || n.endsWith(".wav"); + }).map(p -> p.getFileName().toString()).sorted().toList(); + } catch (Exception e) { return java.util.List.of(); } + } + + private static void loadGeneric(InputStream in) throws Exception { + java.awt.image.BufferedImage bi = ImageIO.read(in); + if (bi == null) throw new java.io.IOException("ImageIO.read returned null for generic image"); + LOG.info("[justasplash] ImageIO read generic {}x{}", bi.getWidth(), bi.getHeight()); + NativeImage img = toNativeImage(bi); + w = img.getWidth(); + h = img.getHeight(); + register(img, 0); + frameIdx = 0; + lastFrameMs = 0; + } + private static void loadPng(InputStream in) throws Exception { - NativeImage img = NativeImage.read(in); + try { + NativeImage img = NativeImage.read(in); + w = img.getWidth(); + h = img.getHeight(); + register(img, 0); + frameIdx = 0; + lastFrameMs = 0; + return; + } catch (Exception e) { + LOG.warn("[justasplash] NativeImage.read failed, trying ImageIO fallback: {}", e.getMessage()); + } + java.awt.image.BufferedImage bi = ImageIO.read(in); + if (bi == null) throw new java.io.IOException("ImageIO.read returned null"); + NativeImage img = toNativeImage(bi); w = img.getWidth(); h = img.getHeight(); register(img, 0); @@ -79,6 +175,7 @@ public class ImageLoader { private static ResourceLocation registerImg(NativeImage img) { ResourceLocation loc = ResourceLocation.fromNamespaceAndPath("justasplash", "dynamic/splash_" + System.nanoTime()); Minecraft.getInstance().getTextureManager().register(loc, new DynamicTexture(img)); + LOG.info("[justasplash] registered dynamic texture {}", loc); return loc; } @@ -117,6 +214,16 @@ public class ImageLoader { return texLoc; } + public static void clearCache() { + texLoc = null; + cachedLoc = null; + w = 0; h = 0; + frames = List.of(); + frameIdx = 0; + lastFrameMs = 0; + LOG.info("[justasplash] cache cleared"); + } + public static int getWidth() { return w; } public static int getHeight() { return h; } } diff --git a/src/main/java/me/sashegdev/justasplash/client/ScreenSplashHandler.java b/src/main/java/me/sashegdev/justasplash/client/ScreenSplashHandler.java new file mode 100644 index 0000000..3117a92 --- /dev/null +++ b/src/main/java/me/sashegdev/justasplash/client/ScreenSplashHandler.java @@ -0,0 +1,16 @@ +package me.sashegdev.justasplash.client; + +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.ScreenEvent; + +@EventBusSubscriber(value = Dist.CLIENT, modid = "justasplash") +public class ScreenSplashHandler { + @SubscribeEvent + public static void onScreenRender(ScreenEvent.Render.Post e) { + if (SplashManager.isShowing()) { + SplashRenderer.render(e.getGuiGraphics()); + } + } +} diff --git a/src/main/java/me/sashegdev/justasplash/client/SplashManager.java b/src/main/java/me/sashegdev/justasplash/client/SplashManager.java index 35283ca..92abeb1 100644 --- a/src/main/java/me/sashegdev/justasplash/client/SplashManager.java +++ b/src/main/java/me/sashegdev/justasplash/client/SplashManager.java @@ -5,9 +5,12 @@ import net.minecraft.client.Minecraft; import net.minecraft.resources.ResourceLocation; import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @OnlyIn(Dist.CLIENT) public class SplashManager { + private static final Logger LOG = LoggerFactory.getLogger("justasplash"); private static long startMs = 0; private static boolean showing = false; private static double fadeSec = 3.0; @@ -16,22 +19,32 @@ public class SplashManager { public static void init() { } - public static void trigger() { + public static boolean trigger() { var mc = Minecraft.getInstance(); - if (mc.level == null) return; + LOG.info("[justasplash] trigger called level={}", mc.level); + boolean allowInMenu = mc.level == null && mc.screen != null; + if (mc.level == null && !allowInMenu) { + LOG.warn("[justasplash] trigger ignored - no level and no screen"); + return false; + } String img = JustASplashConfig.SPLASH_IMAGE.get(); String snd = JustASplashConfig.SPLASH_SOUND.get(); fadeSec = JustASplashConfig.FADE.get(); double vol = JustASplashConfig.VOLUME.get(); + LOG.info("[justasplash] config img={} snd={} fade={} vol={}", img, snd, fadeSec, vol); ResourceLocation imgLoc = ResourceLocation.tryParse(img); ResourceLocation sndLoc = snd == null || snd.isEmpty() ? null : ResourceLocation.tryParse(snd); - if (imgLoc != null) ImageLoader.load(imgLoc); + if (imgLoc != null) ImageLoader.preload(imgLoc); + else LOG.warn("[justasplash] imgLoc parse failed: {}", img); if (sndLoc != null) { audioSec = AudioPlayer.play(sndLoc, (float) vol); + LOG.info("[justasplash] audioSec={}", audioSec); } else audioSec = 0; if (fadeSec < 0) fadeSec = audioSec > 0 ? audioSec : 3.0; startMs = System.currentTimeMillis(); showing = true; + LOG.info("[justasplash] showing=true startMs={} fadeSec={}", startMs, fadeSec); + return true; } public static boolean isShowing() { @@ -40,6 +53,7 @@ public class SplashManager { if (elapsed >= fadeSec && elapsed >= audioSec) { showing = false; if (audioSec > 0 && elapsed >= audioSec) AudioPlayer.stop(); + LOG.info("[justasplash] hiding after elapsed={}", elapsed); return false; } return true; diff --git a/src/main/java/me/sashegdev/justasplash/client/SplashRenderer.java b/src/main/java/me/sashegdev/justasplash/client/SplashRenderer.java index 3d78a79..cc401f1 100644 --- a/src/main/java/me/sashegdev/justasplash/client/SplashRenderer.java +++ b/src/main/java/me/sashegdev/justasplash/client/SplashRenderer.java @@ -1,33 +1,39 @@ package me.sashegdev.justasplash.client; import com.mojang.blaze3d.systems.RenderSystem; -import net.minecraft.client.DeltaTracker; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.resources.ResourceLocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SplashRenderer { + private static final Logger LOG = LoggerFactory.getLogger("justasplash"); + private static long lastLog = 0; public static void render(GuiGraphics g) { - if (!SplashManager.isShowing()) return; + boolean showing = SplashManager.isShowing(); + if (!showing) return; var mc = Minecraft.getInstance(); int w = mc.getWindow().getGuiScaledWidth(); int h = mc.getWindow().getGuiScaledHeight(); float a = SplashManager.getFadeAlpha(); if (a <= 0.01f) return; + long now = System.currentTimeMillis(); + if (now - lastLog > 500) { + lastLog = now; + ResourceLocation tex = ImageLoader.getTexture(); + LOG.info("[justasplash] render w={} h={} a={} tex={} iw={} ih={}", w, h, a, tex, ImageLoader.getWidth(), ImageLoader.getHeight()); + } RenderSystem.enableBlend(); - RenderSystem.setShaderColor(1f, 1f, 1f, a); + RenderSystem.defaultBlendFunc(); + g.setColor(1f, 1f, 1f, a); ResourceLocation tex = ImageLoader.getTexture(); if (tex != null && ImageLoader.getWidth() > 0) { - int iw = ImageLoader.getWidth(); - int ih = ImageLoader.getHeight(); - float scale = Math.max((float) w / iw, (float) h / ih); - int dw = (int) (iw * scale); - int dh = (int) (ih * scale); - int x = (w - dw) / 2; - int y = (h - dh) / 2; - g.blit(tex, x, y, 0, 0, dw, dh, dw, dh); + g.blit(tex, 0, 0, 0, 0, w, h, w, h); + } else { + LOG.warn("[justasplash] render skip tex null or w=0 tex={} w={}", tex, ImageLoader.getWidth()); } - RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + g.setColor(1f, 1f, 1f, 1f); RenderSystem.disableBlend(); } } diff --git a/src/main/java/me/sashegdev/justasplash/client/TitleScreenHandler.java b/src/main/java/me/sashegdev/justasplash/client/TitleScreenHandler.java new file mode 100644 index 0000000..9b250ed --- /dev/null +++ b/src/main/java/me/sashegdev/justasplash/client/TitleScreenHandler.java @@ -0,0 +1,21 @@ +package me.sashegdev.justasplash.client; + +import net.minecraft.client.gui.screens.TitleScreen; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.ScreenEvent; + +@EventBusSubscriber(value = Dist.CLIENT, modid = "justasplash") +public class TitleScreenHandler { + private static boolean shown = false; + @SubscribeEvent + public static void onScreenOpening(ScreenEvent.Opening e) { + if (!shown && e.getNewScreen() instanceof TitleScreen ts) { + if (WelcomeScreen.shouldShow()) { + shown = true; + e.setNewScreen(new WelcomeScreen(ts)); + } + } + } +} diff --git a/src/main/java/me/sashegdev/justasplash/client/WelcomeScreen.java b/src/main/java/me/sashegdev/justasplash/client/WelcomeScreen.java new file mode 100644 index 0000000..55314e7 --- /dev/null +++ b/src/main/java/me/sashegdev/justasplash/client/WelcomeScreen.java @@ -0,0 +1,80 @@ +package me.sashegdev.justasplash.client; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import net.minecraft.client.Minecraft; +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.neoforged.fml.ModList; + +import java.io.FileReader; +import java.io.FileWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class WelcomeScreen extends Screen { + private final Screen parent; + private static final Gson GSON = new Gson(); + + public WelcomeScreen(Screen parent) { + super(Component.translatable("screen.justasplash.welcome.title")); + this.parent = parent; + } + + public static boolean shouldShow() { + try { + String cur = ModList.get().getModContainerById("justasplash").map(c -> c.getModInfo().getVersion().toString()).orElse("1.0.0"); + Path p = Paths.get("config/justasplash/first_launch.json"); + if (!Files.exists(p)) return true; + try (FileReader r = new FileReader(p.toFile())) { + JsonObject o = GSON.fromJson(r, JsonObject.class); + String seen = o.has("lastSeenVersion") ? o.get("lastSeenVersion").getAsString() : ""; + return !cur.equals(seen); + } + } catch (Exception e) { + return true; + } + } + + public static void markSeen() { + try { + String cur = ModList.get().getModContainerById("justasplash").map(c -> c.getModInfo().getVersion().toString()).orElse("1.0.0"); + Path p = Paths.get("config/justasplash/first_launch.json"); + Files.createDirectories(p.getParent()); + JsonObject o = new JsonObject(); + o.addProperty("lastSeenVersion", cur); + try (FileWriter w = new FileWriter(p.toFile())) { GSON.toJson(o, w); } + } catch (Exception ignored) {} + } + + @Override + protected void init() { + int cx = width / 2; + int y = height / 2 - 40; + addRenderableWidget(Button.builder(Component.translatable("screen.justasplash.welcome.test"), b -> { + SplashManager.trigger(); + }).bounds(cx - 105, y + 60, 100, 20).build()); + addRenderableWidget(Button.builder(Component.translatable("screen.justasplash.welcome.ok"), b -> { + markSeen(); + Minecraft.getInstance().setScreen(parent); + }).bounds(cx + 5, y + 60, 100, 20).build()); + } + + @Override + public void render(GuiGraphics g, int mx, int my, float delta) { + super.render(g, mx, my, delta); + int cx = width / 2; + int y = height / 2 - 40; + g.drawCenteredString(font, title, cx, y - 30, 0xFFFFFF); + g.drawCenteredString(font, Component.translatable("screen.justasplash.welcome.line1"), cx, y, 0xDDDDDD); + g.drawCenteredString(font, Component.translatable("screen.justasplash.welcome.line2"), cx, y + 12, 0xDDDDDD); + g.drawCenteredString(font, Component.translatable("screen.justasplash.welcome.line3"), cx, y + 24, 0xDDDDDD); + g.drawCenteredString(font, Component.translatable("screen.justasplash.welcome.line4"), cx, y + 36, 0xDDDDDD); + } + + @Override + public boolean shouldCloseOnEsc() { return false; } +} diff --git a/src/main/java/me/sashegdev/justasplash/config/JustASplashConfig.java b/src/main/java/me/sashegdev/justasplash/config/JustASplashConfig.java index 7def013..83ada8f 100644 --- a/src/main/java/me/sashegdev/justasplash/config/JustASplashConfig.java +++ b/src/main/java/me/sashegdev/justasplash/config/JustASplashConfig.java @@ -12,12 +12,12 @@ public class JustASplashConfig { static { ModConfigSpec.Builder b = new ModConfigSpec.Builder(); b.push("splash"); - SPLASH_IMAGE = b.comment("ResourceLocation png|gif in assets/justasplash/ - e.g. justasplash:textures/gui/splashes/splash.png") - .define("splashImage", "justasplash:textures/gui/splashes/splash.png"); - SPLASH_SOUND = b.comment("ResourceLocation ogg|mp3 - e.g. justasplash:sounds/splash.ogg, empty to disable") + SPLASH_IMAGE = b.comment("ResourceLocation png|jpg|gif in assets/justasplash/ - e.g. justasplash:textures/gui/splashes/splash.png") + .define("splashImage", "justasplash:textures/gui/splashes/splash.jpg"); + SPLASH_SOUND = b.comment("ResourceLocation ogg/wav - e.g. justasplash:sounds/splash.ogg, empty to disable") .define("splashSound", "justasplash:sounds/splash.ogg"); - FADE = b.comment("Fade duration seconds 100%->0%, -1 = use audio duration, default 3.0") - .defineInRange("fade", 3.0, -1.0, 60.0); + FADE = b.comment("Fade duration seconds 100%->0%, -1 = use audio duration, default 2.5") + .defineInRange("fade", 2.5, -1.0, 60.0); VOLUME = b.comment("Sound volume 0.0-1.0") .defineInRange("volume", 1.0, 0.0, 1.0); b.pop(); diff --git a/src/main/java/me/sashegdev/justasplash/mixin/LibraryMixin.java b/src/main/java/me/sashegdev/justasplash/mixin/LibraryMixin.java new file mode 100644 index 0000000..083cdbf --- /dev/null +++ b/src/main/java/me/sashegdev/justasplash/mixin/LibraryMixin.java @@ -0,0 +1,30 @@ +package me.sashegdev.justasplash.mixin; + +import com.mojang.blaze3d.audio.Library; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; + +@Mixin(Library.class) +public class LibraryMixin { + @Inject(method = "init", at = @At("TAIL")) + private void justasplash$unlimitedChannels(String deviceName, boolean hrtf, CallbackInfo ci) { + try { + Class poolClass = Class.forName("com.mojang.blaze3d.audio.Library$CountingChannelPool"); + Constructor ctor = poolClass.getDeclaredConstructor(int.class); + ctor.setAccessible(true); + Object pool1 = ctor.newInstance(128); + Object pool2 = ctor.newInstance(128); + Field streaming = Library.class.getDeclaredField("streamingChannels"); + streaming.setAccessible(true); + streaming.set(this, pool1); + Field staticCh = Library.class.getDeclaredField("staticChannels"); + staticCh.setAccessible(true); + staticCh.set(this, pool2); + } catch (Exception ignored) {} + } +} diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml index 54b81ad..412dc4b 100644 --- a/src/main/resources/META-INF/neoforge.mods.toml +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -23,3 +23,6 @@ description="Hotkey Z -> fullscreen png/gif + ogg/mp3 splash, 100% -> 0% fade, c versionRange="${minecraft_version_range}" ordering="NONE" side="CLIENT" + +[[mixins]] +config = "justasplash.mixins.json" diff --git a/src/main/resources/assets/justasplash/lang/en_us.json b/src/main/resources/assets/justasplash/lang/en_us.json new file mode 100644 index 0000000..2c2ec7d --- /dev/null +++ b/src/main/resources/assets/justasplash/lang/en_us.json @@ -0,0 +1,32 @@ +{ + "key.justasplash.splash": "Trigger Splash", + "key.categories.justasplash": "Just A Splash", + "config.justasplash.title": "Just A Splash Config", + "config.justasplash.splashImage": "Splash Image", + "config.justasplash.splashImage.tooltip": "ResourceLocation like justasplash:textures/gui/splashes/splash.jpg or custom file in config/justasplash/assets/", + "config.justasplash.splashSound": "Splash Sound", + "config.justasplash.splashSound.tooltip": "Sound event like justasplash:splash or file path justasplash:sounds/splash.ogg, empty to disable", + "config.justasplash.fade": "Fade Duration", + "config.justasplash.fade.tooltip": "Seconds 100% -> 0%, -1 = audio duration", + "config.justasplash.volume": "Volume", + "config.justasplash.test": "Test Splash", + "config.justasplash.save": "Save", + "config.justasplash.cancel": "Cancel", + "screen.justasplash.welcome.title": "Welcome to Just A Splash!", + "screen.justasplash.welcome.line1": "Press Z for fullscreen splash", + "screen.justasplash.welcome.line2": "Fullscreen png/jpg/gif + ogg/mp3 overlay with fade", + "screen.justasplash.welcome.line3": "Does not block movement or mouse", + "screen.justasplash.welcome.line4": "Change via resource pack or config screen", + "screen.justasplash.welcome.test": "Test Splash", + "screen.justasplash.welcome.ok": "OK", + "toast.justasplash.ok": "Splash triggered!", + "toast.justasplash.error": "Failed to load splash image", + "command.justasplash.reload.success": "JustASplash reloaded", + "config.justasplash.hint1": "Drop png/jpg/gif into config/justasplash/assets/ and pick via \u25BE", + "config.justasplash.hint2": "or enter path like justasplash:textures/... (512 chars)", + "config.justasplash.preview": "Preview", + "config.justasplash.noPreview": "No preview", + "config.justasplash.fileDropdown": "File \u25BE", + "config.justasplash.nextImage": "Next image", + "config.justasplash.nextSound": "Next sound" +} diff --git a/src/main/resources/assets/justasplash/lang/ru_ru.json b/src/main/resources/assets/justasplash/lang/ru_ru.json new file mode 100644 index 0000000..b914b28 --- /dev/null +++ b/src/main/resources/assets/justasplash/lang/ru_ru.json @@ -0,0 +1,32 @@ +{ + "key.justasplash.splash": "Показать сплеш", + "key.categories.justasplash": "Just A Splash", + "config.justasplash.title": "Настройки Just A Splash", + "config.justasplash.splashImage": "Картинка сплеша", + "config.justasplash.splashImage.tooltip": "ResourceLocation вроде justasplash:textures/gui/splashes/splash.jpg или файл в config/justasplash/assets/", + "config.justasplash.splashSound": "Звук сплеша", + "config.justasplash.splashSound.tooltip": "Событие justasplash:splash или путь justasplash:sounds/splash.ogg, пусто = без звука", + "config.justasplash.fade": "Длительность затухания", + "config.justasplash.fade.tooltip": "Секунды 100% -> 0%, -1 = длительность аудио", + "config.justasplash.volume": "Громкость", + "config.justasplash.test": "Протестить сплеш", + "config.justasplash.save": "Сохранить", + "config.justasplash.cancel": "Отмена", + "screen.justasplash.welcome.title": "Добро пожаловать в Just A Splash!", + "screen.justasplash.welcome.line1": "Нажми Z для фуллскрин сплеша", + "screen.justasplash.welcome.line2": "Фуллскрин png/jpg/gif + ogg/mp3 с затуханием", + "screen.justasplash.welcome.line3": "Не блокирует ходьбу и мышь", + "screen.justasplash.welcome.line4": "Меняй через ресурспак или экран настроек", + "screen.justasplash.welcome.test": "Протестить сплеш", + "screen.justasplash.welcome.ok": "Хорошо", + "toast.justasplash.ok": "Сплеш запущен!", + "toast.justasplash.error": "Не удалось загрузить картинку", + "command.justasplash.reload.success": "JustASplash перезагружен", + "config.justasplash.hint1": "Кидай png/jpg/gif в config/justasplash/assets/ и выбери \u25BE", + "config.justasplash.hint2": "или укажи путь вроде justasplash:textures/... (512 симв.)", + "config.justasplash.preview": "Превью", + "config.justasplash.noPreview": "Нет превью", + "config.justasplash.fileDropdown": "Файл \u25BE", + "config.justasplash.nextImage": "следующая картинка", + "config.justasplash.nextSound": "следующий звук" +} diff --git a/src/main/resources/assets/justasplash/sounds.json b/src/main/resources/assets/justasplash/sounds.json new file mode 100644 index 0000000..f122b5b --- /dev/null +++ b/src/main/resources/assets/justasplash/sounds.json @@ -0,0 +1,5 @@ +{ + "splash": { + "sounds": [{ "name": "justasplash:splash", "stream": true }] + } +} diff --git a/src/main/resources/assets/justasplash/sounds/splash.ogg b/src/main/resources/assets/justasplash/sounds/splash.ogg new file mode 100755 index 0000000000000000000000000000000000000000..849e5ab8b59ee57c645a70045a3b0688e7fe2949 GIT binary patch literal 28166 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 VL|svKWSWbnIisEU7m{8!|JgZrlym?9 literal 0 HcmV?d00001 diff --git a/src/main/resources/justasplash.mixins.json b/src/main/resources/justasplash.mixins.json new file mode 100644 index 0000000..a189bab --- /dev/null +++ b/src/main/resources/justasplash.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "me.sashegdev.justasplash.mixin", + "refmap": "justasplash.refmap.json", + "compatibilityLevel": "JAVA_21", + "client": [ + "LibraryMixin" + ], + "injectors": { + "defaultRequire": 1 + }, + "minVersion": "0.8" +}