Compare commits

...

10 commits

Author SHA1 Message Date
5ebde826ba make it more sexy 2023-05-03 21:02:11 +07:00
087b05fb95 validate command
i know the file is a mess i will make it more modular later mabe
2023-03-15 22:07:24 -04:00
88dc82eb1e Config support
can prob be improved but that will be done later :trollface:
2023-03-11 21:56:16 -05:00
2023ae3cd1 Username command & fix commands a bit 2023-02-12 21:04:04 -05:00
6b0014992c chain -> impulse 2023-02-11 20:13:55 -05:00
a77010e0f4 Add basic self-care 2023-02-08 21:30:14 -05:00
26fd290b92 Initial core implementation 2023-01-31 22:49:50 -05:00
92612c6b32 Add a temporary patch for Chat message validation failed 2023-01-29 20:00:09 -05:00
a91b5ac1f0 Command suggestions
Closes #1
2023-01-28 19:01:04 -05:00
384a35fa9b Change the modid and rename a bit
The name is now `ChipmunkMod` (no space) for consistency reasons
2023-01-27 22:52:04 -05:00
60 changed files with 3808 additions and 169 deletions

View file

@ -1,9 +1,4 @@
# Fabric Example Mod
# ChipmunkMod
My fork of [Chipmunk Sex Mod](https://code.chipmunk.land/ChipmunkMC/chipmunkmod)
## Setup
For setup instructions please see the [fabric wiki page](https://fabricmc.net/wiki/tutorial:setup) that relates to the IDE that you are using.
## License
This template is available under the CC0 license. Feel free to learn from it and incorporate it in your own projects.
ignore messy code pls,. .,,.,...,.,.,

View file

@ -16,6 +16,8 @@ repositories {
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories.
mavenCentral()
}
dependencies {
@ -27,10 +29,18 @@ dependencies {
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
modImplementation include("net.kyori:adventure-platform-fabric:5.8.0") // for Minecraft 1.19.4
// Uncomment the following line to enable the deprecated Fabric API modules.
// These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time.
// modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_version}"
compileOnly 'org.projectlombok:lombok:1.18.24'
annotationProcessor 'org.projectlombok:lombok:1.18.24'
testCompileOnly 'org.projectlombok:lombok:1.18.24'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.24'
}
processResources {

View file

@ -4,9 +4,9 @@ org.gradle.parallel=true
# Fabric Properties
# check these on https://fabricmc.net/develop
minecraft_version=1.19.3
yarn_mappings=1.19.3+build.1
loader_version=0.14.11
minecraft_version=1.19.4
yarn_mappings=1.19.4+build.2
loader_version=0.14.19
# Mod Properties
mod_version = 1.0.0
@ -14,4 +14,5 @@ org.gradle.parallel=true
archives_base_name = chipmunkmod
# Dependencies
fabric_version=0.68.1+1.19.3
fabric_version=0.76.0+1.19.4

2
lombok.config Normal file
View file

@ -0,0 +1,2 @@
config.stopBubbling = true
lombok.accessors.fluent = true

View file

@ -1,14 +1,24 @@
package land.chipmunk.chipmunkmod;
import net.fabricmc.api.ModInitializer;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
public class ChipmunkMod implements ModInitializer {
// This logger is used to write text to the console and the log file.
// It is considered best practice to use your mod id as the logger's name.
// That way, it's clear which mod wrote info, warnings, and errors.
public static final Logger LOGGER = LoggerFactory.getLogger("modid");
public static final Logger LOGGER = LoggerFactory.getLogger("chipmunkmod");
public static Configuration CONFIG;
@Override
public void onInitialize () {
@ -16,6 +26,39 @@ public class ChipmunkMod implements ModInitializer {
// However, some things (like resources) may still be uninitialized.
// Proceed with mild caution.
try {
new File("config").mkdirs(); // TODO: Clean this up
CONFIG = loadConfig();
} catch (IOException exception) {
throw new RuntimeException("Could not load the config", exception);
}
LOGGER.info("Hello Fabric world!");
}
public static Configuration loadConfig () throws IOException {
final Gson gson = new Gson();
final File file = new File("config/chipmunkmod.json");
if (!file.exists()) {
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("default_config.json");
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
final StringBuilder sb = new StringBuilder();
while (reader.ready()) sb.append((char) reader.read());
final String defaultConfig = sb.toString();
// Write the default config
BufferedWriter configWriter = new BufferedWriter(new FileWriter(file));
configWriter.write(defaultConfig);
configWriter.close();
return gson.fromJson(defaultConfig, Configuration.class);
}
InputStream is = new FileInputStream(file);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
return gson.fromJson(reader, Configuration.class);
}
}

View file

@ -0,0 +1,39 @@
package land.chipmunk.chipmunkmod;
import com.google.gson.JsonObject;
import land.chipmunk.chipmunkmod.data.BlockArea;
import net.minecraft.util.math.BlockPos;
import lombok.AllArgsConstructor;
public class Configuration {
public CommandManager commands = new CommandManager();
public CommandCore core = new CommandCore();
public Bots bots = new Bots();
public CustomChat customChat = new CustomChat();
public static class CommandManager {
public String prefix = ".";
}
public static class CommandCore {
public BlockArea relativeArea = new BlockArea(new BlockPos(0, 0, 0), new BlockPos(15, 0, 15));
}
public static class Bots {
public BotInfo hbot = new BotInfo("#", null);
public BotInfo sbot = new BotInfo(":", null);
public BotInfo chipmunk = new BotInfo("'", null);
public BotInfo chomens = new BotInfo("*", null);
public BotInfo kittycorp = new BotInfo("^", null);
}
@AllArgsConstructor
public static class BotInfo {
public String prefix;
public String key;
}
public static class CustomChat {
public JsonObject format;
}
}

View file

@ -13,11 +13,12 @@ import net.minecraft.text.MutableText;
import net.minecraft.util.Formatting;
import net.minecraft.client.MinecraftClient;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import land.chipmunk.chipmunkmod.ChipmunkMod;
import land.chipmunk.chipmunkmod.commands.*;
public class CommandManager {
public static CommandDispatcher<FabricClientCommandSource> dispatcher = new CommandDispatcher();
public static String prefix = ".";
public static String prefix = ChipmunkMod.CONFIG.commands.prefix;
public static void executeCommand (String command) {
final MinecraftClient client = MinecraftClient.getInstance();
@ -28,7 +29,8 @@ public class CommandManager {
dispatcher.execute(command, commandSource);
} catch (CommandSyntaxException e) {
commandSource.sendError(Texts.toText(e.getRawMessage()));
commandSource.sendError(getContext(e));
final Text context = getContext(e);
if (context != null) commandSource.sendError(context);
} catch (CommandException e) {
commandSource.sendError(e.getTextMessage());
} catch (RuntimeException e) {
@ -66,5 +68,15 @@ public class CommandManager {
static {
TestCommand.register(dispatcher);
CoreCommand.register(dispatcher);
UsernameCommand.register(dispatcher);
ValidateCommand.register(dispatcher);
CloopCommand.register(dispatcher);
CustomChatCommand.register(dispatcher);
SayCommand.register(dispatcher);
SelfCareCommand.register(dispatcher);
FullBrightCommand.register(dispatcher);
MusicCommand.register(dispatcher);
RainbowNameCommand.register(dispatcher);
}
}

View file

@ -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();
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,79 @@
package land.chipmunk.chipmunkmod.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.text.Text;
import net.minecraft.nbt.NbtCompound;
import java.util.concurrent.CompletableFuture;
import land.chipmunk.chipmunkmod.modules.CommandCore;
public class CoreCommand {
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(
literal("core")
.then(
literal("run")
.then(
argument("command", greedyString())
.executes(c -> run(c))
)
)
.then(
literal("runTracked")
.then(
argument("command", greedyString())
.executes(c -> runTracked(c))
)
)
.then(literal("refill").executes(c -> refill(c)))
.then(literal("move").executes(c -> move(c)))
);
}
public static int run (CommandContext<FabricClientCommandSource> context) {
CommandCore.INSTANCE.run(getString(context, "command"));
return Command.SINGLE_SUCCESS;
}
public static int runTracked (CommandContext<FabricClientCommandSource> context) {
final FabricClientCommandSource source = context.getSource();
final String command = getString(context, "command");
final CompletableFuture<NbtCompound> future = CommandCore.INSTANCE.runTracked(command);
future.thenApply(tag -> {
try {
final String output = tag.getString("LastOutput");
if (output != null) source.sendFeedback(Text.Serializer.fromJson(output));
} catch (Exception ignored) {
}
return tag;
});
return Command.SINGLE_SUCCESS;
}
public static int refill (CommandContext<FabricClientCommandSource> context) {
CommandCore.INSTANCE.refill();
return Command.SINGLE_SUCCESS;
}
public static int move (CommandContext<FabricClientCommandSource> context) {
final FabricClientCommandSource source = context.getSource();
CommandCore.INSTANCE.move(source.getClient().player.getPos());
return Command.SINGLE_SUCCESS;
}
}

View 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;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,67 @@
package land.chipmunk.chipmunkmod.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.ConnectScreen;
import net.minecraft.client.gui.screen.TitleScreen;
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
import net.minecraft.client.network.ServerInfo;
import net.minecraft.client.network.ServerAddress;
import net.minecraft.client.util.Session;
import net.minecraft.text.Text;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import java.util.Optional;
import land.chipmunk.chipmunkmod.mixin.MinecraftClientAccessor;
public class UsernameCommand {
private static final Session ORIGINAL_SESSION = ((MinecraftClientAccessor) MinecraftClient.getInstance()).session();
private static final SimpleCommandExceptionType USERNAME_TOO_LONG = new SimpleCommandExceptionType(Text.translatable("The specified username is longer than 16 characters"));
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(
literal("username")
.then(
literal("set")
.then(
argument("username", greedyString())
.executes(c -> updateUsername(c))
)
)
.then(
literal("revert")
.executes(c -> updateSession(c, ORIGINAL_SESSION))
)
);
}
public static int updateUsername (CommandContext<FabricClientCommandSource> context) throws CommandSyntaxException {
final String username = getString(context, "username");
if (username.length() > 16) throw USERNAME_TOO_LONG.create();
final Session session = new Session(username, "", "", Optional.empty(), Optional.empty(), Session.AccountType.MOJANG);
return updateSession(context, session);
}
public static int updateSession (CommandContext<FabricClientCommandSource> context, Session session) throws CommandSyntaxException {
final FabricClientCommandSource source = context.getSource();
final MinecraftClient client = source.getClient();
((MinecraftClientAccessor) client).session(session);
// TODO: Put this in a separate class
final ServerInfo info = client.getCurrentServerEntry();
client.world.disconnect();
client.disconnect();
ConnectScreen.connect(new MultiplayerScreen(new TitleScreen()), client, ServerAddress.parse(info.address), info);
return Command.SINGLE_SUCCESS;
}
}

View file

@ -0,0 +1,141 @@
package land.chipmunk.chipmunkmod.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import net.minecraft.text.Text;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import java.util.Arrays;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import land.chipmunk.chipmunkmod.ChipmunkMod;
import land.chipmunk.chipmunkmod.Configuration;
import land.chipmunk.chipmunkmod.util.Hexadecimal;
public class ValidateCommand {
private static final SimpleCommandExceptionType UNSPECIFIED_KEY = new SimpleCommandExceptionType(Text.literal("The key of the bot is unspecified (null), did you incorrectly add it to your config?"));
public static void register (CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(
literal("validate")
.then(literal("hbot").then(argument("command", greedyString()).executes(c -> hbot(c))))
.then(literal("sbot").then(argument("command", greedyString()).executes(c -> sbot(c))))
// .then(literal("chipmunk").then(argument("command", greedyString()).executes(c -> chipmunk(c))))
.then(literal("chomens").then(argument("command", greedyString()).executes(c -> chomens(c))))
.then(literal("kittycorp").then(argument("command", greedyString()).executes(c -> kittycorp(c))))
);
}
public static int hbot (CommandContext<FabricClientCommandSource> context) throws CommandSyntaxException {
final Configuration.BotInfo info = ChipmunkMod.CONFIG.bots.hbot;
final String command = getString(context, "command");
final MinecraftClient client = context.getSource().getClient();
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
final String prefix = info.prefix;
final String key = info.key;
if (key == null) throw UNSPECIFIED_KEY.create();
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
String time = String.valueOf(System.currentTimeMillis() / 10000);
String input = command.replaceAll("&[0-9a-fklmnor]", "") + ";" + client.player.getUuidAsString() + ";" + time + ";" + key;
byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
BigInteger bigInt = new BigInteger(1, Arrays.copyOfRange(hash, 0, 4));
String stringHash = bigInt.toString(Character.MAX_RADIX);
networkHandler.sendChatMessage(prefix + command + " " + stringHash);
} catch (NoSuchAlgorithmException e) {
throw new SimpleCommandExceptionType(Text.literal(e.getMessage())).create();
}
return Command.SINGLE_SUCCESS;
}
public static int sbot (CommandContext<FabricClientCommandSource> context) throws CommandSyntaxException {
final Configuration.BotInfo info = ChipmunkMod.CONFIG.bots.sbot;
final String command = getString(context, "command");
final MinecraftClient client = context.getSource().getClient();
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
final String prefix = info.prefix;
final String key = info.key;
if (key == null) throw UNSPECIFIED_KEY.create();
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
String time = String.valueOf(System.currentTimeMillis() / 20000);
String input = prefix + command.replaceAll("&[0-9a-fklmnorx]", "") + ";" + client.player.getName() + ";" + time + ";" + key;
byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
BigInteger bigInt = new BigInteger(1, Arrays.copyOfRange(hash, 0, 4));
String stringHash = bigInt.toString(Character.MAX_RADIX);
networkHandler.sendChatMessage(prefix + command + " " + stringHash);
} catch (NoSuchAlgorithmException e) {
throw new SimpleCommandExceptionType(Text.literal(e.getMessage())).create();
}
return Command.SINGLE_SUCCESS;
}
public static int chomens (CommandContext<FabricClientCommandSource> context) throws CommandSyntaxException {
final Configuration.BotInfo info = ChipmunkMod.CONFIG.bots.chomens;
final String command = getString(context, "command");
final ClientPlayNetworkHandler networkHandler = context.getSource().getClient().getNetworkHandler();
final String prefix = info.prefix;
final String key = info.key;
if (key == null) throw UNSPECIFIED_KEY.create();
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
String time = String.valueOf(System.currentTimeMillis() / 10000);
String input = time + key;
byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
String stringHash = Hexadecimal.encode(hash).substring(0, 16);
String[] arguments = command.split(" ");
networkHandler.sendChatMessage(prefix + arguments[0] + " " + stringHash + " " + String.join(" ", Arrays.copyOfRange(arguments, 1, arguments.length)));
} catch (NoSuchAlgorithmException e) {
throw new SimpleCommandExceptionType(Text.literal(e.getMessage())).create();
}
return Command.SINGLE_SUCCESS;
}
public static int kittycorp (CommandContext<FabricClientCommandSource> context) throws CommandSyntaxException {
final Configuration.BotInfo info = ChipmunkMod.CONFIG.bots.kittycorp;
final String command = getString(context, "command");
final ClientPlayNetworkHandler networkHandler = context.getSource().getClient().getNetworkHandler();
final String prefix = info.prefix;
final String key = info.key;
if (key == null) throw UNSPECIFIED_KEY.create();
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
String time = String.valueOf(System.currentTimeMillis() / 10000);
String input = prefix + command.replaceAll("&[0-9a-fklmnorx]", "") + ";" + time + ";" + key;
byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
BigInteger bigInt = new BigInteger(1, Arrays.copyOfRange(hash, 0, 4));
String stringHash = bigInt.toString(Character.MAX_RADIX);
networkHandler.sendChatMessage(prefix + command + " " + stringHash);
} catch (NoSuchAlgorithmException e) {
throw new SimpleCommandExceptionType(Text.literal(e.getMessage())).create();
}
return Command.SINGLE_SUCCESS;
}
}

View file

@ -0,0 +1,13 @@
package land.chipmunk.chipmunkmod.data;
import net.minecraft.util.math.BlockPos;
import lombok.AllArgsConstructor;
import lombok.Data;
// ? Am I reinventing the wheel here?
@AllArgsConstructor
@Data
public class BlockArea {
private BlockPos start;
private BlockPos end;
}

View 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;
}
}

