make it more sexy
This commit is contained in:
parent
087b05fb95
commit
5ebde826ba
58 changed files with 2900 additions and 60 deletions
11
README.md
11
README.md
|
@ -1,9 +1,4 @@
|
|||
# Fabric Example Mod
|
||||
# ChipmunkMod
|
||||
My fork of [Chipmunk Sex Mod](https://code.chipmunk.land/ChipmunkMC/chipmunkmod)
|
||||
|
||||
## Setup
|
||||
|
||||
For setup instructions please see the [fabric wiki page](https://fabricmc.net/wiki/tutorial:setup) that relates to the IDE that you are using.
|
||||
|
||||
## License
|
||||
|
||||
This template is available under the CC0 license. Feel free to learn from it and incorporate it in your own projects.
|
||||
ignore messy code pls,. .,,.,...,.,.,
|
|
@ -29,6 +29,8 @@ dependencies {
|
|||
// Fabric API. This is technically optional, but you probably want it anyway.
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||
|
||||
modImplementation include("net.kyori:adventure-platform-fabric:5.8.0") // for Minecraft 1.19.4
|
||||
|
||||
// Uncomment the following line to enable the deprecated Fabric API modules.
|
||||
// These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time.
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ org.gradle.parallel=true
|
|||
|
||||
# Fabric Properties
|
||||
# check these on https://fabricmc.net/develop
|
||||
minecraft_version=1.19.3
|
||||
yarn_mappings=1.19.3+build.1
|
||||
loader_version=0.14.11
|
||||
minecraft_version=1.19.4
|
||||
yarn_mappings=1.19.4+build.2
|
||||
loader_version=0.14.19
|
||||
|
||||
# Mod Properties
|
||||
mod_version = 1.0.0
|
||||
|
@ -14,4 +14,5 @@ org.gradle.parallel=true
|
|||
archives_base_name = chipmunkmod
|
||||
|
||||
# Dependencies
|
||||
fabric_version=0.68.1+1.19.3
|
||||
fabric_version=0.76.0+1.19.4
|
||||
|
||||
|
|
0
src/main/java/land/chipmunk/chipmunkmod/ChipmunkMod.java
Executable file → Normal file
0
src/main/java/land/chipmunk/chipmunkmod/ChipmunkMod.java
Executable file → Normal file
6
src/main/java/land/chipmunk/chipmunkmod/Configuration.java
Executable file → Normal file
6
src/main/java/land/chipmunk/chipmunkmod/Configuration.java
Executable file → Normal file
|
@ -1,5 +1,6 @@
|
|||
package land.chipmunk.chipmunkmod;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import land.chipmunk.chipmunkmod.data.BlockArea;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
@ -8,6 +9,7 @@ public class Configuration {
|
|||
public CommandManager commands = new CommandManager();
|
||||
public CommandCore core = new CommandCore();
|
||||
public Bots bots = new Bots();
|
||||
public CustomChat customChat = new CustomChat();
|
||||
|
||||
public static class CommandManager {
|
||||
public String prefix = ".";
|
||||
|
@ -30,4 +32,8 @@ public class Configuration {
|
|||
public String prefix;
|
||||
public String key;
|
||||
}
|
||||
|
||||
public static class CustomChat {
|
||||
public JsonObject format;
|
||||
}
|
||||
}
|
||||
|
|
7
src/main/java/land/chipmunk/chipmunkmod/command/CommandManager.java
Executable file → Normal file
7
src/main/java/land/chipmunk/chipmunkmod/command/CommandManager.java
Executable file → Normal file
|
@ -71,5 +71,12 @@ public class CommandManager {
|
|||
CoreCommand.register(dispatcher);
|
||||
UsernameCommand.register(dispatcher);
|
||||
ValidateCommand.register(dispatcher);
|
||||
CloopCommand.register(dispatcher);
|
||||
CustomChatCommand.register(dispatcher);
|
||||
SayCommand.register(dispatcher);
|
||||
SelfCareCommand.register(dispatcher);
|
||||
FullBrightCommand.register(dispatcher);
|
||||
MusicCommand.register(dispatcher);
|
||||
RainbowNameCommand.register(dispatcher);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package land.chipmunk.chipmunkmod.command;
|
||||
|
||||
import land.chipmunk.chipmunkmod.util.ComponentUtilities;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import com.mojang.brigadier.Message;
|
||||
import lombok.Getter;
|
||||
|
||||
public class ComponentMessage implements Message {
|
||||
@Getter private final Component component;
|
||||
|
||||
private ComponentMessage (Component component) {
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
public static ComponentMessage wrap (Component component) {
|
||||
return new ComponentMessage(component);
|
||||
}
|
||||
|
||||
public String getString () {
|
||||
return ComponentUtilities.stringify(component);
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
return component.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package land.chipmunk.chipmunkmod.command.arguments;
|
||||
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.StringReader;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import land.chipmunk.chipmunkmod.command.ComponentMessage;
|
||||
import java.util.Collection;
|
||||
import java.util.Arrays;
|
||||
import java.net.URL;
|
||||
import java.net.MalformedURLException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class LocationArgumentType implements ArgumentType<Object> {
|
||||
private static final Collection<String> EXAMPLES = Arrays.<String>asList("songs/amogus.mid", "images/cat.jpg", "videos/badapple.mp4");
|
||||
|
||||
private static final SimpleCommandExceptionType OOB_FILEPATH = new SimpleCommandExceptionType(ComponentMessage.wrap(Component.translatable("The specified file path is outside of the allowed directory")));
|
||||
|
||||
private boolean allowsUrls = false;
|
||||
private boolean allowsPaths = false;
|
||||
private Path root;
|
||||
|
||||
private LocationArgumentType (boolean allowsUrls, boolean allowsPaths, Path root) {
|
||||
this.allowsUrls = allowsUrls;
|
||||
this.allowsPaths = allowsPaths;
|
||||
this.root = root.toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
public static LocationArgumentType location (Path rootPath) { return new LocationArgumentType(true, true, rootPath); }
|
||||
public static LocationArgumentType url () { return new LocationArgumentType(true, false, null); }
|
||||
public static LocationArgumentType filepath (Path rootPath) { return new LocationArgumentType(false, true, rootPath); }
|
||||
|
||||
@Override
|
||||
public Object parse (StringReader reader) throws CommandSyntaxException {
|
||||
final String remaining = reader.getString().substring(reader.getCursor());
|
||||
if (allowsUrls && isUrlStart(remaining)) return parseUrl(reader);
|
||||
if (allowsPaths) return parsePath(reader);
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isUrlStart (String string) { return string.startsWith("http://") || string.startsWith("https://") || string.startsWith("ftp://"); }
|
||||
|
||||
public URL parseUrl (StringReader reader) throws CommandSyntaxException {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
while (reader.canRead() && reader.peek() != ' ') {
|
||||
sb.append(reader.read());
|
||||
}
|
||||
|
||||
try {
|
||||
return new URL(sb.toString());
|
||||
} catch (MalformedURLException exception) {
|
||||
throw new SimpleCommandExceptionType(ComponentMessage.wrap(Component.text(exception.getMessage()))).create();
|
||||
}
|
||||
}
|
||||
|
||||
public Path parsePath (StringReader reader) throws CommandSyntaxException {
|
||||
final String pathString = reader.readString();
|
||||
final Path path = Path.of(root.toString(), pathString).toAbsolutePath().normalize();
|
||||
if (!path.startsWith(root)) throw OOB_FILEPATH.create();
|
||||
return path;
|
||||
}
|
||||
|
||||
private static Object getLocation (CommandContext<?> context, String name) {
|
||||
return context.getArgument(name, Object.class);
|
||||
}
|
||||
|
||||
public static URL getUrl (CommandContext<?> context, String name) {
|
||||
final Object location = getLocation(context, name);
|
||||
if (location instanceof URL) return (URL) location;
|
||||
try {
|
||||
if (location instanceof Path) return new URL("file", "", -1, location.toString());
|
||||
} catch (MalformedURLException ignored) {
|
||||
return null; // The real question is whether this will actually ever get called
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Path getPath (CommandContext<?> context, String name) {
|
||||
final Object location = getLocation(context, name);
|
||||
if (location instanceof Path) return (Path) location;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getExamples () { return EXAMPLES; }
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package land.chipmunk.chipmunkmod.command.arguments;
|
||||
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.StringReader;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import java.util.Collection;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class TimestampArgumentType implements ArgumentType<Long> {
|
||||
private static final Collection<String> EXAMPLES = Arrays.<String>asList("0:01", "1:23", "6:09");
|
||||
|
||||
private TimestampArgumentType () {
|
||||
}
|
||||
|
||||
public static TimestampArgumentType timestamp () { return new TimestampArgumentType(); }
|
||||
|
||||
@Override
|
||||
public Long parse (StringReader reader) throws CommandSyntaxException {
|
||||
long seconds = 0L;
|
||||
long minutes = 0L;
|
||||
|
||||
seconds = reader.readLong();
|
||||
if (reader.canRead() && reader.peek() == ':') {
|
||||
reader.skip();
|
||||
minutes = seconds;
|
||||
seconds = reader.readLong();
|
||||
}
|
||||
|
||||
return (seconds * 1000) + (minutes * 1000 * 60);
|
||||
}
|
||||
|
||||
// ? Should I create a getter method? Seems like reinventing the wheel since LongArgumentType#getLong is already a thing.
|
||||
|
||||
@Override
|
||||
public Collection<String> getExamples () { return EXAMPLES; }
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
package land.chipmunk.chipmunkmod.commands;
|
||||
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import land.chipmunk.chipmunkmod.data.CommandLoop;
|
||||
import land.chipmunk.chipmunkmod.modules.CommandLooper;
|
||||
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||
import net.minecraft.text.MutableText;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Formatting;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
|
||||
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
|
||||
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
|
||||
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
|
||||
|
||||
public class CloopCommand {
|
||||
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
|
||||
dispatcher.register(
|
||||
literal("cloop")
|
||||
.then(
|
||||
literal("add")
|
||||
.then(
|
||||
argument("interval", integer())
|
||||
.then(
|
||||
argument("command", greedyString())
|
||||
.executes(c -> add(c))
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(
|
||||
literal("remove")
|
||||
.then(
|
||||
argument("index", integer())
|
||||
.executes(c -> remove(c))
|
||||
)
|
||||
)
|
||||
.then(
|
||||
literal("clear")
|
||||
.executes(c -> clear(c))
|
||||
)
|
||||
.then(
|
||||
literal("list")
|
||||
.executes(c -> list(c))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static int add (CommandContext<FabricClientCommandSource> context) {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
|
||||
final String command = getString(context, "command");
|
||||
final int interval = getInteger(context, "interval");
|
||||
|
||||
CommandLooper.INSTANCE.addLoop(command, interval);
|
||||
|
||||
source.sendFeedback(
|
||||
Text.translatable(
|
||||
"Added command %s with interval %s to the command loops",
|
||||
Text.literal(command).formatted(Formatting.AQUA),
|
||||
Text.literal(String.valueOf(interval)).formatted(Formatting.GOLD)
|
||||
)
|
||||
);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
public static int remove (CommandContext<FabricClientCommandSource> context) {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
|
||||
final int index = getInteger(context, "index");
|
||||
|
||||
CommandLooper.INSTANCE.removeLoop(index);
|
||||
|
||||
source.sendFeedback(
|
||||
Text.translatable(
|
||||
"Removed command loop %s",
|
||||
Text.literal(String.valueOf(index)).formatted(Formatting.GOLD)
|
||||
)
|
||||
);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
public static int list (CommandContext<FabricClientCommandSource> context) {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
|
||||
final List<CommandLoop> cloops = CommandLooper.INSTANCE.loops();
|
||||
|
||||
MutableText text = Text.empty();
|
||||
text.append(Text.literal("Command Loops:").formatted(Formatting.GREEN));
|
||||
text.append("\n"); // should i use Text.literal("\n")?
|
||||
|
||||
for (int i = 0; i < cloops.size(); i++) {
|
||||
final CommandLoop cloop = cloops.get(i);
|
||||
text.append(
|
||||
Text.translatable(
|
||||
"%s > %s - %s", // should i use \u203a?
|
||||
Text.literal(String.valueOf(i)).formatted(Formatting.GREEN),
|
||||
Text.literal(cloop.command()).formatted(Formatting.AQUA),
|
||||
Text.literal(String.valueOf(cloop.interval())).formatted(Formatting.GOLD)
|
||||
).formatted(Formatting.GRAY)
|
||||
);
|
||||
|
||||
// PLS TELL ME HOW TO REMOVE THE LAST ONE OF MutableText SPLSPLpLSLPsSSPLPSPLSPSL
|
||||
// btw is this the best way to do it lol
|
||||
final int lastIndex = cloops.size() - 1;
|
||||
if (i < lastIndex) { text.append("\n"); }
|
||||
}
|
||||
|
||||
source.sendFeedback(text);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
public static int clear (CommandContext<FabricClientCommandSource> context) {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
|
||||
CommandLooper.INSTANCE.clearLoops();
|
||||
|
||||
source.sendFeedback(Text.literal("Cleared all command loops"));
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
}
|
0
src/main/java/land/chipmunk/chipmunkmod/commands/CoreCommand.java
Executable file → Normal file
0
src/main/java/land/chipmunk/chipmunkmod/commands/CoreCommand.java
Executable file → Normal file
|
@ -0,0 +1,55 @@
|
|||
package land.chipmunk.chipmunkmod.commands;
|
||||
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import land.chipmunk.chipmunkmod.modules.CustomChat;
|
||||
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import static com.mojang.brigadier.arguments.BoolArgumentType.bool;
|
||||
import static com.mojang.brigadier.arguments.BoolArgumentType.getBool;
|
||||
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
|
||||
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
|
||||
|
||||
public class CustomChatCommand {
|
||||
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
|
||||
dispatcher.register(
|
||||
literal("customchat")
|
||||
.then(
|
||||
literal("enabled")
|
||||
.then(
|
||||
argument("boolean", bool())
|
||||
.executes(CustomChatCommand::enabled)
|
||||
)
|
||||
)
|
||||
.then(
|
||||
literal("format")
|
||||
.then(
|
||||
argument("format", greedyString())
|
||||
.executes(CustomChatCommand::setFormat)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static int enabled (CommandContext<FabricClientCommandSource> context) {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
final boolean bool = getBool(context, "boolean");
|
||||
CustomChat.INSTANCE.enabled(bool);
|
||||
source.sendFeedback(Text.literal("Custom chat is now " + (bool ? "on" : "off")));
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
public static int setFormat (CommandContext<FabricClientCommandSource> context) {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
final String format = getString(context, "format");
|
||||
CustomChat.INSTANCE.format(format);
|
||||
source.sendFeedback(Text.literal("Set the custom chat format to: " + format));
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package land.chipmunk.chipmunkmod.commands;
|
||||
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import land.chipmunk.chipmunkmod.modules.FullBright;
|
||||
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import static com.mojang.brigadier.arguments.BoolArgumentType.bool;
|
||||
import static com.mojang.brigadier.arguments.BoolArgumentType.getBool;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
|
||||
|
||||
public class FullBrightCommand {
|
||||
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
|
||||
dispatcher.register(
|
||||
literal("fullbright")
|
||||
.then(
|
||||
argument("boolean", bool())
|
||||
.executes(FullBrightCommand::set)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static int set (CommandContext<FabricClientCommandSource> context) {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
|
||||
final boolean bool = getBool(context, "boolean");
|
||||
|
||||
FullBright.enabled(bool);
|
||||
|
||||
source.sendFeedback(Text.literal("Fullbright is now " + (bool ? "enabled" : "disabled")));
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,280 @@
|
|||
package land.chipmunk.chipmunkmod.commands;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||
import land.chipmunk.chipmunkmod.command.CommandManager;
|
||||
import land.chipmunk.chipmunkmod.command.ComponentMessage;
|
||||
import land.chipmunk.chipmunkmod.modules.SongPlayer;
|
||||
import land.chipmunk.chipmunkmod.song.Song;
|
||||
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.JoinConfiguration;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Formatting;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.mojang.brigadier.arguments.BoolArgumentType.bool;
|
||||
import static com.mojang.brigadier.arguments.BoolArgumentType.getBool;
|
||||
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
|
||||
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
|
||||
import static com.mojang.brigadier.arguments.LongArgumentType.getLong;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
|
||||
import static land.chipmunk.chipmunkmod.command.arguments.LocationArgumentType.*;
|
||||
import static land.chipmunk.chipmunkmod.command.arguments.TimestampArgumentType.timestamp;
|
||||
|
||||
public class MusicCommand {
|
||||
private static SimpleCommandExceptionType NO_SONG_IS_CURRENTLY_PLAYING = new SimpleCommandExceptionType(ComponentMessage.wrap(Component.translatable("No song is currently playing")));
|
||||
private static SimpleCommandExceptionType OOB_TIMESTAMP = new SimpleCommandExceptionType(ComponentMessage.wrap(Component.translatable("Invalid timestamp for the current song")));
|
||||
private static SimpleCommandExceptionType DIRECTORY_DOES_NOT_EXIST = new SimpleCommandExceptionType(ComponentMessage.wrap(Component.translatable("The specified directory does not exist")));
|
||||
|
||||
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
|
||||
final MusicCommand instance = new MusicCommand();
|
||||
|
||||
Path root = Path.of(SongPlayer.SONG_DIR.getPath());
|
||||
|
||||
dispatcher.register(
|
||||
literal("music")
|
||||
.then(
|
||||
literal("play")
|
||||
.then(
|
||||
argument("location", location(root))
|
||||
.executes(instance::play)
|
||||
)
|
||||
)
|
||||
|
||||
.then(literal("stop").executes(instance::stop))
|
||||
.then(literal("skip").executes(instance::skip))
|
||||
.then(literal("pause").executes(instance::pause))
|
||||
|
||||
.then(
|
||||
literal("list")
|
||||
.executes(c -> instance.list(c, root))
|
||||
.then(
|
||||
argument("location", filepath(root))
|
||||
.executes(c -> instance.list(c, getPath(c, "location")))
|
||||
)
|
||||
)
|
||||
|
||||
.then(
|
||||
literal("loop")
|
||||
.executes(instance::toggleLoop)
|
||||
.then(
|
||||
argument("count", integer())
|
||||
.executes(instance::loop)
|
||||
)
|
||||
)
|
||||
|
||||
.then(
|
||||
literal("goto")
|
||||
.then(
|
||||
argument("timestamp", timestamp())
|
||||
.executes(instance::gotoCommand)
|
||||
)
|
||||
)
|
||||
.then(
|
||||
literal("useCore")
|
||||
.then(
|
||||
argument("boolean", bool())
|
||||
.executes(instance::useCore)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public int play (CommandContext<FabricClientCommandSource> context) {
|
||||
final SongPlayer songPlayer = SongPlayer.INSTANCE;
|
||||
|
||||
final Path path = getPath(context, "location");
|
||||
|
||||
if (path != null) songPlayer.loadSong(path);
|
||||
else songPlayer.loadSong(getUrl(context, "location"));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int stop (CommandContext<FabricClientCommandSource> context) throws CommandSyntaxException {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
final SongPlayer songPlayer = SongPlayer.INSTANCE;
|
||||
|
||||
if (songPlayer.currentSong() == null) throw NO_SONG_IS_CURRENTLY_PLAYING.create();
|
||||
|
||||
songPlayer.stopPlaying();
|
||||
songPlayer.songQueue().clear();
|
||||
source.sendFeedback(Text.literal("Stopped music playback").formatted(Formatting.GREEN));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int skip (CommandContext<FabricClientCommandSource> context) throws CommandSyntaxException {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
final SongPlayer songPlayer = SongPlayer.INSTANCE;
|
||||
|
||||
if (songPlayer.currentSong() == null) throw NO_SONG_IS_CURRENTLY_PLAYING.create();
|
||||
|
||||
songPlayer.stopPlaying();
|
||||
source.sendFeedback(Text.literal("Skipped the current song").formatted(Formatting.GREEN));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int pause (CommandContext<FabricClientCommandSource> context) throws CommandSyntaxException {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
final SongPlayer songPlayer = SongPlayer.INSTANCE;
|
||||
final Song currentSong = songPlayer.currentSong();
|
||||
|
||||
if (currentSong == null) throw NO_SONG_IS_CURRENTLY_PLAYING.create();
|
||||
|
||||
if (!currentSong.paused) {
|
||||
currentSong.pause();
|
||||
source.sendFeedback(Text.literal("Paused the current song"));
|
||||
} else {
|
||||
currentSong.play();
|
||||
source.sendFeedback(Text.literal("Unpaused the current song"));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int list (CommandContext<FabricClientCommandSource> context, Path path) throws CommandSyntaxException {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
final String prefix = CommandManager.prefix;
|
||||
|
||||
final File directory = path.toFile();
|
||||
final String[] filenames = directory.list();
|
||||
if (filenames == null) throw DIRECTORY_DOES_NOT_EXIST.create();
|
||||
|
||||
final Path root = Path.of(SongPlayer.SONG_DIR.getAbsoluteFile().getPath()).toAbsolutePath();
|
||||
String relativePath;
|
||||
if (path.getNameCount() - root.getNameCount() > 0) relativePath = path.subpath(root.getNameCount(), path.getNameCount()).toString();
|
||||
else relativePath = "";
|
||||
|
||||
final List<Component> directories = new ArrayList<>();
|
||||
final List<Component> files = new ArrayList<>();
|
||||
int i = 0;
|
||||
|
||||
for (String filename : filenames) {
|
||||
final File file = new File(directory, filename);
|
||||
if (!file.isDirectory()) continue;
|
||||
|
||||
final NamedTextColor color = (i++ & 1) == 0 ? NamedTextColor.DARK_GREEN : NamedTextColor.GREEN;
|
||||
|
||||
final Path relativeFilepath = Path.of(relativePath, filename);
|
||||
final String escapedPath = escapePath(relativeFilepath.toString());
|
||||
|
||||
directories.add(
|
||||
Component.text(filename + "/", color)
|
||||
.clickEvent(ClickEvent.suggestCommand(prefix + "music list " + escapedPath))
|
||||
.hoverEvent(HoverEvent.showText(Component.translatable("Click to list %s", Component.text(filename))))
|
||||
);
|
||||
}
|
||||
|
||||
for (String filename : filenames) {
|
||||
final File file = new File(directory, filename);
|
||||
if (file.isDirectory()) continue;
|
||||
|
||||
final NamedTextColor color = (i++ & 1) == 0 ? NamedTextColor.DARK_GREEN : NamedTextColor.GREEN;
|
||||
|
||||
final Path relativeFilepath = Path.of(relativePath, filename);
|
||||
final String escapedPath = escapePath(relativeFilepath.toString());
|
||||
|
||||
files.add(
|
||||
Component.text(filename, color)
|
||||
.clickEvent(ClickEvent.suggestCommand(prefix + "music play " + escapedPath))
|
||||
.hoverEvent(HoverEvent.showText(Component.translatable("Click to play %s", Component.text(filename))))
|
||||
);
|
||||
}
|
||||
|
||||
final ArrayList<Component> mergedList = new ArrayList<>();
|
||||
for (Component component : directories) mergedList.add(component);
|
||||
for (Component component : files) mergedList.add(component);
|
||||
final Component component = Component.translatable("Songs - %s", Component.join(JoinConfiguration.separator(Component.space()), mergedList)).color(NamedTextColor.GREEN);
|
||||
|
||||
MinecraftClient.getInstance().player.sendMessage(component);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// TODO: Move this into some utility class, as it is more related to brigadier strings in general than to the list command in specific
|
||||
private String escapePath (String path) {
|
||||
final StringBuilder sb = new StringBuilder("'");
|
||||
|
||||
for (char character : path.toCharArray()) {
|
||||
if (character == '\'' || character == '\\') sb.append('\\');
|
||||
sb.append(character);
|
||||
}
|
||||
|
||||
sb.append("'");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public int toggleLoop (CommandContext<FabricClientCommandSource> context) throws CommandSyntaxException {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
final SongPlayer songPlayer = SongPlayer.INSTANCE;
|
||||
final Song currentSong = songPlayer.currentSong();
|
||||
|
||||
if (currentSong == null) throw NO_SONG_IS_CURRENTLY_PLAYING.create();
|
||||
|
||||
currentSong.looping = !currentSong.looping;
|
||||
|
||||
source.sendFeedback(Text.translatable(currentSong.looping ? "Enabled looping" : "Disabled looping"));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
public int loop (CommandContext<FabricClientCommandSource> context) throws CommandSyntaxException {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
final SongPlayer songPlayer = SongPlayer.INSTANCE;
|
||||
final Song currentSong = songPlayer.currentSong();
|
||||
final int count = getInteger(context, "count");
|
||||
|
||||
if (currentSong == null) throw NO_SONG_IS_CURRENTLY_PLAYING.create();
|
||||
|
||||
currentSong.looping = true;
|
||||
currentSong.loopCount = count;
|
||||
|
||||
source.sendFeedback(Text.translatable("Enabled looping for %s times", Text.literal(String.valueOf(count))));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int gotoCommand (CommandContext<FabricClientCommandSource> context) throws CommandSyntaxException {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
final SongPlayer songPlayer = SongPlayer.INSTANCE;
|
||||
final Song currentSong = songPlayer.currentSong();
|
||||
final long millis = getLong(context, "timestamp");
|
||||
|
||||
if (currentSong == null) throw NO_SONG_IS_CURRENTLY_PLAYING.create();
|
||||
|
||||
if (millis < 0 || millis > currentSong.length) throw OOB_TIMESTAMP.create();
|
||||
|
||||
currentSong.setTime(millis);
|
||||
|
||||
source.sendFeedback(Text.translatable("Set the current time of the song to %s", songPlayer.formatTime(millis)));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int useCore (CommandContext<FabricClientCommandSource> context) {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
|
||||
final boolean enabled = getBool(context, "boolean");
|
||||
|
||||
SongPlayer.INSTANCE.useCore(enabled);
|
||||
|
||||
source.sendFeedback(Text.literal("Playing music using core is now " + (enabled ? "enabled" : "disabled")));
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package land.chipmunk.chipmunkmod.commands;
|
||||
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import land.chipmunk.chipmunkmod.modules.RainbowName;
|
||||
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import static com.mojang.brigadier.arguments.BoolArgumentType.bool;
|
||||
import static com.mojang.brigadier.arguments.BoolArgumentType.getBool;
|
||||
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
|
||||
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
|
||||
|
||||
public class RainbowNameCommand {
|
||||
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
|
||||
dispatcher.register(
|
||||
literal("rainbowname")
|
||||
.then(
|
||||
literal("enabled")
|
||||
.then(
|
||||
argument("boolean", bool())
|
||||
.executes(RainbowNameCommand::enabled)
|
||||
)
|
||||
)
|
||||
.then(
|
||||
literal("setName")
|
||||
.then(
|
||||
argument("name", greedyString())
|
||||
.executes(RainbowNameCommand::setName)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static int enabled (CommandContext<FabricClientCommandSource> context) {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
|
||||
final boolean bool = getBool(context, "boolean");
|
||||
|
||||
if (bool) {
|
||||
RainbowName.INSTANCE.enable();
|
||||
source.sendFeedback(Text.literal("Rainbow name is now enabled"));
|
||||
} else {
|
||||
RainbowName.INSTANCE.disable();
|
||||
source.sendFeedback(Text.literal("Rainbow name is now disabled"));
|
||||
}
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
public static int setName (CommandContext<FabricClientCommandSource> context) {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
|
||||
final String name = getString(context, "name");
|
||||
|
||||
RainbowName.INSTANCE.displayName(name);
|
||||
|
||||
source.sendFeedback(Text.literal("Set the display name to: " + name));
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package land.chipmunk.chipmunkmod.commands;
|
||||
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
|
||||
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
|
||||
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
|
||||
|
||||
public class SayCommand {
|
||||
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
|
||||
dispatcher.register(
|
||||
literal("say")
|
||||
.then(
|
||||
argument("message", greedyString())
|
||||
.executes(m -> say(m))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static int say (CommandContext<FabricClientCommandSource> context) {
|
||||
final ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler();
|
||||
|
||||
networkHandler.sendChatMessage(getString(context, "message"));
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package land.chipmunk.chipmunkmod.commands;
|
||||
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import land.chipmunk.chipmunkmod.modules.SelfCare;
|
||||
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import static com.mojang.brigadier.arguments.BoolArgumentType.bool;
|
||||
import static com.mojang.brigadier.arguments.BoolArgumentType.getBool;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
|
||||
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
|
||||
|
||||
public class SelfCareCommand {
|
||||
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
|
||||
dispatcher.register(
|
||||
literal("selfcare")
|
||||
.then(
|
||||
literal("op")
|
||||
.then(
|
||||
argument("boolean", bool())
|
||||
.executes(m -> setSelfCare(m, "op"))
|
||||
)
|
||||
)
|
||||
.then(
|
||||
literal("gamemode")
|
||||
.then(
|
||||
argument("boolean", bool())
|
||||
.executes(m -> setSelfCare(m, "gamemode"))
|
||||
)
|
||||
)
|
||||
.then(
|
||||
literal("cspy")
|
||||
.then(
|
||||
argument("boolean", bool())
|
||||
.executes(m -> setSelfCare(m, "cspy"))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// setSelfCare is probably not a good name for this
|
||||
public static int setSelfCare (CommandContext<FabricClientCommandSource> context, String type) {
|
||||
final FabricClientCommandSource source = context.getSource();
|
||||
final boolean bool = getBool(context, "boolean");
|
||||
|
||||
switch (type) {
|
||||
case "op" -> {
|
||||
SelfCare.INSTANCE.opEnabled(bool);
|
||||
source.sendFeedback(Text.literal("The op self care is now " + (bool ? "enabled" : "disabled")));
|
||||
}
|
||||
case "gamemode" -> {
|
||||
SelfCare.INSTANCE.gamemodeEnabled(bool);
|
||||
source.sendFeedback(Text.literal("The gamemode self care is now " + (bool ? "enabled" : "disabled")));
|
||||
}
|
||||
case "cspy" -> {
|
||||
SelfCare.INSTANCE.cspyEnabled(bool);
|
||||
source.sendFeedback(Text.literal("The CommandSpy self care is now " + (bool ? "enabled" : "disabled")));
|
||||
}
|
||||
}
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
}
|
0
src/main/java/land/chipmunk/chipmunkmod/commands/TestCommand.java
Executable file → Normal file
0
src/main/java/land/chipmunk/chipmunkmod/commands/TestCommand.java
Executable file → Normal file
0
src/main/java/land/chipmunk/chipmunkmod/commands/UsernameCommand.java
Executable file → Normal file
0
src/main/java/land/chipmunk/chipmunkmod/commands/UsernameCommand.java
Executable file → Normal file
0
src/main/java/land/chipmunk/chipmunkmod/commands/ValidateCommand.java
Executable file → Normal file
0
src/main/java/land/chipmunk/chipmunkmod/commands/ValidateCommand.java
Executable file → Normal file
0
src/main/java/land/chipmunk/chipmunkmod/data/BlockArea.java
Executable file → Normal file
0
src/main/java/land/chipmunk/chipmunkmod/data/BlockArea.java
Executable file → Normal file
|
@ -0,0 +1,15 @@
|
|||
package land.chipmunk.chipmunkmod.data;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class CommandLoop {
|
||||
@Getter
|
||||
private String command;
|
||||
@Getter
|
||||
private long interval;
|
||||
|
||||
public CommandLoop (String command, long interval) {
|
||||
this.command = command;
|
||||
this.interval = interval;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package land.chipmunk.chipmunkmod.data;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.world.GameMode;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class MutablePlayerListEntry {
|
||||
private GameProfile profile;
|
||||
private GameMode gamemode;
|
||||
private int latency;
|
||||
private Text displayName;
|
||||
|
||||
public MutablePlayerListEntry (PlayerListS2CPacket.Entry entry) {
|
||||
this(entry.profile(), entry.gameMode(), entry.latency(), entry.displayName());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package land.chipmunk.chipmunkmod.listeners;
|
||||
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
public class Listener {
|
||||
public void chatMessageReceived (Text message) {}
|
||||
|
||||
public void packetReceived (Packet packet) {}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package land.chipmunk.chipmunkmod.listeners;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ListenerManager {
|
||||
public static List<Listener> listeners = new ArrayList<>();
|
||||
|
||||
public static void addListener (Listener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package land.chipmunk.chipmunkmod.mixin;
|
||||
|
||||
import land.chipmunk.chipmunkmod.listeners.Listener;
|
||||
import land.chipmunk.chipmunkmod.listeners.ListenerManager;
|
||||
import land.chipmunk.chipmunkmod.modules.RainbowName;
|
||||
import net.minecraft.client.gui.hud.MessageIndicator;
|
||||
import net.minecraft.network.message.MessageSignatureData;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.text.TranslatableTextContent;
|
||||
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;
|
||||
|
||||
@Mixin(net.minecraft.client.gui.hud.ChatHud.class)
|
||||
public class ChatHudMixin {
|
||||
@Inject(at = @At("HEAD"), method = "addMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;ILnet/minecraft/client/gui/hud/MessageIndicator;Z)V", cancellable = true)
|
||||
public void addMessage(Text message, MessageSignatureData signature, int ticks, MessageIndicator indicator, boolean refresh, CallbackInfo ci) {
|
||||
try {
|
||||
if (RainbowName.INSTANCE.enabled()) {
|
||||
if (message.getString().contains("Your nickname is now ") || message.getString().contains("Nickname changed.")) {
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (((TranslatableTextContent) message.getContent()).getKey().equals("advMode.setCommand.success")) {
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
} catch (ClassCastException ignored) {}
|
||||
|
||||
for (Listener listener : ListenerManager.listeners) {
|
||||
listener.chatMessageReceived(message);
|
||||
}
|
||||
}
|
||||
}
|
0
src/main/java/land/chipmunk/chipmunkmod/mixin/ChatInputSuggestorMixin.java
Executable file → Normal file
0
src/main/java/land/chipmunk/chipmunkmod/mixin/ChatInputSuggestorMixin.java
Executable file → Normal file
7
src/main/java/land/chipmunk/chipmunkmod/mixin/ChatScreenMixin.java
Executable file → Normal file
7
src/main/java/land/chipmunk/chipmunkmod/mixin/ChatScreenMixin.java
Executable file → Normal file
|
@ -1,5 +1,6 @@
|
|||
package land.chipmunk.chipmunkmod.mixin;
|
||||
|
||||
import land.chipmunk.chipmunkmod.modules.CustomChat;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
|
@ -16,6 +17,12 @@ public class ChatScreenMixin {
|
|||
|
||||
if (addToHistory) MinecraftClient.getInstance().inGameHud.getChatHud().addToMessageHistory(chatText);
|
||||
|
||||
cir.setReturnValue(true);
|
||||
} else if (!chatText.startsWith("/")) {
|
||||
CustomChat.INSTANCE.chat(chatText);
|
||||
|
||||
if (addToHistory) MinecraftClient.getInstance().inGameHud.getChatHud().addToMessageHistory(chatText);
|
||||
|
||||
cir.setReturnValue(true);
|
||||
}
|
||||
}
|
||||
|
|
26
src/main/java/land/chipmunk/chipmunkmod/mixin/ClientConnectionMixin.java
Executable file → Normal file
26
src/main/java/land/chipmunk/chipmunkmod/mixin/ClientConnectionMixin.java
Executable file → Normal file
|
@ -1,13 +1,16 @@
|
|||
package land.chipmunk.chipmunkmod.mixin;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import land.chipmunk.chipmunkmod.listeners.Listener;
|
||||
import land.chipmunk.chipmunkmod.listeners.ListenerManager;
|
||||
import net.minecraft.network.listener.PacketListener;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.text.Text;
|
||||
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 net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
import net.minecraft.text.Text;
|
||||
import land.chipmunk.chipmunkmod.modules.CommandCore;
|
||||
import land.chipmunk.chipmunkmod.modules.SelfCare;
|
||||
|
||||
@Mixin(net.minecraft.network.ClientConnection.class)
|
||||
public class ClientConnectionMixin {
|
||||
|
@ -17,4 +20,19 @@ public class ClientConnectionMixin {
|
|||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "exceptionCaught", at = @At("HEAD"), cancellable = true)
|
||||
private void exceptionCaught (ChannelHandlerContext context, Throwable ex, CallbackInfo ci) {
|
||||
if (ex instanceof DecoderException) {
|
||||
ci.cancel();
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "handlePacket", at = @At("HEAD"))
|
||||
private static void handlePacket (Packet packet, PacketListener _listener, CallbackInfo ci) {
|
||||
for (Listener listener : ListenerManager.listeners) {
|
||||
listener.packetReceived(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
0
src/main/java/land/chipmunk/chipmunkmod/mixin/ClientPlayNetworkHandlerAccessor.java
Executable file → Normal file
0
src/main/java/land/chipmunk/chipmunkmod/mixin/ClientPlayNetworkHandlerAccessor.java
Executable file → Normal file
12
src/main/java/land/chipmunk/chipmunkmod/mixin/ClientPlayNetworkHandlerMixin.java
Executable file → Normal file
12
src/main/java/land/chipmunk/chipmunkmod/mixin/ClientPlayNetworkHandlerMixin.java
Executable file → Normal file
|
@ -1,16 +1,24 @@
|
|||
package land.chipmunk.chipmunkmod.mixin;
|
||||
|
||||
import land.chipmunk.chipmunkmod.modules.*;
|
||||
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
|
||||
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 net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
|
||||
import land.chipmunk.chipmunkmod.modules.SelfCare;
|
||||
|
||||
@Mixin(net.minecraft.client.network.ClientPlayNetworkHandler.class)
|
||||
public class ClientPlayNetworkHandlerMixin {
|
||||
@Inject(method = "onGameJoin", at = @At("TAIL"))
|
||||
private void onGameJoin (GameJoinS2CPacket packet, CallbackInfo ci) {
|
||||
SelfCare.INSTANCE.init();
|
||||
CommandLooper.INSTANCE.init();
|
||||
SongPlayer.INSTANCE.coreReady();
|
||||
RainbowName.INSTANCE.init();
|
||||
}
|
||||
|
||||
@Inject(method = "onGameJoin", at = @At("HEAD"))
|
||||
private void onGameJoin2 (GameJoinS2CPacket packet, CallbackInfo ci) {
|
||||
Players.INSTANCE.init();
|
||||
}
|
||||
}
|
||||
|
|
0
src/main/java/land/chipmunk/chipmunkmod/mixin/ClientPlayerEntityMixin.java
Executable file → Normal file
0
src/main/java/land/chipmunk/chipmunkmod/mixin/ClientPlayerEntityMixin.java
Executable file → Normal file
|
@ -0,0 +1,46 @@
|
|||
package land.chipmunk.chipmunkmod.mixin;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import net.minecraft.network.*;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.util.profiling.jfr.FlightProfiler;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static land.chipmunk.chipmunkmod.ChipmunkMod.LOGGER;
|
||||
|
||||
@Mixin(DecoderHandler.class)
|
||||
public class DecoderHandlerMixin {
|
||||
@Final @Mutable @Shadow private final NetworkSide side;
|
||||
|
||||
public DecoderHandlerMixin(NetworkSide side) {
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
@Inject(method = "decode", at = @At("HEAD"), cancellable = true)
|
||||
private void decode (ChannelHandlerContext ctx, ByteBuf buf, List<Object> objects, CallbackInfo ci) {
|
||||
int i = buf.readableBytes();
|
||||
if (i != 0) {
|
||||
PacketByteBuf packetByteBuf = new PacketByteBuf(buf);
|
||||
int j = packetByteBuf.readVarInt();
|
||||
Packet<?> packet = ctx.channel().attr(ClientConnection.PROTOCOL_ATTRIBUTE_KEY).get().getPacketHandler(this.side, j, packetByteBuf);
|
||||
if (packet != null) {
|
||||
int k = ctx.channel().attr(ClientConnection.PROTOCOL_ATTRIBUTE_KEY).get().getId();
|
||||
FlightProfiler.INSTANCE.onPacketReceived(k, j, ctx.channel().remoteAddress(), i);
|
||||
objects.add(packet);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(ClientConnection.PACKET_RECEIVED_MARKER, " IN: [{}:{}] {}", ctx.channel().attr(ClientConnection.PROTOCOL_ATTRIBUTE_KEY).get(), j, packet.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package land.chipmunk.chipmunkmod.mixin;
|
||||
|
||||
import land.chipmunk.chipmunkmod.modules.FullBright;
|
||||
import net.minecraft.client.render.LightmapTextureManager;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArgs;
|
||||
import org.spongepowered.asm.mixin.injection.invoke.arg.Args;
|
||||
|
||||
@Mixin(LightmapTextureManager.class)
|
||||
public class LightmapTextureManagerMixin {
|
||||
@ModifyArgs(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/texture/NativeImage;setColor(III)V"))
|
||||
private void update (Args args) {
|
||||
if (FullBright.enabled()) args.set(2, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
0
src/main/java/land/chipmunk/chipmunkmod/mixin/MinecraftClientAccessor.java
Executable file → Normal file
0
src/main/java/land/chipmunk/chipmunkmod/mixin/MinecraftClientAccessor.java
Executable file → Normal file
29
src/main/java/land/chipmunk/chipmunkmod/modules/CommandCore.java
Executable file → Normal file
29
src/main/java/land/chipmunk/chipmunkmod/modules/CommandCore.java
Executable file → Normal file
|
@ -1,31 +1,28 @@
|
|||
package land.chipmunk.chipmunkmod.modules;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.network.packet.c2s.play.UpdateCommandBlockC2SPacket;
|
||||
import net.minecraft.block.entity.CommandBlockBlockEntity;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import land.chipmunk.chipmunkmod.data.BlockArea;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import net.minecraft.block.entity.CommandBlockBlockEntity;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.network.packet.c2s.play.UpdateCommandBlockC2SPacket;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import land.chipmunk.chipmunkmod.ChipmunkMod;
|
||||
import land.chipmunk.chipmunkmod.data.BlockArea;
|
||||
|
||||
public class CommandCore {
|
||||
private MinecraftClient client;
|
||||
private final MinecraftClient client;
|
||||
@Getter @Setter private boolean ready = false;
|
||||
@Getter @Setter private BlockPos origin;
|
||||
@Getter private final BlockArea relativeArea;
|
||||
@Getter @Setter private BlockPos currentBlockRelative;
|
||||
|
||||
public static CommandCore INSTANCE = new CommandCore(MinecraftClient.getInstance(), ChipmunkMod.CONFIG.core.relativeArea);
|
||||
public static CommandCore INSTANCE = new CommandCore(MinecraftClient.getInstance(), new BlockArea(new BlockPos(0, 0, 0), new BlockPos(15, 2, 15)));
|
||||
|
||||
public CommandCore (MinecraftClient client, BlockArea relativeArea) {
|
||||
this.client = client;
|
||||
|
@ -121,14 +118,14 @@ public class CommandCore {
|
|||
|
||||
incrementCurrentBlock();
|
||||
|
||||
CompletableFuture<NbtCompound> future = new CompletableFuture<NbtCompound>();
|
||||
CompletableFuture<NbtCompound> future = new CompletableFuture<>();
|
||||
|
||||
final Timer timer = new Timer();
|
||||
|
||||
final TimerTask queryTask = new TimerTask() {
|
||||
public void run () {
|
||||
client.getNetworkHandler().getDataQueryHandler().queryBlockNbt(currentBlock,
|
||||
tag -> { future.complete(tag); });
|
||||
future::complete);
|
||||
|
||||
timer.cancel(); // ? Is this necesary?
|
||||
timer.purge();
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
package land.chipmunk.chipmunkmod.modules;
|
||||
|
||||
import land.chipmunk.chipmunkmod.data.CommandLoop;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class CommandLooper {
|
||||
private final MinecraftClient client;
|
||||
|
||||
// sus
|
||||
private final List<TimerTask> loopTasks = new ArrayList<>();
|
||||
@Getter private final List<CommandLoop> loops = new ArrayList<>();
|
||||
|
||||
private Timer timer = null;
|
||||
|
||||
public static CommandLooper INSTANCE = new CommandLooper(MinecraftClient.getInstance());
|
||||
|
||||
public CommandLooper (MinecraftClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public void init () {
|
||||
TimerTask task = new TimerTask() {
|
||||
@Override
|
||||
public void run () {
|
||||
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
|
||||
|
||||
if (networkHandler == null) { cleanup(); }
|
||||
}
|
||||
};
|
||||
|
||||
if (timer != null) cleanup();
|
||||
|
||||
timer = new Timer();
|
||||
timer.schedule(task, 0, 50);
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
if (timer == null) return;
|
||||
clearLoops();
|
||||
}
|
||||
|
||||
public void addLoop(String command, long interval) {
|
||||
TimerTask loopTask = new TimerTask() {
|
||||
public void run() {
|
||||
CommandCore.INSTANCE.run(command);
|
||||
}
|
||||
};
|
||||
loopTasks.add(loopTask);
|
||||
loops.add(new CommandLoop(command, interval)); // mabe,.,..
|
||||
// should i use 50 or 0?
|
||||
timer.scheduleAtFixedRate(loopTask, 0, interval);
|
||||
}
|
||||
|
||||
public void removeLoop(int index) {
|
||||
TimerTask loopTask = loopTasks.remove(index);
|
||||
if (loopTask != null) {
|
||||
loopTask.cancel();
|
||||
}
|
||||
|
||||
loops.remove(index);
|
||||
}
|
||||
|
||||
public void clearLoops() {
|
||||
for (TimerTask loopTask : loopTasks) {
|
||||
loopTask.cancel();
|
||||
}
|
||||
loopTasks.clear();
|
||||
|
||||
loops.clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package land.chipmunk.chipmunkmod.modules;
|
||||
|
||||
import land.chipmunk.chipmunkmod.ChipmunkMod;
|
||||
import land.chipmunk.chipmunkmod.data.MutablePlayerListEntry;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.JoinConfiguration;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
|
||||
public class CustomChat {
|
||||
private final MinecraftClient client;
|
||||
|
||||
public static final CustomChat INSTANCE = new CustomChat(MinecraftClient.getInstance());
|
||||
|
||||
@Getter @Setter private boolean enabled = true;
|
||||
|
||||
@Getter @Setter private String format = ChipmunkMod.CONFIG.customChat.format.toString();
|
||||
|
||||
public CustomChat (MinecraftClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public void chat (String message) {
|
||||
if (!enabled) {
|
||||
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
|
||||
networkHandler.sendChatMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
final String username = MinecraftClient.getInstance().getSession().getUsername();
|
||||
|
||||
final String sanitizedMessage = message
|
||||
.replace("\\", "\\\\")
|
||||
.replace("\"", "\\\"");
|
||||
|
||||
try {
|
||||
final MutablePlayerListEntry entry = Players.INSTANCE.getEntry(client.getNetworkHandler().getProfile().getId());
|
||||
|
||||
final Component displayNameComponent = entry.displayName().asComponent();
|
||||
|
||||
final String prefix = GsonComponentSerializer.gson().serialize(Component.join(JoinConfiguration.separator(Component.empty()), displayNameComponent.children().get(0)));
|
||||
final String displayName = GsonComponentSerializer.gson().serialize(Component.join(JoinConfiguration.separator(Component.empty()), displayNameComponent.children().get(1)));
|
||||
|
||||
final String sanitizedFormat = format
|
||||
.replace("{\"text\":\"PREFIX\"}", prefix)
|
||||
.replace("{\"text\":\"DISPLAYNAME\"}", displayName)
|
||||
.replace("USERNAME", username)
|
||||
.replace("MESSAGE", sanitizedMessage);
|
||||
|
||||
CommandCore.INSTANCE.run("minecraft:tellraw @a " + sanitizedFormat);
|
||||
} catch (Exception e) {
|
||||
if (client.player == null) return;
|
||||
client.player.sendMessage(Component.text(e.toString()).color(NamedTextColor.RED));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package land.chipmunk.chipmunkmod.modules;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class FullBright {
|
||||
@Getter @Setter private static boolean enabled = true;
|
||||
}
|
146
src/main/java/land/chipmunk/chipmunkmod/modules/Players.java
Normal file
146
src/main/java/land/chipmunk/chipmunkmod/modules/Players.java
Normal file
|
@ -0,0 +1,146 @@
|
|||
package land.chipmunk.chipmunkmod.modules;
|
||||
|
||||
import com.mojang.brigadier.Message;
|
||||
import com.mojang.brigadier.suggestion.Suggestion;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import land.chipmunk.chipmunkmod.data.MutablePlayerListEntry;
|
||||
import land.chipmunk.chipmunkmod.listeners.Listener;
|
||||
import land.chipmunk.chipmunkmod.listeners.ListenerManager;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.network.packet.s2c.play.CommandSuggestionsS2CPacket;
|
||||
import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
|
||||
import net.minecraft.network.packet.s2c.play.PlayerRemoveS2CPacket;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class Players extends Listener {
|
||||
public List<MutablePlayerListEntry> list = new ArrayList<>();
|
||||
|
||||
public static Players INSTANCE = new Players();
|
||||
|
||||
public Players () {
|
||||
ListenerManager.addListener(this);
|
||||
}
|
||||
|
||||
public void init () {}
|
||||
|
||||
@Override
|
||||
public void packetReceived (Packet packet) {
|
||||
if (packet instanceof PlayerListS2CPacket) packetReceived((PlayerListS2CPacket) packet);
|
||||
else if (packet instanceof PlayerRemoveS2CPacket) packetReceived((PlayerRemoveS2CPacket) packet);
|
||||
}
|
||||
|
||||
public void packetReceived (PlayerListS2CPacket packet) {
|
||||
try {
|
||||
for (PlayerListS2CPacket.Action action : packet.getActions()) {
|
||||
for (PlayerListS2CPacket.Entry entry : packet.getEntries()) {
|
||||
if (action == PlayerListS2CPacket.Action.ADD_PLAYER) addPlayer(entry);
|
||||
// else if (action == PlayerListS2CPacket.Action.INITIALIZE_CHAT) initializeChat(entry);
|
||||
else if (action == PlayerListS2CPacket.Action.UPDATE_GAME_MODE) updateGamemode(entry);
|
||||
// else if (action == PlayerListS2CPacket.Action.UPDATE_LISTED) updateListed(entry);
|
||||
else if (action == PlayerListS2CPacket.Action.UPDATE_LATENCY) updateLatency(entry);
|
||||
else if (action == PlayerListS2CPacket.Action.UPDATE_DISPLAY_NAME) updateDisplayName(entry);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void packetReceived (PlayerRemoveS2CPacket packet) {
|
||||
for (UUID uuid : packet.profileIds()) {
|
||||
removePlayer(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
public final MutablePlayerListEntry getEntry (UUID uuid) {
|
||||
for (MutablePlayerListEntry candidate : list) {
|
||||
if (candidate.profile().getId().equals(uuid)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public final MutablePlayerListEntry getEntry (String username) {
|
||||
for (MutablePlayerListEntry candidate : list) {
|
||||
if (candidate.profile().getName().equals(username)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public final MutablePlayerListEntry getEntry (Text displayName) {
|
||||
for (MutablePlayerListEntry candidate : list) {
|
||||
if (candidate.displayName() != null && candidate.displayName().equals(displayName)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private MutablePlayerListEntry getEntry (PlayerListS2CPacket.Entry other) {
|
||||
return getEntry(other.profile().getId());
|
||||
}
|
||||
|
||||
private void addPlayer (PlayerListS2CPacket.Entry newEntry) {
|
||||
final MutablePlayerListEntry duplicate = getEntry(newEntry);
|
||||
if (duplicate != null) list.remove(duplicate);
|
||||
|
||||
list.add(new MutablePlayerListEntry(newEntry));
|
||||
}
|
||||
|
||||
private void updateGamemode (PlayerListS2CPacket.Entry newEntry) {
|
||||
final MutablePlayerListEntry target = getEntry(newEntry);
|
||||
if (target == null) return;
|
||||
|
||||
target.gamemode(newEntry.gameMode());
|
||||
}
|
||||
|
||||
private void updateLatency (PlayerListS2CPacket.Entry newEntry) {
|
||||
final MutablePlayerListEntry target = getEntry(newEntry);
|
||||
if (target == null) return;
|
||||
|
||||
target.latency(newEntry.latency());
|
||||
}
|
||||
|
||||
private void updateDisplayName (PlayerListS2CPacket.Entry newEntry) {
|
||||
final MutablePlayerListEntry target = getEntry(newEntry);
|
||||
if (target == null) return;
|
||||
|
||||
target.displayName(newEntry.displayName());
|
||||
}
|
||||
|
||||
private void removePlayer (UUID uuid) {
|
||||
final MutablePlayerListEntry target = getEntry(uuid);
|
||||
if (target == null) return;
|
||||
|
||||
final CompletableFuture<CommandSuggestionsS2CPacket> future = TabComplete.INSTANCE.complete("/scoreboard players add ");
|
||||
|
||||
if (future == null) return;
|
||||
|
||||
future.thenApply(packet -> {
|
||||
final Suggestions matches = packet.getSuggestions();
|
||||
final String username = target.profile().getName();
|
||||
|
||||
for (int i = 0; i < matches.getList().size(); i++) {
|
||||
final Suggestion suggestion = matches.getList().get(i);
|
||||
|
||||
final Message tooltip = suggestion.getTooltip();
|
||||
if (tooltip != null || !suggestion.getText().equals(username)) continue;
|
||||
return packet;
|
||||
}
|
||||
|
||||
list.remove(target);
|
||||
return packet;
|
||||
});
|
||||
}
|
||||
}
|
161
src/main/java/land/chipmunk/chipmunkmod/modules/RainbowName.java
Normal file
161
src/main/java/land/chipmunk/chipmunkmod/modules/RainbowName.java
Normal file
|
@ -0,0 +1,161 @@
|
|||
package land.chipmunk.chipmunkmod.modules;
|
||||
|
||||
import land.chipmunk.chipmunkmod.util.ColorUtilities;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class RainbowName {
|
||||
private final MinecraftClient client;
|
||||
|
||||
public static final RainbowName INSTANCE = new RainbowName(MinecraftClient.getInstance());
|
||||
|
||||
private static final String BUKKIT_COLOR_CODES = "123456789abcdefklmorx";
|
||||
private static final String TEAM_NAME_CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.+";
|
||||
|
||||
private Timer timer = null;
|
||||
|
||||
@Getter @Setter private boolean enabled = false;
|
||||
|
||||
private String[] team;
|
||||
|
||||
@Getter @Setter private String displayName;
|
||||
|
||||
private int startHue = 0;
|
||||
|
||||
public void init () {
|
||||
final TimerTask task = new TimerTask() {
|
||||
public void run () {
|
||||
tick();
|
||||
}
|
||||
};
|
||||
|
||||
if (timer != null) cleanup();
|
||||
|
||||
timer = new Timer();
|
||||
timer.schedule(task, 0, 75);
|
||||
}
|
||||
|
||||
private String[] generateColorCodes(int length) {
|
||||
String SALTCHARS = BUKKIT_COLOR_CODES;
|
||||
StringBuilder salt = new StringBuilder();
|
||||
Random rnd = new Random();
|
||||
while (salt.length() < length) { // length of the random string.
|
||||
int index = (int) (rnd.nextFloat() * SALTCHARS.length());
|
||||
salt.append(SALTCHARS.charAt(index));
|
||||
}
|
||||
String saltStr = salt.toString();
|
||||
return saltStr.split("");
|
||||
}
|
||||
|
||||
private String generateUsername (String[] codes) {
|
||||
StringBuilder string = new StringBuilder();
|
||||
for (String code : codes) string.append("&").append(code);
|
||||
return string.toString();
|
||||
}
|
||||
|
||||
private String generateUsername (int _codes) {
|
||||
StringBuilder string = new StringBuilder();
|
||||
|
||||
final String[] codes = generateColorCodes(_codes);
|
||||
|
||||
for (String code : codes) string.append("&").append(code);
|
||||
return string.toString();
|
||||
}
|
||||
|
||||
private String generateUsername (char[] codes, char character) {
|
||||
StringBuilder string = new StringBuilder();
|
||||
for (char code : codes) string.append(character + code);
|
||||
return string.toString();
|
||||
}
|
||||
|
||||
private String[] generateTeamName () {
|
||||
String SALTCHARS = TEAM_NAME_CHARACTERS;
|
||||
StringBuilder salt = new StringBuilder();
|
||||
Random rnd = new Random();
|
||||
while (salt.length() < TEAM_NAME_CHARACTERS.length()) { // length of the random string.
|
||||
int index = (int) (rnd.nextFloat() * SALTCHARS.length());
|
||||
salt.append(SALTCHARS.charAt(index));
|
||||
}
|
||||
String saltStr = salt.toString();
|
||||
return saltStr.split("");
|
||||
}
|
||||
|
||||
public void enable () {
|
||||
final String[] colorCodes = generateColorCodes(8);
|
||||
client.getNetworkHandler().sendChatCommand("extras:username " + generateUsername(colorCodes));
|
||||
|
||||
team = generateTeamName();
|
||||
|
||||
CommandCore.INSTANCE.run("minecraft:team add " + String.join("", team));
|
||||
|
||||
CommandCore.INSTANCE.run("minecraft:execute as " + client.getNetworkHandler().getProfile().getId() + " run team join " + String.join("", team));
|
||||
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
public void disable () {
|
||||
client.getNetworkHandler().sendChatCommand("extras:username " + client.getSession().getUsername());
|
||||
|
||||
CommandCore.INSTANCE.run("minecraft:team remove " + String.join("", team));
|
||||
team = null;
|
||||
|
||||
CommandCore.INSTANCE.run("essentials:nick " + client.getSession().getUsername() + " off");
|
||||
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
public RainbowName (MinecraftClient client) {
|
||||
this.client = client;
|
||||
this.displayName = client.getSession().getUsername();
|
||||
}
|
||||
|
||||
private void tick () {
|
||||
try {
|
||||
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
|
||||
|
||||
if (networkHandler == null) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!enabled) return;
|
||||
|
||||
int hue = startHue;
|
||||
int increment = (int) (360.0 / Math.max(displayName.length(), 20));
|
||||
|
||||
Component component = Component.empty();
|
||||
StringBuilder essentialsNickname = new StringBuilder();
|
||||
|
||||
for (char character : displayName.toCharArray()) {
|
||||
String color = String.format("%06x", ColorUtilities.hsvToRgb(hue, 100, 100));
|
||||
component = component.append(Component.text(character).color(TextColor.fromHexString("#" + color)));
|
||||
essentialsNickname.append("\u00a7#").append(color).append(character != ' ' ? character : '_');
|
||||
hue = (hue + increment) % 360;
|
||||
}
|
||||
|
||||
CommandCore.INSTANCE.run("minecraft:team modify " + String.join("", team) + " prefix " + GsonComponentSerializer.gson().serialize(component));
|
||||
CommandCore.INSTANCE.run("essentials:nick " + client.getSession().getUsername() + " " + essentialsNickname);
|
||||
|
||||
startHue = (startHue + increment) % 360;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanup () {
|
||||
if (timer == null) return;
|
||||
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
timer = null;
|
||||
}
|
||||
}
|
35
src/main/java/land/chipmunk/chipmunkmod/modules/SelfCare.java
Executable file → Normal file
35
src/main/java/land/chipmunk/chipmunkmod/modules/SelfCare.java
Executable file → Normal file
|
@ -1,17 +1,28 @@
|
|||
package land.chipmunk.chipmunkmod.modules;
|
||||
|
||||
import land.chipmunk.chipmunkmod.listeners.Listener;
|
||||
import land.chipmunk.chipmunkmod.listeners.ListenerManager;
|
||||
import lombok.Setter;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class SelfCare {
|
||||
public class SelfCare extends Listener {
|
||||
private final MinecraftClient client;
|
||||
@Getter private long interval;
|
||||
@Getter private final long interval;
|
||||
|
||||
@Getter @Setter private boolean opEnabled = true;
|
||||
@Getter @Setter private boolean gamemodeEnabled = true;
|
||||
@Getter @Setter private boolean cspyEnabled = true;
|
||||
|
||||
private boolean cspy = false;
|
||||
|
||||
private Timer timer = null;
|
||||
|
||||
|
@ -20,6 +31,8 @@ public class SelfCare {
|
|||
public SelfCare (MinecraftClient client, long interval) {
|
||||
this.client = client;
|
||||
this.interval = interval;
|
||||
|
||||
ListenerManager.addListener(this);
|
||||
}
|
||||
|
||||
public void init () {
|
||||
|
@ -43,6 +56,14 @@ public class SelfCare {
|
|||
timer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void chatMessageReceived (Text message) {
|
||||
final String stringMessage = message.getString();
|
||||
|
||||
if (stringMessage.equals("Successfully enabled CommandSpy")) cspy = true;
|
||||
else if (stringMessage.equals("Successfully disabled CommandSpy")) cspy = false;
|
||||
}
|
||||
|
||||
public void tick () {
|
||||
final ClientPlayerEntity player = client.player;
|
||||
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
|
||||
|
@ -52,17 +73,19 @@ public class SelfCare {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!player.hasPermissionLevel(2)) { if (serverHasCommand("op")) networkHandler.sendChatCommand("op @s[type=player]"); }
|
||||
else if (!client.player.isCreative()) networkHandler.sendChatCommand("gamemode creative");
|
||||
if (player != null && !player.hasPermissionLevel(2) && opEnabled) { if (serverHasCommand("op")) networkHandler.sendChatCommand("op @s[type=player]"); }
|
||||
else if (client.player != null && !client.player.isCreative() && gamemodeEnabled) networkHandler.sendChatCommand("gamemode creative");
|
||||
else if (!cspy && cspyEnabled) { if (serverHasCommand("c")) networkHandler.sendChatCommand("c on"); }
|
||||
}
|
||||
|
||||
// TODO: Move this into a separate class related to server info gathering (and yes, I plan on making this d y n a m i c and require little to no configuration for most servers)
|
||||
private boolean serverHasCommand (String name) {
|
||||
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
|
||||
|
||||
if (networkHandler == null) return false;
|
||||
|
||||
for (CommandNode node : networkHandler.getCommandDispatcher().getRoot().getChildren()) {
|
||||
if (!(node instanceof LiteralCommandNode)) continue;
|
||||
final LiteralCommandNode literal = (LiteralCommandNode) node;
|
||||
if (!(node instanceof LiteralCommandNode literal)) continue;
|
||||
|
||||
if (literal.getLiteral().equals(name)) return true;
|
||||
}
|
||||
|
|
224
src/main/java/land/chipmunk/chipmunkmod/modules/SongPlayer.java
Normal file
224
src/main/java/land/chipmunk/chipmunkmod/modules/SongPlayer.java
Normal file
|
@ -0,0 +1,224 @@
|
|||
package land.chipmunk.chipmunkmod.modules;
|
||||
|
||||
import land.chipmunk.chipmunkmod.song.Note;
|
||||
import land.chipmunk.chipmunkmod.song.Song;
|
||||
import land.chipmunk.chipmunkmod.song.SongLoaderException;
|
||||
import land.chipmunk.chipmunkmod.song.SongLoaderThread;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import net.minecraft.sound.SoundCategory;
|
||||
import net.minecraft.sound.SoundEvent;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class SongPlayer {
|
||||
public static final String SELECTOR = "@a[tag=!nomusic,tag=!chipmunkmod_nomusic]";
|
||||
public static File SONG_DIR = new File("songs");
|
||||
static {
|
||||
if (!SONG_DIR.exists()) {
|
||||
SONG_DIR.mkdir();
|
||||
}
|
||||
}
|
||||
|
||||
public static final SongPlayer INSTANCE = new SongPlayer(MinecraftClient.getInstance());
|
||||
|
||||
@Getter @Setter private Song currentSong;
|
||||
@Getter @Setter private LinkedList<Song> songQueue = new LinkedList<>();
|
||||
@Getter @Setter private Timer playTimer;
|
||||
@Getter @Setter private SongLoaderThread loaderThread;
|
||||
private int ticksUntilPausedActionbar = 20;
|
||||
|
||||
@Getter @Setter private boolean useCore = true;
|
||||
|
||||
private final MinecraftClient client;
|
||||
|
||||
public SongPlayer (MinecraftClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
// TODO: Less duplicate code
|
||||
|
||||
public void loadSong (Path location) {
|
||||
if (loaderThread != null) {
|
||||
client.player.sendMessage(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final SongLoaderThread _loaderThread = new SongLoaderThread(location);
|
||||
client.player.sendMessage(Component.translatable("Loading %s", Component.text(location.getFileName().toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
|
||||
_loaderThread.start();
|
||||
loaderThread = _loaderThread;
|
||||
} catch (SongLoaderException e) {
|
||||
client.player.sendMessage(Component.translatable("Failed to load song: %s", e.message()).color(NamedTextColor.RED));
|
||||
loaderThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void loadSong (URL location) {
|
||||
if (loaderThread != null) {
|
||||
client.player.sendMessage(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final SongLoaderThread _loaderThread = new SongLoaderThread(location);
|
||||
client.player.sendMessage(Component.translatable("Loading %s", Component.text(location.toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
|
||||
_loaderThread.start();
|
||||
loaderThread = _loaderThread;
|
||||
} catch (SongLoaderException e) {
|
||||
client.player.sendMessage(Component.translatable("Failed to load song: %s", e.message()).color(NamedTextColor.RED));
|
||||
loaderThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void coreReady () {
|
||||
playTimer = new Timer();
|
||||
|
||||
final TimerTask playTask = new TimerTask() {
|
||||
@Override
|
||||
public void run () {
|
||||
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
|
||||
|
||||
if (networkHandler == null) {
|
||||
disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loaderThread != null && !loaderThread.isAlive()) {
|
||||
if (loaderThread.exception != null) {
|
||||
client.player.sendMessage(Component.translatable("Failed to load song: %s", loaderThread.exception.message()).color(NamedTextColor.RED));
|
||||
} else {
|
||||
songQueue.add(loaderThread.song);
|
||||
client.player.sendMessage(Component.translatable("Added %s to the song queue", Component.empty().append(loaderThread.song.name).color(NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
|
||||
}
|
||||
loaderThread = null;
|
||||
}
|
||||
|
||||
if (currentSong == null) {
|
||||
if (songQueue.size() == 0) return;
|
||||
|
||||
currentSong = songQueue.poll();
|
||||
client.player.sendMessage(Component.translatable("Now playing %s", Component.empty().append(currentSong.name).color(NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
|
||||
currentSong.play();
|
||||
}
|
||||
|
||||
if (currentSong.paused && ticksUntilPausedActionbar-- < 0) return;
|
||||
else ticksUntilPausedActionbar = 20;
|
||||
|
||||
try {
|
||||
if (!useCore) client.player.sendActionBar(generateActionbar());
|
||||
else CommandCore.INSTANCE.run("title " + SELECTOR + " actionbar " + GsonComponentSerializer.gson().serialize(generateActionbar()));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (currentSong.paused) return;
|
||||
|
||||
handlePlaying();
|
||||
|
||||
if (currentSong.finished()) {
|
||||
client.player.sendMessage(Component.translatable("Finished playing %s", Component.empty().append(currentSong.name).color(NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
|
||||
currentSong = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
playTimer.schedule(playTask, 50, 50);
|
||||
|
||||
if (currentSong != null) currentSong.play();
|
||||
}
|
||||
|
||||
public Component generateActionbar () {
|
||||
final ClientPlayerEntity player = client.player;
|
||||
|
||||
Component component = Component.empty()
|
||||
.append(Component.translatable("%s", player.getName()).color(NamedTextColor.GREEN))
|
||||
.append(Component.translatable(" | ", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.translatable("Now playing %s", Component.empty().append(currentSong.name).color(NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN))
|
||||
.append(Component.translatable(" | ", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.translatable("%s / %s", formatTime(currentSong.time).color(NamedTextColor.GREEN), formatTime(currentSong.length).color(NamedTextColor.GREEN)).color(NamedTextColor.GRAY))
|
||||
.append(Component.translatable(" | ", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.translatable("%s / %s", Component.text(currentSong.position, NamedTextColor.GREEN), Component.text(currentSong.size(), NamedTextColor.GREEN)).color(NamedTextColor.GRAY));
|
||||
|
||||
if (currentSong.paused) {
|
||||
return component
|
||||
.append(Component.translatable(" | ", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.translatable("Paused", NamedTextColor.DARK_GREEN));
|
||||
}
|
||||
|
||||
if (currentSong.looping) {
|
||||
if (currentSong.loopCount > 0) {
|
||||
return component
|
||||
.append(Component.translatable(" | ", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.translatable("Looping (%s/%s)", Component.text(currentSong.currentLoop), Component.text(currentSong.loopCount)).color(NamedTextColor.DARK_GREEN));
|
||||
}
|
||||
|
||||
return component
|
||||
.append(Component.translatable(" | ", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.translatable("Looping", NamedTextColor.DARK_GREEN));
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
public Component formatTime (long millis) {
|
||||
final int seconds = (int) millis / 1000;
|
||||
|
||||
final String minutePart = String.valueOf(seconds / 60);
|
||||
final String unpaddedSecondPart = String.valueOf(seconds % 60);
|
||||
|
||||
return Component.translatable(
|
||||
"%s:%s",
|
||||
Component.text(minutePart),
|
||||
Component.text(unpaddedSecondPart.length() < 2 ? "0" + unpaddedSecondPart : unpaddedSecondPart)
|
||||
);
|
||||
}
|
||||
|
||||
public void stopPlaying () {
|
||||
currentSong = null;
|
||||
}
|
||||
|
||||
public void disconnected () {
|
||||
playTimer.cancel();
|
||||
playTimer.purge();
|
||||
|
||||
if (currentSong != null) currentSong.pause();
|
||||
}
|
||||
|
||||
public void handlePlaying () {
|
||||
currentSong.advanceTime();
|
||||
while (currentSong.reachedNextNote()) {
|
||||
final Note note = currentSong.getNextNote();
|
||||
|
||||
final float floatingPitch = (float) Math.pow(2, (note.pitch - 12) / 12.0);
|
||||
|
||||
try {
|
||||
if (!useCore) {
|
||||
if (floatingPitch < 0 || floatingPitch > 2) return;
|
||||
|
||||
final String[] thing = note.instrument.sound.split(":");
|
||||
|
||||
if (thing[1] == null) return; // idk if this can be null but ill just protect it for now i guess
|
||||
|
||||
client.player.playSound(SoundEvent.of(Identifier.of(thing[0], thing[1])), SoundCategory.RECORDS, note.volume, floatingPitch);
|
||||
} else CommandCore.INSTANCE.run("execute as " + SELECTOR + " at @s run playsound " + note.instrument.sound + " record @s ~ ~ ~ " + note.volume + " " + floatingPitch);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package land.chipmunk.chipmunkmod.modules;
|
||||
|
||||
import land.chipmunk.chipmunkmod.listeners.Listener;
|
||||
import land.chipmunk.chipmunkmod.listeners.ListenerManager;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.network.packet.c2s.play.RequestCommandCompletionsC2SPacket;
|
||||
import net.minecraft.network.packet.s2c.play.CommandSuggestionsS2CPacket;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class TabComplete extends Listener {
|
||||
private final MinecraftClient client;
|
||||
|
||||
private int nextTransactionId = 0;
|
||||
private final Map<Integer, CompletableFuture<CommandSuggestionsS2CPacket>> transactions = new HashMap<>();
|
||||
|
||||
public static TabComplete INSTANCE = new TabComplete(MinecraftClient.getInstance());
|
||||
|
||||
@Getter private boolean loggedIn = false;
|
||||
|
||||
public TabComplete (MinecraftClient client) {
|
||||
this.client = client;
|
||||
ListenerManager.addListener(this);
|
||||
}
|
||||
|
||||
public CompletableFuture<CommandSuggestionsS2CPacket> complete (String command) {
|
||||
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
|
||||
|
||||
if (networkHandler == null) return null;
|
||||
|
||||
final ClientConnection connection = networkHandler.getConnection();
|
||||
|
||||
if (connection == null) return null;
|
||||
|
||||
final int transactionId = nextTransactionId++;
|
||||
if (nextTransactionId > Integer.MAX_VALUE) nextTransactionId = 0; // ? Can and should I use negative numbers too?
|
||||
connection.send(new RequestCommandCompletionsC2SPacket(transactionId, command));
|
||||
|
||||
final CompletableFuture<CommandSuggestionsS2CPacket> future = new CompletableFuture<>();
|
||||
transactions.put(transactionId, future);
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packetReceived (Packet packet) {
|
||||
if (packet instanceof CommandSuggestionsS2CPacket) packetReceived((CommandSuggestionsS2CPacket) packet);
|
||||
}
|
||||
|
||||
public void packetReceived (CommandSuggestionsS2CPacket packet) {
|
||||
final CompletableFuture<CommandSuggestionsS2CPacket> future = transactions.get(packet.getCompletionId());
|
||||
|
||||
if (future == null) return;
|
||||
future.complete(packet);
|
||||
transactions.remove(future);
|
||||
}
|
||||
}
|
48
src/main/java/land/chipmunk/chipmunkmod/song/Instrument.java
Normal file
48
src/main/java/land/chipmunk/chipmunkmod/song/Instrument.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
package land.chipmunk.chipmunkmod.song;
|
||||
|
||||
public class Instrument {
|
||||
public static final Instrument HARP = new Instrument(0, "harp", 54);
|
||||
public static final Instrument BASEDRUM = new Instrument(1, "basedrum", 0);
|
||||
public static final Instrument SNARE = new Instrument(2, "snare", 0);
|
||||
public static final Instrument HAT = new Instrument(3, "hat", 0);
|
||||
public static final Instrument BASS = new Instrument(4, "bass", 30);
|
||||
public static final Instrument FLUTE = new Instrument(5, "flute", 66);
|
||||
public static final Instrument BELL = new Instrument(6, "bell", 78);
|
||||
public static final Instrument GUITAR = new Instrument(7, "guitar", 42);
|
||||
public static final Instrument CHIME = new Instrument(8, "chime", 78);
|
||||
public static final Instrument XYLOPHONE = new Instrument(9, "xylophone", 78);
|
||||
public static final Instrument IRON_XYLOPHONE = new Instrument(10, "iron_xylophone", 54);
|
||||
public static final Instrument COW_BELL = new Instrument(11, "cow_bell", 66);
|
||||
public static final Instrument DIDGERIDOO = new Instrument(12, "didgeridoo", 30);
|
||||
public static final Instrument BIT = new Instrument(13, "bit", 54);
|
||||
public static final Instrument BANJO = new Instrument(14, "banjo", 54);
|
||||
public static final Instrument PLING = new Instrument(15, "pling", 54);
|
||||
|
||||
public final int id;
|
||||
public final String name;
|
||||
public final int offset;
|
||||
public final String sound;
|
||||
|
||||
private Instrument (int id, String name, int offset, String sound) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.offset = offset;
|
||||
this.sound = name;
|
||||
}
|
||||
|
||||
private Instrument (int id, String name, int offset) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.offset = offset;
|
||||
this.sound = "minecraft:block.note_block." + name;
|
||||
}
|
||||
|
||||
public static Instrument of (String sound) {
|
||||
return new Instrument(-1, null, 0, sound);
|
||||
}
|
||||
|
||||
private static Instrument[] values = {HARP, BASEDRUM, SNARE, HAT, BASS, FLUTE, BELL, GUITAR, CHIME, XYLOPHONE, IRON_XYLOPHONE, COW_BELL, DIDGERIDOO, BIT, BANJO, PLING};
|
||||
public static Instrument fromId (int id) {
|
||||
return values[id];
|
||||
}
|
||||
}
|
373
src/main/java/land/chipmunk/chipmunkmod/song/MidiConverter.java
Normal file
373
src/main/java/land/chipmunk/chipmunkmod/song/MidiConverter.java
Normal file
|
@ -0,0 +1,373 @@
|
|||
package land.chipmunk.chipmunkmod.song;
|
||||
|
||||
import land.chipmunk.chipmunkmod.util.DownloadUtilities;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.sound.midi.*;
|
||||
|
||||
public class MidiConverter {
|
||||
public static final int SET_INSTRUMENT = 0xC0;
|
||||
public static final int SET_TEMPO = 0x51;
|
||||
public static final int NOTE_ON = 0x90;
|
||||
public static final int NOTE_OFF = 0x80;
|
||||
|
||||
public static Song getSongFromUrl(URL url) throws IOException, InvalidMidiDataException, URISyntaxException, NoSuchAlgorithmException, KeyManagementException {
|
||||
Sequence sequence = MidiSystem.getSequence(DownloadUtilities.DownloadToInputStream(url, 5*1024*1024));
|
||||
return getSong(sequence, Paths.get(url.toURI().getPath()).getFileName().toString());
|
||||
}
|
||||
|
||||
public static Song getSongFromFile(File file) throws InvalidMidiDataException, IOException {
|
||||
Sequence sequence = MidiSystem.getSequence(file);
|
||||
return getSong(sequence, file.getName());
|
||||
}
|
||||
|
||||
public static Song getSongFromBytes(byte[] bytes, String name) throws InvalidMidiDataException, IOException {
|
||||
Sequence sequence = MidiSystem.getSequence(new ByteArrayInputStream(bytes));
|
||||
return getSong(sequence, name);
|
||||
}
|
||||
|
||||
public static Song getSong(Sequence sequence, String name) {
|
||||
Song song = new Song(name);
|
||||
|
||||
long tpq = sequence.getResolution();
|
||||
|
||||
ArrayList<MidiEvent> tempoEvents = new ArrayList<>();
|
||||
for (Track track : sequence.getTracks()) {
|
||||
for (int i = 0; i < track.size(); i++) {
|
||||
MidiEvent event = track.get(i);
|
||||
MidiMessage message = event.getMessage();
|
||||
if (message instanceof MetaMessage) {
|
||||
MetaMessage mm = (MetaMessage) message;
|
||||
if (mm.getType() == SET_TEMPO) {
|
||||
tempoEvents.add(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(tempoEvents, (a, b) -> Long.compare(a.getTick(), b.getTick()));
|
||||
|
||||
for (Track track : sequence.getTracks()) {
|
||||
|
||||
long microTime = 0;
|
||||
int[] ids = new int[16];
|
||||
int mpq = 500000;
|
||||
int tempoEventIdx = 0;
|
||||
long prevTick = 0;
|
||||
|
||||
for (int i = 0; i < track.size(); i++) {
|
||||
MidiEvent event = track.get(i);
|
||||
MidiMessage message = event.getMessage();
|
||||
|
||||
while (tempoEventIdx < tempoEvents.size() && event.getTick() > tempoEvents.get(tempoEventIdx).getTick()) {
|
||||
long deltaTick = tempoEvents.get(tempoEventIdx).getTick() - prevTick;
|
||||
prevTick = tempoEvents.get(tempoEventIdx).getTick();
|
||||
microTime += (mpq/tpq) * deltaTick;
|
||||
|
||||
MetaMessage mm = (MetaMessage) tempoEvents.get(tempoEventIdx).getMessage();
|
||||
byte[] data = mm.getData();
|
||||
int new_mpq = (data[2]&0xFF) | ((data[1]&0xFF)<<8) | ((data[0]&0xFF)<<16);
|
||||
if (new_mpq != 0) mpq = new_mpq;
|
||||
tempoEventIdx++;
|
||||
}
|
||||
|
||||
if (message instanceof ShortMessage) {
|
||||
ShortMessage sm = (ShortMessage) message;
|
||||
if (sm.getCommand() == SET_INSTRUMENT) {
|
||||
ids[sm.getChannel()] = sm.getData1();
|
||||
}
|
||||
else if (sm.getCommand() == NOTE_ON) {
|
||||
if (sm.getData2() == 0) continue;
|
||||
int pitch = sm.getData1();
|
||||
int velocity = sm.getData2();
|
||||
long deltaTick = event.getTick() - prevTick;
|
||||
prevTick = event.getTick();
|
||||
microTime += (mpq/tpq) * deltaTick;
|
||||
|
||||
Note note;
|
||||
if (sm.getChannel() == 9) {
|
||||
note = getMidiPercussionNote(pitch, velocity, microTime);
|
||||
}
|
||||
else {
|
||||
note = getMidiInstrumentNote(ids[sm.getChannel()], pitch, velocity, microTime);
|
||||
}
|
||||
if (note != null) {
|
||||
song.add(note);
|
||||
}
|
||||
|
||||
long time = microTime / 1000L;
|
||||
if (time > song.length) {
|
||||
song.length = time;
|
||||
}
|
||||
}
|
||||
else if (sm.getCommand() == NOTE_OFF) {
|
||||
long deltaTick = event.getTick() - prevTick;
|
||||
prevTick = event.getTick();
|
||||
microTime += (mpq/tpq) * deltaTick;
|
||||
long time = microTime / 1000L;
|
||||
if (time > song.length) {
|
||||
song.length = time;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
song.sort();
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
public static Note getMidiInstrumentNote(int midiInstrument, int midiPitch, int velocity, long microTime) {
|
||||
Instrument instrument = null;
|
||||
Instrument[] instrumentList = instrumentMap.get(midiInstrument);
|
||||
if (instrumentList != null) {
|
||||
for (Instrument candidateInstrument : instrumentList) {
|
||||
if (midiPitch >= candidateInstrument.offset && midiPitch <= candidateInstrument.offset+24) {
|
||||
instrument = candidateInstrument;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (instrument == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int pitch = midiPitch-instrument.offset;
|
||||
float volume = (float) velocity / 127.0f;
|
||||
long time = microTime / 1000L;
|
||||
|
||||
return new Note(instrument, pitch, volume, time);
|
||||
}
|
||||
|
||||
private static Note getMidiPercussionNote (int midiPitch, int velocity, long microTime) {
|
||||
if (percussionMap.containsKey(midiPitch)) {
|
||||
int noteId = percussionMap.get(midiPitch);
|
||||
int pitch = noteId % 25;
|
||||
float volume = (float) velocity / 127.0f;
|
||||
Instrument instrument = Instrument.fromId(noteId / 25);
|
||||
long time = microTime / 1000L;
|
||||
|
||||
return new Note(instrument, pitch, volume, time);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static HashMap<Integer, Instrument[]> instrumentMap = new HashMap<>();
|
||||
static {
|
||||
// Piano (HARP BASS BELL)
|
||||
instrumentMap.put(0, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Acoustic Grand Piano
|
||||
instrumentMap.put(1, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Bright Acoustic Piano
|
||||
instrumentMap.put(2, new Instrument[]{Instrument.BIT, Instrument.DIDGERIDOO, Instrument.BELL}); // Electric Grand Piano
|
||||
instrumentMap.put(3, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Honky-tonk Piano
|
||||
instrumentMap.put(4, new Instrument[]{Instrument.BIT, Instrument.DIDGERIDOO, Instrument.BELL}); // Electric Piano 1
|
||||
instrumentMap.put(5, new Instrument[]{Instrument.BIT, Instrument.DIDGERIDOO, Instrument.BELL}); // Electric Piano 2
|
||||
instrumentMap.put(6, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Harpsichord
|
||||
instrumentMap.put(7, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Clavinet
|
||||
|
||||
// Chromatic Percussion (IRON_XYLOPHONE XYLOPHONE BASS)
|
||||
instrumentMap.put(8, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE}); // Celesta
|
||||
instrumentMap.put(9, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE}); // Glockenspiel
|
||||
instrumentMap.put(10, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE}); // Music Box
|
||||
instrumentMap.put(11, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE}); // Vibraphone
|
||||
instrumentMap.put(12, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE}); // Marimba
|
||||
instrumentMap.put(13, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE}); // Xylophone
|
||||
instrumentMap.put(14, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE}); // Tubular Bells
|
||||
instrumentMap.put(15, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE}); // Dulcimer
|
||||
|
||||
// Organ (BIT DIDGERIDOO BELL)
|
||||
instrumentMap.put(16, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Drawbar Organ
|
||||
instrumentMap.put(17, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Percussive Organ
|
||||
instrumentMap.put(18, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Rock Organ
|
||||
instrumentMap.put(19, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Church Organ
|
||||
instrumentMap.put(20, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Reed Organ
|
||||
instrumentMap.put(21, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Accordian
|
||||
instrumentMap.put(22, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Harmonica
|
||||
instrumentMap.put(23, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Tango Accordian
|
||||
|
||||
// Guitar (BIT DIDGERIDOO BELL)
|
||||
instrumentMap.put(24, new Instrument[]{Instrument.GUITAR, Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Acoustic Guitar (nylon)
|
||||
instrumentMap.put(25, new Instrument[]{Instrument.GUITAR, Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Acoustic Guitar (steel)
|
||||
instrumentMap.put(26, new Instrument[]{Instrument.GUITAR, Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Electric Guitar (jazz)
|
||||
instrumentMap.put(27, new Instrument[]{Instrument.GUITAR, Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Electric Guitar (clean)
|
||||
instrumentMap.put(28, new Instrument[]{Instrument.GUITAR, Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Electric Guitar (muted)
|
||||
instrumentMap.put(29, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Overdriven Guitar
|
||||
instrumentMap.put(30, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Distortion Guitar
|
||||
instrumentMap.put(31, new Instrument[]{Instrument.GUITAR, Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Guitar Harmonics
|
||||
|
||||
// Bass
|
||||
instrumentMap.put(32, new Instrument[]{Instrument.BASS, Instrument.HARP, Instrument.BELL}); // Acoustic Bass
|
||||
instrumentMap.put(33, new Instrument[]{Instrument.BASS, Instrument.HARP, Instrument.BELL}); // Electric Bass (finger)
|
||||
instrumentMap.put(34, new Instrument[]{Instrument.BASS, Instrument.HARP, Instrument.BELL}); // Electric Bass (pick)
|
||||
instrumentMap.put(35, new Instrument[]{Instrument.BASS, Instrument.HARP, Instrument.BELL}); // Fretless Bass
|
||||
instrumentMap.put(36, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Slap Bass 1
|
||||
instrumentMap.put(37, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Slap Bass 2
|
||||
instrumentMap.put(38, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Synth Bass 1
|
||||
instrumentMap.put(39, new Instrument[]{Instrument.DIDGERIDOO, Instrument.BIT, Instrument.XYLOPHONE}); // Synth Bass 2
|
||||
|
||||
// Strings
|
||||
instrumentMap.put(40, new Instrument[]{Instrument.FLUTE, Instrument.GUITAR, Instrument.BASS, Instrument.BELL}); // Violin
|
||||
instrumentMap.put(41, new Instrument[]{Instrument.FLUTE, Instrument.GUITAR, Instrument.BASS, Instrument.BELL}); // Viola
|
||||
instrumentMap.put(42, new Instrument[]{Instrument.FLUTE, Instrument.GUITAR, Instrument.BASS, Instrument.BELL}); // Cello
|
||||
instrumentMap.put(43, new Instrument[]{Instrument.FLUTE, Instrument.GUITAR, Instrument.BASS, Instrument.BELL}); // Contrabass
|
||||
instrumentMap.put(44, new Instrument[]{Instrument.BIT, Instrument.DIDGERIDOO, Instrument.BELL}); // Tremolo Strings
|
||||
instrumentMap.put(45, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Pizzicato Strings
|
||||
instrumentMap.put(46, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.CHIME}); // Orchestral Harp
|
||||
instrumentMap.put(47, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Timpani
|
||||
|
||||
// Ensenble
|
||||
instrumentMap.put(48, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // String Ensemble 1
|
||||
instrumentMap.put(49, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // String Ensemble 2
|
||||
instrumentMap.put(50, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Synth Strings 1
|
||||
instrumentMap.put(51, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Synth Strings 2
|
||||
instrumentMap.put(52, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Choir Aahs
|
||||
instrumentMap.put(53, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Voice Oohs
|
||||
instrumentMap.put(54, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Synth Choir
|
||||
instrumentMap.put(55, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL}); // Orchestra Hit
|
||||
|
||||
// Brass
|
||||
instrumentMap.put(56, new Instrument[]{Instrument.BIT, Instrument.DIDGERIDOO, Instrument.BELL});
|
||||
instrumentMap.put(57, new Instrument[]{Instrument.BIT, Instrument.DIDGERIDOO, Instrument.BELL});
|
||||
instrumentMap.put(58, new Instrument[]{Instrument.BIT, Instrument.DIDGERIDOO, Instrument.BELL});
|
||||
instrumentMap.put(59, new Instrument[]{Instrument.BIT, Instrument.DIDGERIDOO, Instrument.BELL});
|
||||
instrumentMap.put(60, new Instrument[]{Instrument.BIT, Instrument.DIDGERIDOO, Instrument.BELL});
|
||||
instrumentMap.put(61, new Instrument[]{Instrument.BIT, Instrument.DIDGERIDOO, Instrument.BELL});
|
||||
instrumentMap.put(62, new Instrument[]{Instrument.BIT, Instrument.DIDGERIDOO, Instrument.BELL});
|
||||
instrumentMap.put(63, new Instrument[]{Instrument.BIT, Instrument.DIDGERIDOO, Instrument.BELL});
|
||||
|
||||
// Reed
|
||||
instrumentMap.put(64, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(65, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(66, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(67, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(68, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(69, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(70, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(71, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
|
||||
// Pipe
|
||||
instrumentMap.put(72, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(73, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(74, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(75, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(76, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(77, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(78, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
instrumentMap.put(79, new Instrument[]{Instrument.FLUTE, Instrument.DIDGERIDOO, Instrument.IRON_XYLOPHONE, Instrument.BELL});
|
||||
|
||||
// Synth Lead
|
||||
instrumentMap.put(80, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(81, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(82, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(83, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(84, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(85, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(86, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(87, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
|
||||
// Synth Pad
|
||||
instrumentMap.put(88, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(89, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(90, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(91, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(92, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(93, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(94, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(95, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
|
||||
// Synth Effects
|
||||
// instrumentMap.put(96, new Instrument[]{});
|
||||
// instrumentMap.put(97, new Instrument[]{});
|
||||
instrumentMap.put(98, new Instrument[]{Instrument.BIT, Instrument.DIDGERIDOO, Instrument.BELL});
|
||||
instrumentMap.put(99, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(100, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(101, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(102, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(103, new Instrument[]{Instrument.HARP, Instrument.BASS, Instrument.BELL});
|
||||
|
||||
// Ethnic
|
||||
instrumentMap.put(104, new Instrument[]{Instrument.BANJO, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(105, new Instrument[]{Instrument.BANJO, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(106, new Instrument[]{Instrument.BANJO, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(107, new Instrument[]{Instrument.BANJO, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(108, new Instrument[]{Instrument.BANJO, Instrument.BASS, Instrument.BELL});
|
||||
instrumentMap.put(109, new Instrument[]{Instrument.HARP, Instrument.DIDGERIDOO, Instrument.BELL});
|
||||
instrumentMap.put(110, new Instrument[]{Instrument.HARP, Instrument.DIDGERIDOO, Instrument.BELL});
|
||||
instrumentMap.put(111, new Instrument[]{Instrument.HARP, Instrument.DIDGERIDOO, Instrument.BELL});
|
||||
|
||||
// Percussive
|
||||
instrumentMap.put(112, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE});
|
||||
instrumentMap.put(113, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE});
|
||||
instrumentMap.put(114, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE});
|
||||
instrumentMap.put(115, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE});
|
||||
instrumentMap.put(116, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE});
|
||||
instrumentMap.put(117, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE});
|
||||
instrumentMap.put(118, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE});
|
||||
instrumentMap.put(119, new Instrument[]{Instrument.IRON_XYLOPHONE, Instrument.BASS, Instrument.XYLOPHONE});
|
||||
}
|
||||
|
||||
public static HashMap<Integer, Integer> percussionMap = new HashMap<>();
|
||||
static {
|
||||
percussionMap.put(35, 10 + 25*Instrument.BASEDRUM.id);
|
||||
percussionMap.put(36, 6 + 25*Instrument.BASEDRUM.id);
|
||||
percussionMap.put(37, 6 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(38, 8 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(39, 6 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(40, 4 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(41, 6 + 25*Instrument.BASEDRUM.id);
|
||||
percussionMap.put(42, 22 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(43, 13 + 25*Instrument.BASEDRUM.id);
|
||||
percussionMap.put(44, 22 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(45, 15 + 25*Instrument.BASEDRUM.id);
|
||||
percussionMap.put(46, 18 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(47, 20 + 25*Instrument.BASEDRUM.id);
|
||||
percussionMap.put(48, 23 + 25*Instrument.BASEDRUM.id);
|
||||
percussionMap.put(49, 17 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(50, 23 + 25*Instrument.BASEDRUM.id);
|
||||
percussionMap.put(51, 24 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(52, 8 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(53, 13 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(54, 18 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(55, 18 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(56, 1 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(57, 13 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(58, 2 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(59, 13 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(60, 9 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(61, 2 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(62, 8 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(63, 22 + 25*Instrument.BASEDRUM.id);
|
||||
percussionMap.put(64, 15 + 25*Instrument.BASEDRUM.id);
|
||||
percussionMap.put(65, 13 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(66, 8 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(67, 8 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(68, 3 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(69, 20 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(70, 23 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(71, 24 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(72, 24 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(73, 17 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(74, 11 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(75, 18 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(76, 9 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(77, 5 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(78, 22 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(79, 19 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(80, 17 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(81, 22 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(82, 22 + 25*Instrument.SNARE.id);
|
||||
percussionMap.put(83, 24 + 25*Instrument.CHIME.id);
|
||||
percussionMap.put(84, 24 + 25*Instrument.CHIME.id);
|
||||
percussionMap.put(85, 21 + 25*Instrument.HAT.id);
|
||||
percussionMap.put(86, 14 + 25*Instrument.BASEDRUM.id);
|
||||
percussionMap.put(87, 7 + 25*Instrument.BASEDRUM.id);
|
||||
}
|
||||
}
|
201
src/main/java/land/chipmunk/chipmunkmod/song/NBSConverter.java
Normal file
201
src/main/java/land/chipmunk/chipmunkmod/song/NBSConverter.java
Normal file
|
@ -0,0 +1,201 @@
|
|||
package land.chipmunk.chipmunkmod.song;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class NBSConverter {
|
||||
public static Instrument[] instrumentIndex = new Instrument[] {
|
||||
Instrument.HARP,
|
||||
Instrument.BASS,
|
||||
Instrument.BASEDRUM,
|
||||
Instrument.SNARE,
|
||||
Instrument.HAT,
|
||||
Instrument.GUITAR,
|
||||
Instrument.FLUTE,
|
||||
Instrument.BELL,
|
||||
Instrument.CHIME,
|
||||
Instrument.XYLOPHONE,
|
||||
Instrument.IRON_XYLOPHONE,
|
||||
Instrument.COW_BELL,
|
||||
Instrument.DIDGERIDOO,
|
||||
Instrument.BIT,
|
||||
Instrument.BANJO,
|
||||
Instrument.PLING,
|
||||
};
|
||||
|
||||
private static class NBSNote {
|
||||
public int tick;
|
||||
public short layer;
|
||||
public byte instrument;
|
||||
public byte key;
|
||||
public byte velocity = 100;
|
||||
public byte panning = 100;
|
||||
public short pitch = 0;
|
||||
}
|
||||
|
||||
private static class NBSLayer {
|
||||
public String name;
|
||||
public byte lock = 0;
|
||||
public byte volume;
|
||||
public byte stereo = 100;
|
||||
}
|
||||
|
||||
private static class NBSCustomInstrument {
|
||||
public String name;
|
||||
public String file;
|
||||
public byte pitch = 0;
|
||||
public boolean key = false;
|
||||
}
|
||||
|
||||
public static Song getSongFromBytes(byte[] bytes, String fileName) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
short songLength = 0;
|
||||
byte format = 0;
|
||||
byte vanillaInstrumentCount = 0;
|
||||
songLength = buffer.getShort(); // If it's not 0, then it uses the old format
|
||||
if (songLength == 0) {
|
||||
format = buffer.get();
|
||||
}
|
||||
|
||||
if (format >= 1) {
|
||||
vanillaInstrumentCount = buffer.get();
|
||||
}
|
||||
if (format >= 3) {
|
||||
songLength = buffer.getShort();
|
||||
}
|
||||
|
||||
short layerCount = buffer.getShort();
|
||||
String songName = getString(buffer, bytes.length);
|
||||
String songAuthor = getString(buffer, bytes.length);
|
||||
String songOriginalAuthor = getString(buffer, bytes.length);
|
||||
String songDescription = getString(buffer, bytes.length);
|
||||
short tempo = buffer.getShort();
|
||||
byte autoSaving = buffer.get();
|
||||
byte autoSavingDuration = buffer.get();
|
||||
byte timeSignature = buffer.get();
|
||||
int minutesSpent = buffer.getInt();
|
||||
int leftClicks = buffer.getInt();
|
||||
int rightClicks = buffer.getInt();
|
||||
int blocksAdded = buffer.getInt();
|
||||
int blocksRemoved = buffer.getInt();
|
||||
String origFileName = getString(buffer, bytes.length);
|
||||
|
||||
byte loop = 0;
|
||||
byte maxLoopCount = 0;
|
||||
short loopStartTick = 0;
|
||||
if (format >= 4) {
|
||||
loop = buffer.get();
|
||||
maxLoopCount = buffer.get();
|
||||
loopStartTick = buffer.getShort();
|
||||
}
|
||||
|
||||
ArrayList<NBSNote> nbsNotes = new ArrayList<>();
|
||||
short tick = -1;
|
||||
while (true) {
|
||||
int tickJumps = buffer.getShort();
|
||||
if (tickJumps == 0) break;
|
||||
tick += tickJumps;
|
||||
|
||||
short layer = -1;
|
||||
while (true) {
|
||||
int layerJumps = buffer.getShort();
|
||||
if (layerJumps == 0) break;
|
||||
layer += layerJumps;
|
||||
NBSNote note = new NBSNote();
|
||||
note.tick = tick;
|
||||
note.layer = layer;
|
||||
note.instrument = buffer.get();
|
||||
note.key = buffer.get();
|
||||
if (format >= 4) {
|
||||
note.velocity = buffer.get();
|
||||
note.panning = buffer.get();
|
||||
note.pitch = buffer.getShort();
|
||||
}
|
||||
nbsNotes.add(note);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<NBSLayer> nbsLayers = new ArrayList<>();
|
||||
if (buffer.hasRemaining()) {
|
||||
for (int i=0; i<layerCount; i++) {
|
||||
NBSLayer layer = new NBSLayer();
|
||||
layer.name = getString(buffer, bytes.length);
|
||||
if (format >= 4) {
|
||||
layer.lock = buffer.get();
|
||||
}
|
||||
layer.volume = buffer.get();
|
||||
if (format >= 2) {
|
||||
layer.stereo = buffer.get();
|
||||
}
|
||||
nbsLayers.add(layer);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<NBSCustomInstrument> customInstruments = new ArrayList<>();
|
||||
if (buffer.hasRemaining()) {
|
||||
byte customInstrumentCount = buffer.get();
|
||||
for (int i = 0; i < customInstrumentCount; i++) {
|
||||
NBSCustomInstrument customInstrument = new NBSCustomInstrument();
|
||||
customInstrument.name = getString(buffer, bytes.length);
|
||||
customInstrument.file = getString(buffer, bytes.length);
|
||||
customInstrument.pitch = buffer.get();
|
||||
customInstrument.key = buffer.get() == 0 ? false : true;
|
||||
customInstruments.add(customInstrument);
|
||||
}
|
||||
}
|
||||
|
||||
Song song = new Song(songName.trim().length() > 0 ? songName : fileName);
|
||||
if (loop > 0) {
|
||||
song.looping = true;
|
||||
song.loopPosition = getMilliTime(loopStartTick, tempo);
|
||||
song.loopCount = maxLoopCount;
|
||||
}
|
||||
for (NBSNote note : nbsNotes) {
|
||||
Instrument instrument;
|
||||
int key = note.key;
|
||||
if (note.instrument < instrumentIndex.length) {
|
||||
instrument = instrumentIndex[note.instrument];
|
||||
} else {
|
||||
int index = note.instrument - instrumentIndex.length;
|
||||
if (index >= customInstruments.size()) continue;
|
||||
NBSCustomInstrument customInstrument = customInstruments.get(index);
|
||||
instrument = Instrument.of(customInstrument.name);
|
||||
// key += customInstrument.pitch;
|
||||
}
|
||||
|
||||
if (key < 33 || key > 57) {
|
||||
continue;
|
||||
}
|
||||
|
||||
byte layerVolume = 100;
|
||||
if (nbsLayers.size() > note.layer) {
|
||||
layerVolume = nbsLayers.get(note.layer).volume;
|
||||
}
|
||||
|
||||
int pitch = key-33;
|
||||
song.add(new Note(instrument, pitch, (float) note.velocity * (float) layerVolume / 10000f, getMilliTime(note.tick, tempo)));
|
||||
}
|
||||
|
||||
song.length = song.get(song.size()-1).time + 50;
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
private static String getString (ByteBuffer buffer, int maxSize) throws IOException {
|
||||
int length = buffer.getInt();
|
||||
if (length > maxSize) {
|
||||
throw new IOException("String is too large");
|
||||
}
|
||||
byte arr[] = new byte[length];
|
||||
buffer.get(arr, 0, length);
|
||||
return new String(arr);
|
||||
}
|
||||
|
||||
private static int getMilliTime(int tick, int tempo) {
|
||||
return 1000 * tick * 100 / tempo;
|
||||
}
|
||||
}
|
28
src/main/java/land/chipmunk/chipmunkmod/song/Note.java
Normal file
28
src/main/java/land/chipmunk/chipmunkmod/song/Note.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
package land.chipmunk.chipmunkmod.song;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class Note implements Comparable<Note> {
|
||||
public Instrument instrument;
|
||||
public int pitch;
|
||||
public float volume;
|
||||
public long time;
|
||||
|
||||
@Override
|
||||
public int compareTo(Note other) {
|
||||
if (time < other.time) {
|
||||
return -1;
|
||||
}
|
||||
else if (time > other.time) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int noteId () {
|
||||
return pitch + instrument.id * 25;
|
||||
}
|
||||
}
|
131
src/main/java/land/chipmunk/chipmunkmod/song/Song.java
Normal file
131
src/main/java/land/chipmunk/chipmunkmod/song/Song.java
Normal file
|
@ -0,0 +1,131 @@
|
|||
package land.chipmunk.chipmunkmod.song;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
public class Song {
|
||||
public ArrayList<Note> notes = new ArrayList<>();
|
||||
public Component name;
|
||||
public int position = 0; // Current note index
|
||||
public boolean looping = false;
|
||||
public boolean paused = true;
|
||||
public long startTime = 0; // Start time in millis since unix epoch
|
||||
public long length = 0; // Milliseconds in the song
|
||||
public long time = 0; // Time since start of song
|
||||
public long loopPosition = 0; // Milliseconds into the song to start looping
|
||||
public int loopCount = 0; // Number of times to loop
|
||||
public int currentLoop = 0; // Number of loops so far
|
||||
|
||||
public Song (Component name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Song (String name) {
|
||||
this(Component.text(name));
|
||||
}
|
||||
|
||||
public Note get (int i) {
|
||||
return notes.get(i);
|
||||
}
|
||||
|
||||
public void add (Note e) {
|
||||
notes.add(e);
|
||||
}
|
||||
|
||||
public void sort () {
|
||||
Collections.sort(notes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts playing song (does nothing if already playing)
|
||||
*/
|
||||
public void play () {
|
||||
if (paused) {
|
||||
paused = false;
|
||||
startTime = System.currentTimeMillis() - time;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses song (does nothing if already paused)
|
||||
*/
|
||||
public void pause () {
|
||||
if (!paused) {
|
||||
paused = true;
|
||||
// Recalculates time so that the song will continue playing after the exact point it was paused
|
||||
advanceTime();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTime (long t) {
|
||||
time = t;
|
||||
startTime = System.currentTimeMillis() - time;
|
||||
position = 0;
|
||||
while (position < notes.size() && notes.get(position).time < t) {
|
||||
position++;
|
||||
}
|
||||
}
|
||||
|
||||
public void advanceTime () {
|
||||
time = System.currentTimeMillis() - startTime;
|
||||
}
|
||||
|
||||
public boolean reachedNextNote () {
|
||||
if (position < notes.size()) {
|
||||
return notes.get(position).time <= time;
|
||||
} else {
|
||||
if (time > length && shouldLoop()) {
|
||||
loop();
|
||||
if (position < notes.size()) {
|
||||
return notes.get(position).time <= time;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Note getNextNote () {
|
||||
if (position >= notes.size()) {
|
||||
if (shouldLoop()) {
|
||||
loop();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return notes.get(position++);
|
||||
}
|
||||
|
||||
public boolean finished () {
|
||||
return time > length && !shouldLoop();
|
||||
}
|
||||
|
||||
private void loop () {
|
||||
position = 0;
|
||||
startTime += length - loopPosition;
|
||||
time -= length - loopPosition;
|
||||
while (position < notes.size() && notes.get(position).time < loopPosition) {
|
||||
position++;
|
||||
}
|
||||
currentLoop++;
|
||||
}
|
||||
|
||||
private boolean shouldLoop () {
|
||||
if (looping) {
|
||||
if (loopCount == 0) {
|
||||
return true;
|
||||
} else {
|
||||
return currentLoop < loopCount;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int size () {
|
||||
return notes.size();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package land.chipmunk.chipmunkmod.song;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import lombok.Getter;
|
||||
import land.chipmunk.chipmunkmod.util.ComponentUtilities;
|
||||
|
||||
public class SongLoaderException extends Exception {
|
||||
@Getter private final Component message;
|
||||
|
||||
public SongLoaderException (Component message) {
|
||||
super();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public SongLoaderException (Component message, Throwable cause) {
|
||||
super(null, cause);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage () {
|
||||
return ComponentUtilities.stringify(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package land.chipmunk.chipmunkmod.song;
|
||||
|
||||
import land.chipmunk.chipmunkmod.modules.SongPlayer;
|
||||
import land.chipmunk.chipmunkmod.util.DownloadUtilities;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class SongLoaderThread extends Thread {
|
||||
private String location;
|
||||
private File songPath;
|
||||
private URL songUrl;
|
||||
public SongLoaderException exception;
|
||||
public Song song;
|
||||
|
||||
private boolean isUrl = false;
|
||||
|
||||
public SongLoaderThread (URL location) throws SongLoaderException {
|
||||
isUrl = true;
|
||||
songUrl = location;
|
||||
}
|
||||
|
||||
public SongLoaderThread (Path location) throws SongLoaderException {
|
||||
isUrl = false;
|
||||
songPath = location.toFile();
|
||||
}
|
||||
|
||||
public void run () {
|
||||
byte[] bytes;
|
||||
String name;
|
||||
try {
|
||||
if (isUrl) {
|
||||
bytes = DownloadUtilities.DownloadToByteArray(songUrl, 10*1024*1024);
|
||||
name = Paths.get(songUrl.toURI().getPath()).getFileName().toString();
|
||||
} else {
|
||||
bytes = Files.readAllBytes(songPath.toPath());
|
||||
name = songPath.getName();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
exception = new SongLoaderException(Component.text(e.getMessage()), e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
song = MidiConverter.getSongFromBytes(bytes, name);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
if (song == null) {
|
||||
try {
|
||||
song = NBSConverter.getSongFromBytes(bytes, name);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (song == null) {
|
||||
exception = new SongLoaderException(Component.translatable("Invalid song format"));
|
||||
}
|
||||
}
|
||||
|
||||
private File getSongFile (String name) {
|
||||
return new File(SongPlayer.SONG_DIR, name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package land.chipmunk.chipmunkmod.util;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public class ColorUtilities {
|
||||
public static int hsvToRgb (int hue, int saturation, int value) {
|
||||
Color color = Color.getHSBColor(hue / 360.0f, saturation / 100.0f, value / 100.0f);
|
||||
return color.getRGB() & 0xFFFFFF;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package land.chipmunk.chipmunkmod.util;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.SelectorComponent;
|
||||
import net.kyori.adventure.text.KeybindComponent;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ComponentUtilities {
|
||||
private static final Map<String, String> language = loadJsonStringMap("language.json");
|
||||
private static final Map<String, String> keybinds = loadJsonStringMap("keybinds.json");
|
||||
|
||||
public static final Pattern ARG_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?(s|%)");
|
||||
|
||||
private ComponentUtilities () {
|
||||
}
|
||||
|
||||
private static Map<String, String> loadJsonStringMap (String name) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
|
||||
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(name);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
JsonObject json = JsonParser.parseReader(reader).getAsJsonObject();
|
||||
|
||||
for (Map.Entry<String, JsonElement> entry : json.entrySet()) {
|
||||
map.put(entry.getKey(), json.get(entry.getKey()).getAsString());
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static String getOrReturnKey (Map<String, String> map, String key) {
|
||||
return map.containsKey(key) ? map.get(key) : key;
|
||||
}
|
||||
|
||||
public static String stringify (Component message) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append(stringifyPartially(message));
|
||||
|
||||
for (Component child : message.children()) builder.append(stringify(child));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String stringifyPartially (Component message) {
|
||||
if (message instanceof TextComponent) return stringifyPartially((TextComponent) message);
|
||||
if (message instanceof TranslatableComponent) return stringifyPartially((TranslatableComponent) message);
|
||||
if (message instanceof SelectorComponent) return stringifyPartially((SelectorComponent) message);
|
||||
if (message instanceof KeybindComponent) return stringifyPartially((KeybindComponent) message);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String stringifyPartially (TextComponent message) {
|
||||
return message.content();
|
||||
}
|
||||
|
||||
public static String stringifyPartially (TranslatableComponent message) {
|
||||
String format = getOrReturnKey(language, message.key());
|
||||
|
||||
// totallynotskidded™️ from HBot (and changed a bit)
|
||||
Matcher matcher = ARG_PATTERN.matcher(format);
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
int i = 0;
|
||||
while (matcher.find()) {
|
||||
if (matcher.group().equals("%%")) {
|
||||
matcher.appendReplacement(sb, "%");
|
||||
} else {
|
||||
String idxStr = matcher.group(1);
|
||||
int idx = idxStr == null ? i++ : (Integer.parseInt(idxStr) - 1);
|
||||
if (idx >= 0 && idx < message.args().size()) {
|
||||
matcher.appendReplacement(sb, Matcher.quoteReplacement( stringify(message.args().get(idx)) ));
|
||||
} else {
|
||||
matcher.appendReplacement(sb, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String stringifyPartially (SelectorComponent message) {
|
||||
return message.pattern(); // * Client-side selector components are equivalent to text ones, and do NOT list entities.
|
||||
}
|
||||
|
||||
public static String stringifyPartially (KeybindComponent message) {
|
||||
String keybind = message.keybind();
|
||||
Component component = keybinds.containsKey(keybind) ? Component.translatable(keybind) : Component.text(keybind); // TODO: Fix some keys like `key.keyboard.a`
|
||||
return stringifyPartially(component);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package land.chipmunk.chipmunkmod.util;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
public class DownloadUtilities {
|
||||
|
||||
private static class DefaultTrustManager implements X509TrustManager {
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] arg0, String arg1) {}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] arg0, String arg1) {}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] DownloadToByteArray(URL url, int maxSize) throws IOException, KeyManagementException, NoSuchAlgorithmException {
|
||||
SSLContext ctx = SSLContext.getInstance("TLS");
|
||||
ctx.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom());
|
||||
SSLContext.setDefault(ctx);
|
||||
URLConnection conn = url.openConnection();
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(10000);
|
||||
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0");
|
||||
|
||||
try (BufferedInputStream downloadStream = new BufferedInputStream(conn.getInputStream())) {
|
||||
ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
|
||||
byte[] buf = new byte[1024];
|
||||
int n;
|
||||
int tot = 0;
|
||||
while ((n = downloadStream.read(buf)) > 0) {
|
||||
byteArrayStream.write(buf, 0, n);
|
||||
tot += n;
|
||||
if (tot > maxSize) {
|
||||
throw new IOException("File is too large");
|
||||
}
|
||||
if (Thread.interrupted()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return byteArrayStream.toByteArray();
|
||||
}
|
||||
// Closing a ByteArrayInputStream has no effect, so I do not close it.
|
||||
}
|
||||
|
||||
public static InputStream DownloadToInputStream(URL url, int maxSize) throws KeyManagementException, NoSuchAlgorithmException, IOException {
|
||||
return new ByteArrayInputStream(DownloadToByteArray(url, maxSize));
|
||||
}
|
||||
}
|
|
@ -1,15 +1,15 @@
|
|||
package land.chipmunk.chipmunkmod.util;
|
||||
|
||||
public interface Hexadecimal {
|
||||
static String encode (byte b) {
|
||||
return "" + Character.forDigit((b >> 4) & 0xF, 16) + Character.forDigit((b & 0xF), 16);
|
||||
}
|
||||
static String encode (byte b) {
|
||||
return "" + Character.forDigit((b >> 4) & 0xF, 16) + Character.forDigit((b & 0xF), 16);
|
||||
}
|
||||
|
||||
static String encode (byte[] array) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < array.length; i++) sb.append(encode(array[i]));
|
||||
return sb.toString();
|
||||
}
|
||||
static String encode (byte[] array) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < array.length; i++) sb.append(encode(array[i]));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// TODO: Decode
|
||||
// TODO: Decode
|
||||
}
|
||||
|
|
|
@ -3,16 +3,17 @@
|
|||
"minVersion": "0.8",
|
||||
"package": "land.chipmunk.chipmunkmod.mixin",
|
||||
"compatibilityLevel": "JAVA_17",
|
||||
"mixins": [
|
||||
],
|
||||
"client": [
|
||||
"ChatScreenMixin",
|
||||
"ChatHudMixin",
|
||||
"ChatInputSuggestorMixin",
|
||||
"ClientPlayNetworkHandlerAccessor",
|
||||
"ChatScreenMixin",
|
||||
"ClientConnectionMixin",
|
||||
"ClientPlayerEntityMixin",
|
||||
"ClientPlayNetworkHandlerAccessor",
|
||||
"ClientPlayNetworkHandlerMixin",
|
||||
"MinecraftClientAccessor"
|
||||
"MinecraftClientAccessor",
|
||||
"LightmapTextureManagerMixin",
|
||||
"DecoderHandlerMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
|
|
@ -16,5 +16,17 @@
|
|||
"chipmunk": { "prefix": "'", "key": null },
|
||||
"chomens": { "prefix": "*", "key": null },
|
||||
"kittycorp": { "prefix": "^", "key": null }
|
||||
},
|
||||
|
||||
"customChat": {
|
||||
"format": {
|
||||
"translate": "chat.type.text",
|
||||
"with": [
|
||||
{
|
||||
"selector": "USERNAME"
|
||||
},
|
||||
"MESSAGE"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,15 @@
|
|||
"id": "chipmunkmod",
|
||||
"version": "${version}",
|
||||
|
||||
"name": "ChipmunkMod",
|
||||
"description": "A utility mod created for free-op servers with minimal or no restrictions",
|
||||
"name": "ChipmunkMod (chayapak's fork)",
|
||||
"description": "My fork of ChipmunkMod",
|
||||
"authors": [
|
||||
"_ChipMC_"
|
||||
"_ChipMC_",
|
||||
"chayapak"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://chipmunk.land/",
|
||||
"sources": "https://code.chipmunk.land/ChipmunkMC/fabric-mod-mabe"
|
||||
"homepage": "https://chayapak.chipmunk.land/",
|
||||
"sources": "https://code.chipmunk.land/ChomeNS/chipmunkmod"
|
||||
},
|
||||
|
||||
"license": "CC0-1.0",
|
||||
|
|
Loading…
Reference in a new issue