feat: rebuild JustASplash NeoForge 1.21.1 - hotkey Z splash, config screen, welcome, commands, vorbisspi ogg, jarJar
This commit is contained in:
@@ -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) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.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() {
|
||||
|
||||
@@ -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")
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Frame> 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<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 {
|
||||
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; }
|
||||
}
|
||||
|
||||
@@ -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.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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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();
|
||||
|
||||
@@ -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}"
|
||||
ordering="NONE"
|
||||
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 }]
|
||||
}
|
||||
}
|
||||
BIN
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"
|
||||
}
|
||||
Reference in New Issue
Block a user