View file

@ -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());
}
}

View file

@ -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) {}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -0,0 +1,50 @@
package land.chipmunk.chipmunkmod.mixin;
import org.spongepowered.asm.mixin.Mixin;
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.concurrent.CompletableFuture;
import com.mojang.brigadier.suggestion.Suggestions;
import net.minecraft.client.gui.widget.TextFieldWidget;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
import land.chipmunk.chipmunkmod.command.CommandManager;
@Mixin(net.minecraft.client.gui.screen.ChatInputSuggestor.class)
public class ChatInputSuggestorMixin {
@Shadow
CompletableFuture<Suggestions> pendingSuggestions;
@Shadow
public void show (boolean narrateFirstSuggestion) {}
@Shadow
final TextFieldWidget textField;
public ChatInputSuggestorMixin () {
textField = null;
}
@Inject(at = @At("TAIL"), method = "refresh()V")
public void onRefresh (CallbackInfo ci) {
final String text = this.textField.getText();
final int cursor = this.textField.getCursor();
if (cursor < CommandManager.prefix.length() || !text.startsWith(CommandManager.prefix)) return;
final StringReader reader = new StringReader(text);
reader.setCursor(CommandManager.prefix.length()); // Skip the prefix
final CommandDispatcher<FabricClientCommandSource> dispatcher = CommandManager.dispatcher;
final MinecraftClient client = MinecraftClient.getInstance();
final FabricClientCommandSource commandSource = (FabricClientCommandSource) client.getNetworkHandler().getCommandSource();
pendingSuggestions = dispatcher.getCompletionSuggestions(dispatcher.parse(reader, commandSource), cursor);
show(true);
}
}

