forked from ChomeNS/chipmunkmod
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
|
ignore messy code pls,. .,,.,...,.,.,
|
||||||
|
|
||||||
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.
|
|
|
@ -29,6 +29,8 @@ dependencies {
|
||||||
// Fabric API. This is technically optional, but you probably want it anyway.
|
// Fabric API. This is technically optional, but you probably want it anyway.
|
||||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
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.
|
// 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.
|
// 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
|
# Fabric Properties
|
||||||
# check these on https://fabricmc.net/develop
|
# check these on https://fabricmc.net/develop
|
||||||
minecraft_version=1.19.3
|
minecraft_version=1.19.4
|
||||||
yarn_mappings=1.19.3+build.1
|
yarn_mappings=1.19.4+build.2
|
||||||
loader_version=0.14.11
|
loader_version=0.14.19
|
||||||
|
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
mod_version = 1.0.0
|
mod_version = 1.0.0
|
||||||
|
@ -14,4 +14,5 @@ org.gradle.parallel=true
|
||||||
archives_base_name = chipmunkmod
|
archives_base_name = chipmunkmod
|
||||||
|
|
||||||
# Dependencies
|
# 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;
|
package land.chipmunk.chipmunkmod;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import land.chipmunk.chipmunkmod.data.BlockArea;
|
import land.chipmunk.chipmunkmod.data.BlockArea;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
@ -8,6 +9,7 @@ public class Configuration {
|
||||||
public CommandManager commands = new CommandManager();
|
public CommandManager commands = new CommandManager();
|
||||||
public CommandCore core = new CommandCore();
|
public CommandCore core = new CommandCore();
|
||||||
public Bots bots = new Bots();
|
public Bots bots = new Bots();
|
||||||
|
public CustomChat customChat = new CustomChat();
|
||||||
|
|
||||||
public static class CommandManager {
|
public static class CommandManager {
|
||||||
public String prefix = ".";
|
public String prefix = ".";
|
||||||
|
@ -30,4 +32,8 @@ public class Configuration {
|
||||||
public String prefix;
|
public String prefix;
|
||||||
public String key;
|
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);
|
CoreCommand.register(dispatcher);
|
||||||
UsernameCommand.register(dispatcher);
|
UsernameCommand.register(dispatcher);
|
||||||
ValidateCommand.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;
|
package land.chipmunk.chipmunkmod.mixin;
|
||||||
|
|
||||||
|
import land.chipmunk.chipmunkmod.modules.CustomChat;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
@ -16,6 +17,12 @@ public class ChatScreenMixin {
|
||||||
|
|
||||||
if (addToHistory) MinecraftClient.getInstance().inGameHud.getChatHud().addToMessageHistory(chatText);
|
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);
|
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;
|
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.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
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)
|
@Mixin(net.minecraft.network.ClientConnection.class)
|
||||||
public class ClientConnectionMixin {
|
public class ClientConnectionMixin {
|
||||||
|
@ -17,4 +20,19 @@ public class ClientConnectionMixin {
|
||||||
ci.cancel();
|
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;
|
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.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
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)
|
@Mixin(net.minecraft.client.network.ClientPlayNetworkHandler.class)
|
||||||
public class ClientPlayNetworkHandlerMixin {
|
public class ClientPlayNetworkHandlerMixin {
|
||||||
@Inject(method = "onGameJoin", at = @At("TAIL"))
|
@Inject(method = "onGameJoin", at = @At("TAIL"))
|
||||||
private void onGameJoin (GameJoinS2CPacket packet, CallbackInfo ci) {
|
private void onGameJoin (GameJoinS2CPacket packet, CallbackInfo ci) {
|
||||||
SelfCare.INSTANCE.init();
|
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;
|
package land.chipmunk.chipmunkmod.modules;
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import land.chipmunk.chipmunkmod.data.BlockArea;
|
||||||
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 lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import java.util.List;
|
import net.minecraft.block.entity.CommandBlockBlockEntity;
|
||||||
import java.util.ArrayList;
|
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.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import land.chipmunk.chipmunkmod.ChipmunkMod;
|
|
||||||
import land.chipmunk.chipmunkmod.data.BlockArea;
|
|
||||||
|
|
||||||
public class CommandCore {
|
public class CommandCore {
|
||||||
private MinecraftClient client;
|
private final MinecraftClient client;
|
||||||
@Getter @Setter private boolean ready = false;
|
@Getter @Setter private boolean ready = false;
|
||||||
@Getter @Setter private BlockPos origin;
|
@Getter @Setter private BlockPos origin;
|
||||||
@Getter private final BlockArea relativeArea;
|
@Getter private final BlockArea relativeArea;
|
||||||
@Getter @Setter private BlockPos currentBlockRelative;
|
@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) {
|
public CommandCore (MinecraftClient client, BlockArea relativeArea) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
@ -121,14 +118,14 @@ public class CommandCore {
|
||||||
|
|
||||||
incrementCurrentBlock();
|
incrementCurrentBlock();
|
||||||
|
|
||||||
CompletableFuture<NbtCompound> future = new CompletableFuture<NbtCompound>();
|
CompletableFuture<NbtCompound> future = new CompletableFuture<>();
|
||||||
|
|
||||||
final Timer timer = new Timer();
|
final Timer timer = new Timer();
|
||||||
|
|
||||||
final TimerTask queryTask = new TimerTask() {
|
final TimerTask queryTask = new TimerTask() {
|
||||||
public void run () {
|
public void run () {
|
||||||
client.getNetworkHandler().getDataQueryHandler().queryBlockNbt(currentBlock,
|
client.getNetworkHandler().getDataQueryHandler().queryBlockNbt(currentBlock,
|
||||||
tag -> { future.complete(tag); });
|
future::complete);
|
||||||
|
|
||||||
timer.cancel(); // ? Is this necesary?
|
timer.cancel(); // ? Is this necesary?
|
||||||
timer.purge();
|
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;
|
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.MinecraftClient;
|
||||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||||
import net.minecraft.client.network.ClientPlayerEntity;
|
import net.minecraft.client.network.ClientPlayerEntity;
|
||||||
import com.mojang.brigadier.tree.CommandNode;
|
import com.mojang.brigadier.tree.CommandNode;
|
||||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
|
||||||
public class SelfCare {
|
public class SelfCare extends Listener {
|
||||||
private final MinecraftClient client;
|
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;
|
private Timer timer = null;
|
||||||
|
|
||||||
|
@ -20,6 +31,8 @@ public class SelfCare {
|
||||||
public SelfCare (MinecraftClient client, long interval) {
|
public SelfCare (MinecraftClient client, long interval) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.interval = interval;
|
this.interval = interval;
|
||||||
|
|
||||||
|
ListenerManager.addListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init () {
|
public void init () {
|
||||||
|
@ -43,6 +56,14 @@ public class SelfCare {
|
||||||
timer = null;
|
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 () {
|
public void tick () {
|
||||||
final ClientPlayerEntity player = client.player;
|
final ClientPlayerEntity player = client.player;
|
||||||
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
|
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
|
||||||
|
@ -52,17 +73,19 @@ public class SelfCare {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!player.hasPermissionLevel(2)) { if (serverHasCommand("op")) networkHandler.sendChatCommand("op @s[type=player]"); }
|
if (player != null && !player.hasPermissionLevel(2) && opEnabled) { if (serverHasCommand("op")) networkHandler.sendChatCommand("op @s[type=player]"); }
|
||||||
else if (!client.player.isCreative()) networkHandler.sendChatCommand("gamemode creative");
|
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)
|
// 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) {
|
private boolean serverHasCommand (String name) {
|
||||||
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
|
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
|
||||||
|
|
||||||
|
if (networkHandler == null) return false;
|
||||||
|
|
||||||
for (CommandNode node : networkHandler.getCommandDispatcher().getRoot().getChildren()) {
|
for (CommandNode node : networkHandler.getCommandDispatcher().getRoot().getChildren()) {
|
||||||
if (!(node instanceof LiteralCommandNode)) continue;
|
if (!(node instanceof LiteralCommandNode literal)) continue;
|
||||||
final LiteralCommandNode literal = (LiteralCommandNode) node;
|
|
||||||
|
|
||||||
if (literal.getLiteral().equals(name)) return true;
|
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;
|
package land.chipmunk.chipmunkmod.util;
|
||||||
|
|
||||||
public interface Hexadecimal {
|
public interface Hexadecimal {
|
||||||
static String encode (byte b) {
|
static String encode (byte b) {
|
||||||
return "" + Character.forDigit((b >> 4) & 0xF, 16) + Character.forDigit((b & 0xF), 16);
|
return "" + Character.forDigit((b >> 4) & 0xF, 16) + Character.forDigit((b & 0xF), 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String encode (byte[] array) {
|
static String encode (byte[] array) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (int i = 0; i < array.length; i++) sb.append(encode(array[i]));
|
for (int i = 0; i < array.length; i++) sb.append(encode(array[i]));
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Decode
|
// TODO: Decode
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,17 @@
|
||||||
"minVersion": "0.8",
|
"minVersion": "0.8",
|
||||||
"package": "land.chipmunk.chipmunkmod.mixin",
|
"package": "land.chipmunk.chipmunkmod.mixin",
|
||||||
"compatibilityLevel": "JAVA_17",
|
"compatibilityLevel": "JAVA_17",
|
||||||
"mixins": [
|
|
||||||
],
|
|
||||||
"client": [
|
"client": [
|
||||||
"ChatScreenMixin",
|
"ChatHudMixin",
|
||||||
"ChatInputSuggestorMixin",
|
"ChatInputSuggestorMixin",
|
||||||
"ClientPlayNetworkHandlerAccessor",
|
"ChatScreenMixin",
|
||||||
"ClientConnectionMixin",
|
"ClientConnectionMixin",
|
||||||
"ClientPlayerEntityMixin",
|
"ClientPlayerEntityMixin",
|
||||||
|
"ClientPlayNetworkHandlerAccessor",
|
||||||
"ClientPlayNetworkHandlerMixin",
|
"ClientPlayNetworkHandlerMixin",
|
||||||
"MinecraftClientAccessor"
|
"MinecraftClientAccessor",
|
||||||
|
"LightmapTextureManagerMixin",
|
||||||
|
"DecoderHandlerMixin"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
|
|
|
@ -16,5 +16,17 @@
|
||||||
"chipmunk": { "prefix": "'", "key": null },
|
"chipmunk": { "prefix": "'", "key": null },
|
||||||
"chomens": { "prefix": "*", "key": null },
|
"chomens": { "prefix": "*", "key": null },
|
||||||
"kittycorp": { "prefix": "^", "key": null }
|
"kittycorp": { "prefix": "^", "key": null }
|
||||||
|
},
|
||||||
|
|
||||||
|
"customChat": {
|
||||||
|
"format": {
|
||||||
|
"translate": "chat.type.text",
|
||||||
|
"with": [
|
||||||
|
{
|
||||||
|
"selector": "USERNAME"
|
||||||
|
},
|
||||||
|
"MESSAGE"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,14 +3,15 @@
|
||||||
"id": "chipmunkmod",
|
"id": "chipmunkmod",
|
||||||
"version": "${version}",
|
"version": "${version}",
|
||||||
|
|
||||||
"name": "ChipmunkMod",
|
"name": "ChipmunkMod (chayapak's fork)",
|
||||||
"description": "A utility mod created for free-op servers with minimal or no restrictions",
|
"description": "My fork of ChipmunkMod",
|
||||||
"authors": [
|
"authors": [
|
||||||
"_ChipMC_"
|
"_ChipMC_",
|
||||||
|
"chayapak"
|
||||||
],
|
],
|
||||||
"contact": {
|
"contact": {
|
||||||
"homepage": "https://chipmunk.land/",
|
"homepage": "https://chayapak.chipmunk.land/",
|
||||||
"sources": "https://code.chipmunk.land/ChipmunkMC/fabric-mod-mabe"
|
"sources": "https://code.chipmunk.land/ChomeNS/chipmunkmod"
|
||||||
},
|
},
|
||||||
|
|
||||||
"license": "CC0-1.0",
|
"license": "CC0-1.0",
|
||||||
|
|
Loading…
Reference in a new issue