feat: rebuild JustASplash NeoForge 1.21.1 - hotkey Z splash, config screen, welcome, commands, vorbisspi ogg, jarJar

This commit is contained in:
SashegDev
2026-08-27 00:05:11 +03:00
parent 2b4cd71f3f
commit ad0a5d57cb
23 changed files with 897 additions and 35 deletions
+2 -4
View File
@@ -34,10 +34,8 @@ configurations {
dependencies { dependencies {
implementation "net.neoforged:neoforge:${neo_version}" implementation "net.neoforged:neoforge:${neo_version}"
implementation "javazoom:jlayer:1.0.1" localRuntime "com.googlecode.soundlibs:vorbisspi:1.0.3.3"
localRuntime "com.googlecode.soundlibs:mp3spi:1.9.5.4" jarJar "com.googlecode.soundlibs:vorbisspi:1.0.3.3"
localRuntime "com.googlecode.soundlibs:tritonus-share:0.3.7.4"
localRuntime "com.googlecode.soundlibs:jorbis:0.0.17.4"
} }
tasks.named('processResources', Copy).configure { tasks.named('processResources', Copy).configure {
@@ -1,15 +1,55 @@
package me.sashegdev.justasplash; package me.sashegdev.justasplash;
import me.sashegdev.justasplash.client.ConfigScreen;
import me.sashegdev.justasplash.config.JustASplashConfig; import me.sashegdev.justasplash.config.JustASplashConfig;
import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModContainer; import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod; import net.neoforged.fml.common.Mod;
import net.neoforged.fml.config.ModConfig; import net.neoforged.fml.config.ModConfig;
import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
@Mod(value = "justasplash", dist = Dist.CLIENT) @Mod(value = "justasplash", dist = Dist.CLIENT)
public class JustASplash { public class JustASplash {
public JustASplash(IEventBus modBus, ModContainer container) { public JustASplash(IEventBus modBus, ModContainer container) {
container.registerConfig(ModConfig.Type.CLIENT, JustASplashConfig.SPEC); 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) {}
} }
} }
@@ -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<CommandSourceStack> 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;
})));
}
}
@@ -3,7 +3,6 @@ package me.sashegdev.justasplash.client;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.client.resources.sounds.SimpleSoundInstance;
import net.minecraft.client.resources.sounds.SoundInstance; import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.sounds.SoundManager;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource; import net.minecraft.sounds.SoundSource;
@@ -18,18 +17,35 @@ import java.io.InputStream;
public class AudioPlayer { public class AudioPlayer {
private static SoundInstance current; private static SoundInstance current;
public static double play(ResourceLocation loc, float vol) { public static double play(ResourceLocation fileLoc, float vol) {
try { try {
var mc = Minecraft.getInstance(); var mc = Minecraft.getInstance();
var rm = mc.getResourceManager(); var rm = mc.getResourceManager();
var res = rm.getResource(loc).orElse(null); var res = rm.getResource(fileLoc).orElse(null);
double dur = 3.0; double dur = 3.0;
if (res != null) { if (res != null) {
try (InputStream in = res.open()) { 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) {} } 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); 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); mc.getSoundManager().play(current);
return dur; 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) { private static double probeDuration(InputStream in, String path) {
try { try {
in.mark(Integer.MAX_VALUE); in.mark(Integer.MAX_VALUE);
@@ -48,7 +105,7 @@ public class AudioPlayer {
double sec = frames / rate; double sec = frames / rate;
if (sec > 0 && sec < 300) return sec; if (sec > 0 && sec < 300) return sec;
} catch (Exception ignored) {} } catch (Exception ignored) {}
return path.endsWith(".mp3") ? 5.0 : 3.0; return 3.0;
} }
public static void stop() { public static void stop() {
@@ -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));
}
}
@@ -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<InputStream> 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<InputStream> 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<String> getNamespaces(PackType type) {
if (type == PackType.CLIENT_RESOURCES) {
Set<String> s = new HashSet<>();
s.add("justasplash");
return s;
}
return Set.of();
}
@Override
public <T> T getMetadataSection(MetadataSectionSerializer<T> 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() {}
}
@@ -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<String> imgFiles = List.of();
private List<String> 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<String> 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); }
}
@@ -7,9 +7,11 @@ import net.neoforged.neoforge.client.event.ClientTickEvent;
@EventBusSubscriber(value = Dist.CLIENT, modid = "justasplash") @EventBusSubscriber(value = Dist.CLIENT, modid = "justasplash")
public class HotkeyTick { public class HotkeyTick {
private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger("justasplash");
@SubscribeEvent @SubscribeEvent
public static void onTick(ClientTickEvent.Post e) { public static void onTick(ClientTickEvent.Post e) {
while (Hotkey.SPLASH.consumeClick()) { while (Hotkey.SPLASH.consumeClick()) {
LOG.info("[justasplash] hotkey Z pressed");
SplashManager.trigger(); SplashManager.trigger();
} }
} }
@@ -4,6 +4,8 @@ import com.mojang.blaze3d.platform.NativeImage;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.ImageReader; import javax.imageio.ImageReader;
@@ -13,7 +15,9 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
public class ImageLoader { public class ImageLoader {
private static final Logger LOG = LoggerFactory.getLogger("justasplash");
private static ResourceLocation texLoc; private static ResourceLocation texLoc;
private static ResourceLocation cachedLoc;
private static int w, h; private static int w, h;
private static List<Frame> frames = List.of(); private static List<Frame> frames = List.of();
private static int frameIdx = 0; private static int frameIdx = 0;
@@ -21,22 +25,114 @@ public class ImageLoader {
record Frame(ResourceLocation loc, int delayMs) {} 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) { public static void load(ResourceLocation loc) {
LOG.info("[justasplash] ImageLoader.load {}", loc);
try { try {
byte[] data = null;
String p = loc.getPath().toLowerCase();
var rm = Minecraft.getInstance().getResourceManager(); var rm = Minecraft.getInstance().getResourceManager();
var res = rm.getResource(loc).orElse(null); var res = rm.getResource(loc).orElse(null);
if (res == null) return; if (res != null) {
try (InputStream in = res.open()) { LOG.info("[justasplash] resource found: {}", loc);
if (loc.getPath().endsWith(".gif")) loadGif(in); try (InputStream in = res.open()) { data = in.readAllBytes(); }
else loadPng(in); } 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) { } catch (Exception e) {
LOG.error("[justasplash] load failed {}", loc, e);
e.printStackTrace(); 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<String> 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<String> 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 { 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(); w = img.getWidth();
h = img.getHeight(); h = img.getHeight();
register(img, 0); register(img, 0);
@@ -79,6 +175,7 @@ public class ImageLoader {
private static ResourceLocation registerImg(NativeImage img) { private static ResourceLocation registerImg(NativeImage img) {
ResourceLocation loc = ResourceLocation.fromNamespaceAndPath("justasplash", "dynamic/splash_" + System.nanoTime()); ResourceLocation loc = ResourceLocation.fromNamespaceAndPath("justasplash", "dynamic/splash_" + System.nanoTime());
Minecraft.getInstance().getTextureManager().register(loc, new DynamicTexture(img)); Minecraft.getInstance().getTextureManager().register(loc, new DynamicTexture(img));
LOG.info("[justasplash] registered dynamic texture {}", loc);
return loc; return loc;
} }
@@ -117,6 +214,16 @@ public class ImageLoader {
return texLoc; 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 getWidth() { return w; }
public static int getHeight() { return h; } public static int getHeight() { return h; }
} }
@@ -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());
}
}
}
@@ -5,9 +5,12 @@ import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.api.distmarker.OnlyIn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class SplashManager { public class SplashManager {
private static final Logger LOG = LoggerFactory.getLogger("justasplash");
private static long startMs = 0; private static long startMs = 0;
private static boolean showing = false; private static boolean showing = false;
private static double fadeSec = 3.0; private static double fadeSec = 3.0;
@@ -16,22 +19,32 @@ public class SplashManager {
public static void init() { public static void init() {
} }
public static void trigger() { public static boolean trigger() {
var mc = Minecraft.getInstance(); 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 img = JustASplashConfig.SPLASH_IMAGE.get();
String snd = JustASplashConfig.SPLASH_SOUND.get(); String snd = JustASplashConfig.SPLASH_SOUND.get();
fadeSec = JustASplashConfig.FADE.get(); fadeSec = JustASplashConfig.FADE.get();
double vol = JustASplashConfig.VOLUME.get(); double vol = JustASplashConfig.VOLUME.get();
LOG.info("[justasplash] config img={} snd={} fade={} vol={}", img, snd, fadeSec, vol);
ResourceLocation imgLoc = ResourceLocation.tryParse(img); ResourceLocation imgLoc = ResourceLocation.tryParse(img);
ResourceLocation sndLoc = snd == null || snd.isEmpty() ? null : ResourceLocation.tryParse(snd); 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) { if (sndLoc != null) {
audioSec = AudioPlayer.play(sndLoc, (float) vol); audioSec = AudioPlayer.play(sndLoc, (float) vol);
LOG.info("[justasplash] audioSec={}", audioSec);
} else audioSec = 0; } else audioSec = 0;
if (fadeSec < 0) fadeSec = audioSec > 0 ? audioSec : 3.0; if (fadeSec < 0) fadeSec = audioSec > 0 ? audioSec : 3.0;
startMs = System.currentTimeMillis(); startMs = System.currentTimeMillis();
showing = true; showing = true;
LOG.info("[justasplash] showing=true startMs={} fadeSec={}", startMs, fadeSec);
return true;
} }
public static boolean isShowing() { public static boolean isShowing() {
@@ -40,6 +53,7 @@ public class SplashManager {
if (elapsed >= fadeSec && elapsed >= audioSec) { if (elapsed >= fadeSec && elapsed >= audioSec) {
showing = false; showing = false;
if (audioSec > 0 && elapsed >= audioSec) AudioPlayer.stop(); if (audioSec > 0 && elapsed >= audioSec) AudioPlayer.stop();
LOG.info("[justasplash] hiding after elapsed={}", elapsed);
return false; return false;
} }
return true; return true;
@@ -1,33 +1,39 @@
package me.sashegdev.justasplash.client; package me.sashegdev.justasplash.client;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SplashRenderer { public class SplashRenderer {
private static final Logger LOG = LoggerFactory.getLogger("justasplash");
private static long lastLog = 0;
public static void render(GuiGraphics g) { public static void render(GuiGraphics g) {
if (!SplashManager.isShowing()) return; boolean showing = SplashManager.isShowing();
if (!showing) return;
var mc = Minecraft.getInstance(); var mc = Minecraft.getInstance();
int w = mc.getWindow().getGuiScaledWidth(); int w = mc.getWindow().getGuiScaledWidth();
int h = mc.getWindow().getGuiScaledHeight(); int h = mc.getWindow().getGuiScaledHeight();
float a = SplashManager.getFadeAlpha(); float a = SplashManager.getFadeAlpha();
if (a <= 0.01f) return; 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.enableBlend();
RenderSystem.setShaderColor(1f, 1f, 1f, a); RenderSystem.defaultBlendFunc();
g.setColor(1f, 1f, 1f, a);
ResourceLocation tex = ImageLoader.getTexture(); ResourceLocation tex = ImageLoader.getTexture();
if (tex != null && ImageLoader.getWidth() > 0) { if (tex != null && ImageLoader.getWidth() > 0) {
int iw = ImageLoader.getWidth(); g.blit(tex, 0, 0, 0, 0, w, h, w, h);
int ih = ImageLoader.getHeight(); } else {
float scale = Math.max((float) w / iw, (float) h / ih); LOG.warn("[justasplash] render skip tex null or w=0 tex={} w={}", tex, ImageLoader.getWidth());
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);
} }
RenderSystem.setShaderColor(1f, 1f, 1f, 1f); g.setColor(1f, 1f, 1f, 1f);
RenderSystem.disableBlend(); RenderSystem.disableBlend();
} }
} }
@@ -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));
}
}
}
}
@@ -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; }
}
@@ -12,12 +12,12 @@ public class JustASplashConfig {
static { static {
ModConfigSpec.Builder b = new ModConfigSpec.Builder(); ModConfigSpec.Builder b = new ModConfigSpec.Builder();
b.push("splash"); b.push("splash");
SPLASH_IMAGE = b.comment("ResourceLocation png|gif in assets/justasplash/ - e.g. justasplash:textures/gui/splashes/splash.png") 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.png"); .define("splashImage", "justasplash:textures/gui/splashes/splash.jpg");
SPLASH_SOUND = b.comment("ResourceLocation ogg|mp3 - e.g. justasplash:sounds/splash.ogg, empty to disable") SPLASH_SOUND = b.comment("ResourceLocation ogg/wav - e.g. justasplash:sounds/splash.ogg, empty to disable")
.define("splashSound", "justasplash:sounds/splash.ogg"); .define("splashSound", "justasplash:sounds/splash.ogg");
FADE = b.comment("Fade duration seconds 100%->0%, -1 = use audio duration, default 3.0") FADE = b.comment("Fade duration seconds 100%->0%, -1 = use audio duration, default 2.5")
.defineInRange("fade", 3.0, -1.0, 60.0); .defineInRange("fade", 2.5, -1.0, 60.0);
VOLUME = b.comment("Sound volume 0.0-1.0") VOLUME = b.comment("Sound volume 0.0-1.0")
.defineInRange("volume", 1.0, 0.0, 1.0); .defineInRange("volume", 1.0, 0.0, 1.0);
b.pop(); b.pop();
@@ -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) {}
}
}
@@ -23,3 +23,6 @@ description="Hotkey Z -> fullscreen png/gif + ogg/mp3 splash, 100% -> 0% fade, c
versionRange="${minecraft_version_range}" versionRange="${minecraft_version_range}"
ordering="NONE" ordering="NONE"
side="CLIENT" side="CLIENT"
[[mixins]]
config = "justasplash.mixins.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"
}
@@ -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": "следующий звук"
}
@@ -0,0 +1,5 @@
{
"splash": {
"sounds": [{ "name": "justasplash:splash", "stream": true }]
}
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

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