View file

@ -1,5 +1,6 @@
package land.chipmunk.chipmunkmod.mixin;
import land.chipmunk.chipmunkmod.modules.CustomChat;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@ -16,6 +17,12 @@ public class ChatScreenMixin {
if (addToHistory) MinecraftClient.getInstance().inGameHud.getChatHud().addToMessageHistory(chatText);
cir.setReturnValue(true);
} else if (!chatText.startsWith("/")) {
CustomChat.INSTANCE.chat(chatText);
if (addToHistory) MinecraftClient.getInstance().inGameHud.getChatHud().addToMessageHistory(chatText);
cir.setReturnValue(true);
}
}

View file

@ -0,0 +1,38 @@
package land.chipmunk.chipmunkmod.mixin;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException;
import land.chipmunk.chipmunkmod.listeners.Listener;
import land.chipmunk.chipmunkmod.listeners.ListenerManager;
import net.minecraft.network.listener.PacketListener;
import net.minecraft.network.packet.Packet;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(net.minecraft.network.ClientConnection.class)
public class ClientConnectionMixin {
@Inject(at = @At("HEAD"), method = "disconnect", cancellable = true)
public void disconnect (Text disconnectReason, CallbackInfo ci) {
if (disconnectReason == ClientPlayNetworkHandlerAccessor.chatValidationFailedText()) {
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);
}
}
}

