好多bug

This commit is contained in:
Coloryr committed 2026-11-02 21:37:54 +08:00
1 parent 590af206d3
commit 64e08b4bd3
42 files changed
+1467 -24

No files matched your search

@@ -208,5 +208,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
RenderSystem.recordRenderCall(AllMusicCore::renderInit);
ClientLifecycleEvents.CLIENT_STOPPING.register((a) -> AllMusicCore.stop());
// 客户端指令:/music_client help | info | test | imagesize <大小>
CommandMusicClientFabric.register();
}
}
@@ -0,0 +1,30 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.fabric.api.client.command.v1.ClientCommandManager;
/**
* Fabric(1.16.5)的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public final class CommandMusicClientFabric {
private CommandMusicClientFabric() {
}
public static void register() {
ClientCommandManager.DISPATCHER.register(ClientCommandManager.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(ClientCommandManager.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args"))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -138,5 +138,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
RenderSystem.recordRenderCall(AllMusicCore::renderInit);
ClientLifecycleEvents.CLIENT_STOPPING.register((a) -> AllMusicCore.stop());
// 客户端指令:/music_client help | info | test | imagesize <大小>
CommandMusicClientFabric.register();
}
}
@@ -0,0 +1,32 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.minecraft.commands.Commands;
/**
* Fabric(1.19.3+)的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public final class CommandMusicClientFabric {
private CommandMusicClientFabric() {
}
public static void register() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args")))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -140,5 +140,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
RenderSystem.recordRenderCall(AllMusicCore::renderInit);
ClientLifecycleEvents.CLIENT_STOPPING.register((a) -> AllMusicCore.stop());
// 客户端指令:/music_client help | info | test | imagesize <大小>
CommandMusicClientFabric.register();
}
}
@@ -0,0 +1,32 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.minecraft.commands.Commands;
/**
* Fabric(1.19.3+)的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public final class CommandMusicClientFabric {
private CommandMusicClientFabric() {
}
public static void register() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args")))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -135,5 +135,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
AllMusicCore.init(FabricLoader.getInstance().getConfigDir(), this);
ClientLifecycleEvents.CLIENT_STOPPING.register((a) -> AllMusicCore.stop());
// 客户端指令:/music_client help | info | test | imagesize <大小>
CommandMusicClientFabric.register();
}
}
@@ -0,0 +1,32 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.minecraft.commands.Commands;
/**
* Fabric(1.19.3+)的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public final class CommandMusicClientFabric {
private CommandMusicClientFabric() {
}
public static void register() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args")))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -140,5 +140,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
RenderSystem.recordRenderCall(AllMusicCore::renderInit);
ClientLifecycleEvents.CLIENT_STOPPING.register((a) -> AllMusicCore.stop());
// 客户端指令:/music_client help | info | test | imagesize <大小>
CommandMusicClientFabric.register();
}
}
@@ -0,0 +1,32 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.minecraft.commands.Commands;
/**
* Fabric(1.19.3+)的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public final class CommandMusicClientFabric {
private CommandMusicClientFabric() {
}
public static void register() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args")))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -140,5 +140,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
RenderSystem.recordRenderCall(AllMusicCore::renderInit);
ClientLifecycleEvents.CLIENT_STOPPING.register((a) -> AllMusicCore.stop());
// 客户端指令:/music_client help | info | test | imagesize <大小>
CommandMusicClientFabric.register();
}
}
@@ -0,0 +1,32 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.minecraft.commands.Commands;
/**
* Fabric(1.19.3+)的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public final class CommandMusicClientFabric {
private CommandMusicClientFabric() {
}
public static void register() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args")))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -133,5 +133,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
AllMusicCore.init(FabricLoader.getInstance().getConfigDir(), this);
ClientLifecycleEvents.CLIENT_STOPPING.register((a) -> AllMusicCore.stop());
// 客户端指令:/music_client help | info | test | imagesize <大小>
CommandMusicClientFabric.register();
}
}
@@ -0,0 +1,32 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.minecraft.commands.Commands;
/**
* Fabric(1.19.3+)的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public final class CommandMusicClientFabric {
private CommandMusicClientFabric() {
}
public static void register() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args")))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -133,5 +133,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
AllMusicCore.init(FabricLoader.getInstance().getConfigDir(), this);
ClientLifecycleEvents.CLIENT_STOPPING.register((a) -> AllMusicCore.stop());
// 客户端指令:/music_client help | info | test | imagesize <大小>
CommandMusicClientFabric.register();
}
}
@@ -0,0 +1,32 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.minecraft.commands.Commands;
/**
* Fabric(1.19.3+)的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public final class CommandMusicClientFabric {
private CommandMusicClientFabric() {
}
public static void register() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args")))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -135,5 +135,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
AllMusicCore.init(FabricLoader.getInstance().getConfigDir(), this);
ClientLifecycleEvents.CLIENT_STOPPING.register((a) -> AllMusicCore.stop());
// 客户端指令:/music_client help | info | test | imagesize <大小>
CommandMusicClientFabric.register();
}
}
@@ -0,0 +1,32 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.minecraft.commands.Commands;
/**
* Fabric(1.19.3+)的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public final class CommandMusicClientFabric {
private CommandMusicClientFabric() {
}
public static void register() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args")))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -134,5 +134,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
AllMusicCore.init(FabricLoader.getInstance().getConfigDir(), this);
ClientLifecycleEvents.CLIENT_STOPPING.register((a) -> AllMusicCore.stop());
// 客户端指令:/music_client help | info | test | imagesize <大小>
CommandMusicClientFabric.register();
}
}
@@ -0,0 +1,32 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.minecraft.commands.Commands;
/**
* Fabric(1.19.3+)的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public final class CommandMusicClientFabric {
private CommandMusicClientFabric() {
}
public static void register() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args")))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -131,5 +131,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
AllMusicCore.init(FabricLoader.getInstance().getConfigDir(), this);
ClientLifecycleEvents.CLIENT_STOPPING.register((a) -> AllMusicCore.stop());
// 客户端指令:/music_client help | info | test | imagesize <大小>
CommandMusicClientFabric.register();
}
}
@@ -0,0 +1,32 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.minecraft.commands.Commands;
/**
* Fabric(1.19.3+)的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public final class CommandMusicClientFabric {
private CommandMusicClientFabric() {
}
public static void register() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args")))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -14,6 +14,7 @@ import net.minecraft.network.NetworkManager;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.text.TextComponentString;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.client.event.sound.PlaySoundEvent;
import net.minecraftforge.common.MinecraftForge;
@@ -61,6 +62,9 @@ public class AllMusicClient implements AllMusicBridge {
}
AllMusicCore.init(evt.getModConfigurationDirectory().toPath(), this);
MinecraftForge.EVENT_BUS.register(this);
// 客户端指令:/music_client help | info | test | imagesize <大小>
ClientCommandHandler.instance.registerCommand(new CommandMusicClientForge());
try {
Class<?> server = Class.forName("com.coloryr.allmusic.server.AllMusicServer");
Field m = server.getField("channel");
@@ -0,0 +1,60 @@
package com.coloryr.allmusic.client;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.math.BlockPos;
import java.util.Collections;
import java.util.List;
/**
* 1.12.2 的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public class CommandMusicClientForge extends CommandBase {
@Override
public String getName() {
return CommandMusicClient.NAME;
}
@Override
public String getUsage(ICommandSender sender) {
return CommandMusicClient.usage();
}
@Override
public List<String> getAliases() {
return Collections.emptyList();
}
@Override
public int getRequiredPermissionLevel() {
return 0;
}
@Override
public boolean checkPermission(MinecraftServer server, ICommandSender sender) {
return true;
}
@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException {
CommandMusicClient.run(args);
}
@Override
public List<String> getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, BlockPos targetPos) {
if (args.length <= 1) {
return getListOfStringsMatchingLastWord(args, CommandMusicClient.SUB_COMMANDS);
}
return Collections.emptyList();
}
@Override
public boolean isUsernameIndex(String[] args, int index) {
return false;
}
}
@@ -14,6 +14,7 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.sounds.SoundSource;
import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
import net.minecraftforge.client.event.RegisterClientCommandsEvent;
import net.minecraftforge.client.event.RenderGuiOverlayEvent;
import net.minecraftforge.client.event.sound.SoundEngineLoadEvent;
import net.minecraftforge.client.event.sound.SoundEvent;
@@ -135,6 +136,11 @@ public class AllMusicClient implements AllMusicBridge {
AllMusicCore.tick();
}
@SubscribeEvent
public void onRegisterCommands(RegisterClientCommandsEvent event) {
CommandMusicClientForge.register(event.getDispatcher());
}
@Override
public TextFrameBuffer makeTextRender(String name) {
return new CoreRenderTarget(name);
@@ -0,0 +1,32 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
/**
* Forge(1.16.5+)的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public final class CommandMusicClientForge {
private CommandMusicClientForge() {
}
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args"))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -22,6 +22,7 @@ import net.minecraft.network.NetworkManager;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.IChatComponent;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.client.event.sound.PlaySoundEvent17;
import net.minecraftforge.common.MinecraftForge;
@@ -77,6 +78,9 @@ public class AllMusic implements AllMusicBridge {
IntBuffer source = findOpenAlSource();
AllMusicCore.init(new File("config").toPath(), this, source);
AllMusicCore.renderInit();
// 客户端指令:/music_client help | info | test | imagesize <大小>
ClientCommandHandler.instance.registerCommand(new CommandMusicClientForge());
}
/**
@@ -0,0 +1,87 @@
package com.coloryr.allmusic.client;
import net.minecraft.command.CommandBase;
import net.minecraft.command.ICommand;
import net.minecraft.command.ICommandSender;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.EnumChatFormatting;
import java.util.Collections;
import java.util.List;
/**
* 1.7.10 的 /music_client 指令壳
* <p>
* 只负责把 {@link CommandMusicClient#execute(String[])} 的结果发到聊天栏,
* 具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
public class CommandMusicClientForge implements ICommand {
@Override
public String getCommandName() {
return CommandMusicClient.NAME;
}
@Override
public String getCommandUsage(ICommandSender sender) {
return CommandMusicClient.usage();
}
@Override
public List getCommandAliases() {
return Collections.emptyList();
}
@Override
public void processCommand(ICommandSender sender, String[] args) {
CommandMusicClient.Result result = CommandMusicClient.execute(args);
if (result.title != null) {
send(sender, result.title, EnumChatFormatting.YELLOW);
}
for (String line : result.lines) {
send(sender, line, color(CommandMusicClient.styleOf(line)));
}
}
private static EnumChatFormatting color(int style) {
switch (style) {
case CommandMusicClient.STYLE_OK:
return EnumChatFormatting.GREEN;
case CommandMusicClient.STYLE_ERROR:
return EnumChatFormatting.RED;
default:
return null;
}
}
private static void send(ICommandSender sender, String message, EnumChatFormatting color) {
ChatComponentText text = new ChatComponentText("[AllMusic Client] " + message);
if (color != null) {
text.getChatStyle().setColor(color);
}
sender.addChatMessage(text);
}
@Override
public boolean canCommandSenderUseCommand(ICommandSender sender) {
return true;
}
@Override
public List addTabCompletionOptions(ICommandSender sender, String[] args) {
if (args.length == 1) {
return CommandBase.getListOfStringsMatchingLastWord(args, CommandMusicClient.SUB_COMMANDS);
}
return null;
}
@Override
public boolean isUsernameIndex(String[] args, int index) {
return false;
}
@Override
public int compareTo(Object o) {
return 0;
}
}
@@ -38,6 +38,12 @@ public class PicRender extends PictureFrameBuffer {
}
}
@Override
public void dispose() {
sourceTexture.deleteGlTexture();
rotateTexture.deleteGlTexture();
}
@Override
public void draw(boolean rotate, int size, float x, float y, int ang, HudPosType dir, float alpha) {
Point2f point = AllMusicHud.getPos(size, size, x, y, dir);
@@ -0,0 +1,40 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent;
/**
* NeoForge 的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
@EventBusSubscriber(modid = AllMusicClient.MODID, value = Dist.CLIENT)
public final class CommandMusicClientNeoForge {
private CommandMusicClientNeoForge() {
}
@SubscribeEvent
public static void onRegisterCommands(RegisterClientCommandsEvent event) {
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args"))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -0,0 +1,40 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent;
/**
* NeoForge 的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
@EventBusSubscriber(modid = AllMusicClient.MODID, value = Dist.CLIENT)
public final class CommandMusicClientNeoForge {
private CommandMusicClientNeoForge() {
}
@SubscribeEvent
public static void onRegisterCommands(RegisterClientCommandsEvent event) {
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args"))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -0,0 +1,40 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent;
/**
* NeoForge 的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
@EventBusSubscriber(modid = AllMusicClient.MODID, value = Dist.CLIENT)
public final class CommandMusicClientNeoForge {
private CommandMusicClientNeoForge() {
}
@SubscribeEvent
public static void onRegisterCommands(RegisterClientCommandsEvent event) {
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args"))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -0,0 +1,40 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent;
/**
* NeoForge 的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
@EventBusSubscriber(modid = AllMusicClient.MODID, value = Dist.CLIENT)
public final class CommandMusicClientNeoForge {
private CommandMusicClientNeoForge() {
}
@SubscribeEvent
public static void onRegisterCommands(RegisterClientCommandsEvent event) {
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args"))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -0,0 +1,40 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent;
/**
* NeoForge 的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
@EventBusSubscriber(modid = AllMusicClient.MODID, value = Dist.CLIENT)
public final class CommandMusicClientNeoForge {
private CommandMusicClientNeoForge() {
}
@SubscribeEvent
public static void onRegisterCommands(RegisterClientCommandsEvent event) {
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args"))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -0,0 +1,40 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent;
/**
* NeoForge 的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
@EventBusSubscriber(modid = AllMusicClient.MODID, value = Dist.CLIENT)
public final class CommandMusicClientNeoForge {
private CommandMusicClientNeoForge() {
}
@SubscribeEvent
public static void onRegisterCommands(RegisterClientCommandsEvent event) {
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args"))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -0,0 +1,40 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent;
/**
* NeoForge 的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
@EventBusSubscriber(modid = AllMusicClient.MODID, value = Dist.CLIENT)
public final class CommandMusicClientNeoForge {
private CommandMusicClientNeoForge() {
}
@SubscribeEvent
public static void onRegisterCommands(RegisterClientCommandsEvent event) {
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args"))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -0,0 +1,40 @@
package com.coloryr.allmusic.client;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent;
/**
* NeoForge 的 /music_client 指令壳
* <p>
* 只负责注册指令,具体逻辑都在通用的 {@link CommandMusicClient} 里
*/
@EventBusSubscriber(modid = AllMusicClient.MODID, value = Dist.CLIENT)
public final class CommandMusicClientNeoForge {
private CommandMusicClientNeoForge() {
}
@SubscribeEvent
public static void onRegisterCommands(RegisterClientCommandsEvent event) {
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
dispatcher.register(Commands.literal(CommandMusicClient.NAME)
.executes(context -> CommandMusicClient.run(new String[0]))
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> CommandMusicClient.run(
split(StringArgumentType.getString(context, "args"))))));
}
private static String[] split(String args) {
String text = args == null ? "" : args.trim();
if (text.isEmpty()) {
return new String[0];
}
return text.split("\\s+");
}
}
@@ -0,0 +1,204 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicCore;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* /music_client 指令的通用实现
* <p>
* 这里只做参数解析和输出内容,不依赖任何 Minecraft 类,
* 每个版本各自实现一层很薄的指令壳(比如 1.7.10 的 {@code CommandMusicClientForge}),
* 把 {@link #execute(String[])} 的结果发给玩家即可
*/
public class CommandMusicClient {
public static final String NAME = "music_client";
private static final String ARG_HELP = "help";
private static final String ARG_INFO = "info";
private static final String ARG_TEST = "test";
private static final String ARG_IMAGE_SIZE = "imagesize";
/**
* 所有子指令,Tab 补全用
*/
public static final String[] SUB_COMMANDS = {ARG_HELP, ARG_INFO, ARG_TEST, ARG_IMAGE_SIZE};
/**
* 普通输出
*/
public static final int STYLE_NORMAL = 0;
/**
* 正常([OK])
*/
public static final int STYLE_OK = 1;
/**
* 异常([!!])
*/
public static final int STYLE_ERROR = 2;
/**
* 指令用法
*
* @return 用法
*/
public static String usage() {
return "/" + NAME + " " + ARG_HELP;
}
/**
* 执行指令
*
* @param args 参数(不含指令名本身)
* @return 要显示的内容
*/
public static Result execute(String[] args) {
if (args == null || args.length == 0) {
return help();
}
switch (args[0].toLowerCase(Locale.ROOT)) {
case ARG_HELP:
return help();
case ARG_INFO:
return new Result("AllMusic 客户端信息", new ArrayList<>(AllMusicCore.getInfoLines()));
case ARG_TEST:
return test();
case ARG_IMAGE_SIZE:
return imageSize(args);
default:
return help();
}
}
/**
* 执行指令并把结果发到聊天栏
* <p>
* 各版本的指令壳解析出参数后直接调用这里,输出统一走游戏桥,
* 不用关心各版本聊天 API 的差异
*
* @param args 参数(不含指令名本身)
* @return Brigadier 需要的返回值
*/
public static int run(String[] args) {
if (AllMusicCore.bridge == null) {
return 0;
}
Result result = execute(args);
if (result.title != null) {
AllMusicCore.bridge.sendMessage(result.title);
}
for (String line : result.lines) {
AllMusicCore.bridge.sendMessage(line);
}
return 1;
}
/**
* 输出行的样式,方便各版本上色
*
* @param line 一行内容
* @return 样式
*/
public static int styleOf(String line) {
if (line == null) {
return STYLE_NORMAL;
}
if (line.startsWith("[OK]")) {
return STYLE_OK;
}
if (line.startsWith("[!!]")) {
return STYLE_ERROR;
}
return STYLE_NORMAL;
}
private static Result help() {
List<String> lines = new ArrayList<>();
lines.add("/" + NAME + " " + ARG_HELP + " - 显示帮助");
lines.add("/" + NAME + " " + ARG_INFO + " - 显示客户端状态(图片大小、封面、播放状态等)");
lines.add("/" + NAME + " " + ARG_TEST + " - 运行客户端自检并输出结果");
lines.add("/" + NAME + " " + ARG_IMAGE_SIZE + " - 查看当前图片大小");
lines.add("/" + NAME + " " + ARG_IMAGE_SIZE + " <" + AllMusicCore.MIN_PIC_SIZE + "-"
+ AllMusicCore.MAX_PIC_SIZE + "> - 修改图片大小");
lines.add("当前图片大小: " + AllMusicCore.getPicSize());
return new Result("AllMusic 客户端指令", lines);
}
private static Result test() {
List<String> lines = new ArrayList<>(AllMusicCore.getTestLines());
lines.add("自检完成: " + count(lines, "[OK]") + " 项正常, " + count(lines, "[!!]") + " 项异常");
return new Result("AllMusic 客户端自检", lines);
}
private static Result imageSize(String[] args) {
List<String> lines = new ArrayList<>();
String title = "AllMusic 图片大小";
if (args.length != 2) {
if (args.length == 1) {
lines.add("当前图片大小: " + AllMusicCore.getPicSize());
} else {
lines.add("用法: /" + NAME + " " + ARG_IMAGE_SIZE + " <"
+ AllMusicCore.MIN_PIC_SIZE + "-" + AllMusicCore.MAX_PIC_SIZE + ">");
}
return new Result(title, lines);
}
int size;
try {
size = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
lines.add("不是有效的数字: " + args[1]);
return new Result(title, lines);
}
if (!AllMusicCore.setPicSize(size)) {
lines.add("图片大小必须在 " + AllMusicCore.MIN_PIC_SIZE + "-"
+ AllMusicCore.MAX_PIC_SIZE + " 之间");
return new Result(title, lines);
}
lines.add("图片大小已设置为 " + size + ",正在重新加载封面;没有封面时下一首歌生效");
lines.add("当前图片大小: " + AllMusicCore.getPicSize());
return new Result(title, lines);
}
private static int count(List<String> lines, String prefix) {
int count = 0;
for (String line : lines) {
if (line.startsWith(prefix)) {
count++;
}
}
return count;
}
/**
* 指令执行结果
*/
public static class Result {
/**
* 标题,可为 null
*/
public final String title;
/**
* 内容行
*/
public final List<String> lines;
public Result(String title, List<String> lines) {
this.title = title;
this.lines = lines;
}
}
}
@@ -19,11 +19,13 @@ import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import java.io.*;
import java.lang.reflect.Method;
import java.nio.IntBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -33,6 +35,25 @@ import java.util.concurrent.ScheduledExecutorService;
*/
public class AllMusicCore {
public static final CommandType[] types = CommandType.values();
/**
* 图片大小的最小值
*/
public static final int MIN_PIC_SIZE = 16;
/**
* 图片大小的最大值
*/
public static final int MAX_PIC_SIZE = 1024;
/**
* 默认静音的日志
* <p>
* httpclient 的调试输出会刷 debug.log;
* 旅行者地图则是按 debug 等级在屏幕上画调试标签(TileDrawStep 里 isDebugEnabled() 就画),
* 所以一起压到 INFO,避免 debug.log 和屏幕都被刷
*/
public static final List<String> DEFAULT_LOG_QUIET = Arrays.asList(
"org.apache.hc.client5",
"org.apache.hc.core5",
"journeymap");
private static final Gson gson = new Gson();
/**
@@ -44,6 +65,10 @@ public class AllMusicCore {
*/
public static ConfigObj config;
public static CloseableHttpClient client;
/**
* 配置文件
*/
private static File configFile;
/**
* 音频解码器与播放器
*/
@@ -90,7 +115,7 @@ public class AllMusicCore {
headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0"));
client = HttpClients.custom().setDefaultHeaders(headers).build();
File configFile = new File(file.toFile(), "allmusic_client.json");
configFile = new File(file.toFile(), "allmusic_client.json");
if (configFile.exists()) {
try {
InputStreamReader reader = new InputStreamReader(
@@ -108,33 +133,53 @@ public class AllMusicCore {
config = new ConfigObj();
config.picSize = 240;
config.queueSize = 100;
try {
String data = new GsonBuilder().setPrettyPrinting()
.create()
.toJson(config);
FileOutputStream out = new FileOutputStream(configFile);
OutputStreamWriter write = new OutputStreamWriter(out, StandardCharsets.UTF_8);
write.write(data);
write.close();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (config.logQuiet == null || config.logQuiet.isEmpty()) {
config.logQuiet = new ArrayList<>(DEFAULT_LOG_QUIET);
}
saveConfig();
AllMusicCore.bridge = bridge;
player = new AllMusicPlayer(source);
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
// LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
// Configuration logConfig = ctx.getConfiguration();
//
// setLoggerLevel(logConfig, "org.apache.hc.client5", Level.INFO);
// setLoggerLevel(logConfig, "org.apache.hc.core5", Level.INFO);
//
// ctx.updateLoggers(logConfig);
}
LoggerConfig loggerConfig = config.getLoggerConfig("org.apache.hc.client5");
loggerConfig.setLevel(Level.INFO);
/**
* 只调整指定 logger 的等级,不动根 logger
* <p>
* {@link Configuration#getLoggerConfig(String)} 找不到同名 logger 时会返回父级或根 logger,
* 直接 setLevel 会顺带把根 logger 的等级改掉,等于把玩家自己的日志配置整个覆盖了。
* 这里只修改同名 logger;没有就新建一个(additive,日志依旧会输出到根 logger 的 appender)
*
* @param logConfig 日志配置
* @param name logger 名字
* @param level 日志等级
*/
private static void setLoggerLevel(Configuration logConfig, String name, Level level) {
LoggerConfig loggerConfig = logConfig.getLoggerConfig(name);
LoggerConfig coreConfig = config.getLoggerConfig("org.apache.hc.core5");
coreConfig.setLevel(Level.INFO);
if (name.equals(loggerConfig.getName())) {
loggerConfig.setLevel(level);
return;
}
ctx.updateLoggers(config);
// 没有同名 logger:新建一个挂进配置。
// Configuration#addLogger 只存在于实现类(1.7.10 自带的 2.0-beta9 里连接口都没有),
// 所以按运行时类型反射调用,避免换版本时 NoSuchMethodError
try {
Method addLogger = logConfig.getClass()
.getMethod("addLogger", String.class, LoggerConfig.class);
addLogger.invoke(logConfig, name, new LoggerConfig(name, level, true));
} catch (Exception ignored) {
// 实在加不进去就保持原样,绝不去改根 logger
}
}
public static void stop() {
@@ -165,6 +210,120 @@ public class AllMusicCore {
hud = new AllMusicHud(config.picSize);
}
/**
* 获取图片大小
*
* @return 图片大小
*/
public static int getPicSize() {
return config == null ? 0 : config.picSize;
}
/**
* 设置图片大小
* <p>
* 立即重建图片渲染缓冲;下一次收到图片(比如下一首歌的封面)时就会用新的大小生成。
* 当前正在显示的封面会重新拉取一次,没有封面时等下一首歌的图片包。
* 同时写回 allmusic_client.json,重启后依然生效
*
* @param size 图片大小
* @return 是否设置成功
*/
public static boolean setPicSize(int size) {
if (config == null) {
return false;
}
if (size < MIN_PIC_SIZE || size > MAX_PIC_SIZE) {
return false;
}
config.picSize = size;
saveConfig();
if (hud != null) {
hud.setPicSize(size);
}
return true;
}
/**
* 保存配置文件
*/
public static void saveConfig() {
if (configFile == null || config == null) {
return;
}
try {
String data = new GsonBuilder().setPrettyPrinting()
.create()
.toJson(config);
FileOutputStream out = new FileOutputStream(configFile);
OutputStreamWriter write = new OutputStreamWriter(out, StandardCharsets.UTF_8);
write.write(data);
write.close();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 客户端信息
*
* @return 每一行内容
*/
public static List<String> getInfoLines() {
List<String> list = new ArrayList<>();
list.add("配置文件: config/allmusic_client.json");
list.add("图片大小 picSize: " + getPicSize()
+ "(范围 " + MIN_PIC_SIZE + "-" + MAX_PIC_SIZE + ",/music_client imagesize <大小> 修改)");
list.add("音频队列: " + (config == null ? 0 : config.queueSize));
list.add("HUD 渲染层: " + (hud == null ? "未初始化" : "已初始化"));
if (hud != null) {
list.add("HUD 数据: " + (hud.hasHudData() ? "已收到" : "未收到(没进服或者服务端没发)"));
list.add("当前封面: " + (hud.hasImage() ? "有,尺寸 " + hud.getPicSize() + "x" + hud.getPicSize() : "无"));
String url = hud.getLastPicUrl();
if (url != null && !url.isEmpty()) {
list.add("封面地址: " + url);
}
}
list.add("播放状态: " + (isPlay() ? "播放中" : "未播放"));
return list;
}
/**
* 客户端自检
*
* @return 每一行结果,[OK] 正常,[!!] 异常
*/
public static List<String> getTestLines() {
List<String> list = new ArrayList<>();
list.add(check(config != null, "核心初始化"));
list.add(check(bridge != null, "游戏桥"));
list.add(check(client != null, "网络客户端"));
list.add(check(player != null, "音频播放器"));
list.add(check(hud != null, "HUD 渲染层"));
if (bridge != null) {
list.add(" 屏幕大小: " + bridge.getScreenWidth() + "x" + bridge.getScreenHeight());
list.add(" 字体高度: " + bridge.getFontHeight());
}
if (hud != null) {
list.addAll(hud.getTestLines());
}
return list;
}
private static String check(boolean ok, String text) {
return (ok ? "[OK] " : "[!!] ") + text;
}
/**
* 退出服务器时
*/
@@ -14,6 +14,8 @@ import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.*;
@@ -40,11 +42,23 @@ public class AllMusicHud {
/**
* 图片大小
*/
private final int size;
private volatile int size;
/**
* 图片渲染
*/
private final PictureFrameBuffer picRender;
private PictureFrameBuffer picRender;
/**
* 新大小的图片渲染,等新图片上传时再替换,避免改大小后当前封面直接消失
*/
private PictureFrameBuffer pendingPicRender;
/**
* 新渲染缓冲的大小
*/
private int pendingSize;
/**
* 最后一张图片是用多大的尺寸生成的
*/
private volatile int loadedSize;
/**
* 文字渲染
*/
@@ -79,8 +93,18 @@ public class AllMusicHud {
private int lyricWidth;
private final int pgOffset;
private final float picScale;
private BufferedImage bg2;
/**
* 原始背景图,改图片大小时用来重新缩放
*/
private final BufferedImage bg2Source;
private volatile BufferedImage bg2;
private boolean isRun;
/**
* 最后一张图片的地址,改图片大小时重新拉取
* <p>
* 由下载线程写入、客户端线程读取,必须 volatile
*/
private volatile String lastPicUrl;
/**
* 是否有图片
@@ -136,12 +160,17 @@ public class AllMusicHud {
offset = AllMusicCore.bridge.readText(PIC_SCALE);
picScale = Float.parseFloat(offset);
BufferedImage source = null;
try {
InputStream stream = AllMusicCore.bridge.readFile(BG2);
bg2 = resizeImage(ImageIO.read(stream), size, size);
source = ImageIO.read(stream);
if (source != null) {
bg2 = resizeImage(source, size, size);
}
} catch (Exception e) {
e.printStackTrace();
}
bg2Source = source;
}
public void stop() {
@@ -324,6 +353,7 @@ public class AllMusicHud {
*/
private void loadPic(String picUrl) {
haveImg = false;
lastPicUrl = picUrl;
try {
while (save == null || imageNeedUpload) {
Thread.sleep(200);
@@ -332,6 +362,9 @@ public class AllMusicHud {
return;
}
// 固定本次使用的尺寸,避免下载过程中被改掉,生成出一张和缓冲对不上的图
final int size = this.size;
HttpGet request = new HttpGet(picUrl);
BufferedImage image = AllMusicCore.client.execute(request, (response -> {
InputStream inputStream = response.getEntity().getContent();
@@ -362,6 +395,7 @@ public class AllMusicHud {
rotateImage = outputStream.toByteArray();
request.clear();
loadedSize = size;
imageNeedUpload = true;
} catch (Exception e) {
e.printStackTrace();
@@ -374,6 +408,23 @@ public class AllMusicHud {
* 更新材质
*/
private void updateTexture() {
if (pendingPicRender != null) {
if (loadedSize != pendingSize) {
// 这张图是按旧尺寸生成的,缓冲对不上,丢掉重新拉一次
String url = lastPicUrl;
if (url != null && !url.isEmpty()) {
setImg(url);
}
return;
}
PictureFrameBuffer old = picRender;
picRender = pendingPicRender;
pendingPicRender = null;
// 旧大小的贴图已经用不到了,释放掉
if (old != null) {
old.dispose();
}
}
picRender.update(sourceImage, rotateImage);
haveImg = true;
}
@@ -433,6 +484,119 @@ public class AllMusicHud {
return true;
}
/**
* 获取图片大小
*
* @return 图片大小
*/
public int getPicSize() {
return size;
}
/**
* 是否已经收到服务端的 HUD 数据
*
* @return 是否收到
*/
public boolean hasHudData() {
return save != null;
}
/**
* 当前是否有封面可以显示
*
* @return 是否有封面
*/
public boolean hasImage() {
return haveImg;
}
/**
* 最后加载的封面地址
*
* @return 封面地址
*/
public String getLastPicUrl() {
return lastPicUrl;
}
/**
* 客户端自检信息
*
* @return 每一行结果,[OK] 正常,[!!] 异常
*/
public List<String> getTestLines() {
List<String> list = new ArrayList<>();
list.add(check(size > 0, "图片缓冲: " + size + "x" + size
+ (pendingPicRender != null ? "(有 " + pendingSize + " 的新缓冲待替换)" : "")));
list.add(check(bg1.width > 0 && bg1.height > 0, "底图 bg1.png: " + bg1.width + "x" + bg1.height));
list.add(check(bg3.width > 0 && bg3.height > 0, "唱臂 bg3.png: " + bg3.width + "x" + bg3.height));
list.add(check(progress1.width > 0 && progress1.height > 0,
"进度条 pg1.png: " + progress1.width + "x" + progress1.height));
list.add(check(progress2.width > 0 && progress2.height > 0,
"进度条 pg2.png: " + progress2.width + "x" + progress2.height));
list.add(check(progress3.width > 0 && progress3.height > 0,
"进度条 pg3.png: " + progress3.width + "x" + progress3.height));
list.add(check(bg2 != null, "封面圆环 bg2.png: "
+ (bg2 == null ? "未加载" : bg2.getWidth() + "x" + bg2.getHeight())));
list.add(check(save != null, "服务端 HUD 数据: " + (save == null ? "未收到" : "已收到")));
if (save != null) {
list.add(" 信息 " + enable(save.info.enable) + " / 歌词 " + enable(save.lyric.enable)
+ " / 进度 " + enable(save.state.enable) + " / 封面 " + enable(save.pic.enable));
list.add(" 封面位置: " + save.pic.x + "," + save.pic.y + " 大小 " + save.pic.size
+ " 旋转 " + enable(save.pic.rotate));
}
list.add(check(haveImg, "当前封面: " + (haveImg ? "有" : "无")));
return list;
}
private static String check(boolean ok, String text) {
return (ok ? "[OK] " : "[!!] ") + text;
}
private static String enable(boolean value) {
return value ? "开" : "关";
}
/**
* 修改图片大小
* <p>
* 只在客户端本地改,不需要服务端配合。新大小的贴图缓冲会立刻建好,
* 等新图片上传(重新拉取当前封面,或者下一首歌的图片包)时替换掉旧的
*
* @param size 图片大小
* @return 是否修改成功
*/
public boolean setPicSize(int size) {
if (size <= 0 || size == this.size) {
return false;
}
this.size = size;
if (bg2Source != null) {
bg2 = resizeImage(bg2Source, size, size);
}
if (pendingPicRender != null) {
pendingPicRender.dispose();
}
pendingPicRender = AllMusicCore.bridge.makePictureRender(size);
pendingSize = size;
// 重新拉一次当前封面,让新大小马上生效;没有封面时就等下一首歌
String url = lastPicUrl;
if (url != null && !url.isEmpty()) {
setImg(url);
}
return true;
}
/**
* 显示更新
*/
@@ -1,5 +1,7 @@
package com.coloryr.allmusic.client.core.objs;
import java.util.List;
public class ConfigObj {
/**
* 图片大小
@@ -9,4 +11,8 @@ public class ConfigObj {
* 音频队列大小
*/
public int queueSize;
/**
* 需要静音(设成 INFO)的日志
*/
public List<String> logQuiet;
}
@@ -22,4 +22,12 @@ public abstract class PictureFrameBuffer {
* @param alpha 透明度
*/
public abstract void draw(boolean rotate, int size, float x, float y, int ang, HudPosType dir, float alpha);
/**
* 释放图片占用的资源
* <p>
* 修改图片大小时会换掉旧的渲染缓冲,旧的贴图需要释放
*/
public void dispose() {
}
}