View file

@ -0,0 +1,11 @@
package land.chipmunk.chipmunkmod.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.text.Text;
@Mixin(net.minecraft.client.network.ClientPlayNetworkHandler.class)
public interface ClientPlayNetworkHandlerAccessor {
@Accessor("CHAT_VALIDATION_FAILED_TEXT")
public static Text chatValidationFailedText () { throw new AssertionError(); }
}

View file

@ -0,0 +1,24 @@
package land.chipmunk.chipmunkmod.mixin;
import land.chipmunk.chipmunkmod.modules.*;
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(net.minecraft.client.network.ClientPlayNetworkHandler.class)
public class ClientPlayNetworkHandlerMixin {
@Inject(method = "onGameJoin", at = @At("TAIL"))
private void onGameJoin (GameJoinS2CPacket packet, CallbackInfo ci) {
SelfCare.INSTANCE.init();
CommandLooper.INSTANCE.init();
SongPlayer.INSTANCE.coreReady();
RainbowName.INSTANCE.init();
}
@Inject(method = "onGameJoin", at = @At("HEAD"))
private void onGameJoin2 (GameJoinS2CPacket packet, CallbackInfo ci) {
Players.INSTANCE.init();
}
}

View file

@ -0,0 +1,34 @@
package land.chipmunk.chipmunkmod.mixin;
import org.spongepowered.asm.mixin.Mixin;
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 net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.entity.MovementType;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec2f;
import land.chipmunk.chipmunkmod.modules.CommandCore;
@Mixin(ClientPlayerEntity.class)
public class ClientPlayerEntityMixin {
private static MinecraftClient CLIENT = MinecraftClient.getInstance();
@Inject(at = @At("HEAD"), method = "move")
public void move (MovementType type, Vec3d relPos, CallbackInfo ci) {
if ((ClientPlayerEntity) (Object) this != CLIENT.player) return;
final Vec3d position = ((ClientPlayerEntity) (Object) this).getPos().add(relPos);
final ClientWorld world = CLIENT.getNetworkHandler().getWorld();
final BlockPos origin = CommandCore.INSTANCE.origin();
if (origin == null) { CommandCore.INSTANCE.move(position); return; }
final int distance = (int) Math.sqrt(new Vec2f(origin.getX() / 16, origin.getZ() / 16).distanceSquared(new Vec2f((int) position.getX() / 16, (int) position.getZ() / 16)));
if (distance > world.getSimulationDistance()) CommandCore.INSTANCE.move(position);
}
}

View 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();
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,16 @@
package land.chipmunk.chipmunkmod.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.client.util.Session;
@Mixin(net.minecraft.client.MinecraftClient.class)
public interface MinecraftClientAccessor {
@Accessor("session")
Session session ();
@Mutable
@Accessor("session")
void session (Session session);
}

View file

@ -0,0 +1,145 @@
package land.chipmunk.chipmunkmod.modules;
import land.chipmunk.chipmunkmod.data.BlockArea;
import lombok.Getter;
import lombok.Setter;
import net.minecraft.block.entity.CommandBlockBlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.c2s.play.UpdateCommandBlockC2SPacket;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CompletableFuture;
public class CommandCore {
private final MinecraftClient client;
@Getter @Setter private boolean ready = false;
@Getter @Setter private BlockPos origin;
@Getter private final BlockArea relativeArea;
@Getter @Setter private BlockPos currentBlockRelative;
public static CommandCore INSTANCE = new CommandCore(MinecraftClient.getInstance(), new BlockArea(new BlockPos(0, 0, 0), new BlockPos(15, 2, 15)));
public CommandCore (MinecraftClient client, BlockArea relativeArea) {
this.client = client;
this.relativeArea = relativeArea;
}
public void move (Vec3d position) {
if (!ready) {
ready = true;
// for (Listener listener : listeners) listener.ready();
}
origin = new BlockPos(
((int) position.getX() / 16) * 16,
0, // TODO: Use the actual bottom of the world instead of hardcoding to 0
((int) position.getZ() / 16) * 16
);
if (currentBlockRelative == null) currentBlockRelative = new BlockPos(relativeArea.start());
refill();
}
public void refill () {
// final PositionManager position = client.position();
final BlockPos relStart = relativeArea.start();
final BlockPos relEnd = relativeArea.end();
final String command = String.format(
"fill %s %s %s %s %s %s command_block",
relStart.getX() + origin.getX(),
relStart.getY() + origin.getY(),
relStart.getZ() + origin.getZ(),
relEnd.getX() + origin.getX(),
relEnd.getY() + origin.getY(),
relEnd.getZ() + origin.getZ()
);
client.getNetworkHandler().sendChatCommand(command);
}
public void incrementCurrentBlock () {
final BlockPos start = relativeArea.start();
final BlockPos end = relativeArea.end();
int x = currentBlockRelative.getX();
int y = currentBlockRelative.getY();
int z = currentBlockRelative.getZ();
x++;
if (x > end.getX()) {
x = start.getX();
z++;
}
if (z > end.getZ()) {
z = start.getZ();
y++;
}
if (y > end.getY()) {
x = start.getX();
y = start.getY();
z = start.getZ();
}
currentBlockRelative = new BlockPos(x, y, z);
}
public BlockPos currentBlockAbsolute () {
return currentBlockRelative.add(origin);
}
public void run (String command) {
final ClientConnection connection = client.getNetworkHandler().getConnection();
final BlockPos currentBlock = currentBlockAbsolute();
// TODO: Support using repeating command blocks (on kaboom-like servers) (because less packets)
connection.send(new UpdateCommandBlockC2SPacket(currentBlock, "", CommandBlockBlockEntity.Type.REDSTONE, false, false, false));
connection.send(new UpdateCommandBlockC2SPacket(currentBlock, command, CommandBlockBlockEntity.Type.REDSTONE, false, false, true));
incrementCurrentBlock();
}
public CompletableFuture<NbtCompound> runTracked (String command) {
final ClientConnection connection = client.getNetworkHandler().getConnection();
final BlockPos currentBlock = currentBlockAbsolute();
// TODO: Support using repeating command blocks (on kaboom-like servers) (because less packets)
connection.send(new UpdateCommandBlockC2SPacket(currentBlock, "", CommandBlockBlockEntity.Type.SEQUENCE, false, false, false));
connection.send(new UpdateCommandBlockC2SPacket(currentBlock, command, CommandBlockBlockEntity.Type.REDSTONE, true, false, true));
incrementCurrentBlock();
CompletableFuture<NbtCompound> future = new CompletableFuture<>();
final Timer timer = new Timer();
final TimerTask queryTask = new TimerTask() {
public void run () {
client.getNetworkHandler().getDataQueryHandler().queryBlockNbt(currentBlock,
future::complete);
timer.cancel(); // ? Is this necesary?
timer.purge();
}
};
timer.schedule(queryTask, 50);
return future;
}
public void cleanup () {
origin = null;
currentBlockRelative = null;
ready = false;
}
}

View file

@ -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();
}
}

View file

@ -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));
}
}
}

View file

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

View 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;
});
}
}

View 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;
}
}

View file

@ -0,0 +1,95 @@
package land.chipmunk.chipmunkmod.modules;
import land.chipmunk.chipmunkmod.listeners.Listener;
import land.chipmunk.chipmunkmod.listeners.ListenerManager;
import lombok.Setter;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import lombok.Getter;
import net.minecraft.text.Text;
import java.util.Timer;
import java.util.TimerTask;
public class SelfCare extends Listener {
private final MinecraftClient client;
@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;
public static final SelfCare INSTANCE = new SelfCare(MinecraftClient.getInstance(), 70L);
public SelfCare (MinecraftClient client, long interval) {
this.client = client;
this.interval = interval;
ListenerManager.addListener(this);
}
public void init () {
final TimerTask task = new TimerTask() {
public void run () {
tick();
}
};
if (timer != null) cleanup();
timer = new Timer();
timer.schedule(task, interval, interval);
}
public void cleanup () {
if (timer == null) return;
timer.cancel();
timer.purge();
timer = null;
}
@Override
public void chatMessageReceived (Text message) {
final String stringMessage = message.getString();
if (stringMessage.equals("Successfully enabled CommandSpy")) cspy = true;
else if (stringMessage.equals("Successfully disabled CommandSpy")) cspy = false;
}
public void tick () {
final ClientPlayerEntity player = client.player;
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
if (networkHandler == null) {
cleanup();
return;
}
if (player != null && !player.hasPermissionLevel(2) && opEnabled) { if (serverHasCommand("op")) networkHandler.sendChatCommand("op @s[type=player]"); }
else if (client.player != null && !client.player.isCreative() && gamemodeEnabled) networkHandler.sendChatCommand("gamemode creative");
else if (!cspy && cspyEnabled) { if (serverHasCommand("c")) networkHandler.sendChatCommand("c on"); }
}
// TODO: Move this into a separate class related to server info gathering (and yes, I plan on making this d y n a m i c and require little to no configuration for most servers)
private boolean serverHasCommand (String name) {
final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
if (networkHandler == null) return false;
for (CommandNode node : networkHandler.getCommandDispatcher().getRoot().getChildren()) {
if (!(node instanceof LiteralCommandNode literal)) continue;
if (literal.getLiteral().equals(name)) return true;
}
return false;
}
}

View 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();
}
}
}
}

View file

@ -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);
}
}

View 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];
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}

View 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();
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

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

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -0,0 +1,15 @@
package land.chipmunk.chipmunkmod.util;
public interface Hexadecimal {
static String encode (byte b) {
return "" + Character.forDigit((b >> 4) & 0xF, 16) + Character.forDigit((b & 0xF), 16);
}
static String encode (byte[] array) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < array.length; i++) sb.append(encode(array[i]));
return sb.toString();
}
// TODO: Decode
}

View file

@ -0,0 +1,21 @@
{
"required": true,
"minVersion": "0.8",
"package": "land.chipmunk.chipmunkmod.mixin",
"compatibilityLevel": "JAVA_17",
"client": [
"ChatHudMixin",
"ChatInputSuggestorMixin",
"ChatScreenMixin",
"ClientConnectionMixin",
"ClientPlayerEntityMixin",
"ClientPlayNetworkHandlerAccessor",
"ClientPlayNetworkHandlerMixin",
"MinecraftClientAccessor",
"LightmapTextureManagerMixin",
"DecoderHandlerMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,32 @@
{
"commands": {
"prefix": "."
},
"core": {
"relativeArea": {
"start": { "x": 0, "y": 0, "z": 0 },
"end": { "x": 15, "y": 0, "z": 15 }
}
},
"bots": {
"hbot": { "prefix": "#", "key": null },
"sbot": { "prefix": ":", "key": null },
"chipmunk": { "prefix": "'", "key": null },
"chomens": { "prefix": "*", "key": null },
"kittycorp": { "prefix": "^", "key": null }
},
"customChat": {
"format": {
"translate": "chat.type.text",
"with": [
{
"selector": "USERNAME"
},
"MESSAGE"
]
}
}
}

View file

@ -1,16 +1,17 @@
{
"schemaVersion": 1,
"id": "modid",
"id": "chipmunkmod",
"version": "${version}",
"name": "Chipmunk Mod",
"description": "A utility mod created for free-op servers with minimal or no restrictions",
"name": "ChipmunkMod (chayapak's fork)",
"description": "My fork of ChipmunkMod",
"authors": [
"_ChipMC_"
"_ChipMC_",
"chayapak"
],
"contact": {
"homepage": "https://chipmunk.land/",
"sources": "https://code.chipmunk.land/ChipmunkMC/fabric-mod-mabe"
"homepage": "https://chayapak.chipmunk.land/",
"sources": "https://code.chipmunk.land/ChomeNS/chipmunkmod"
},
"license": "CC0-1.0",
@ -23,7 +24,7 @@
]
},
"mixins": [
"modid.mixins.json"
"chipmunkmod.mixins.json"
],
"depends": {

View file

@ -1,14 +0,0 @@
{
"required": true,
"minVersion": "0.8",
"package": "land.chipmunk.chipmunkmod.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
],
"client": [
"ChatScreenMixin"
],
"injectors": {
"defaultRequire": 1
}
}