Added my code and the build

This commit is contained in:
hhhzzzsss 2020-08-09 21:34:51 -05:00 committed by GitHub
parent 56b8d24117
commit d6ce6a59c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 1451 additions and 0 deletions

View file

@ -0,0 +1,36 @@
{
"mappings": {
"com/github/hhhzzzsss/songplayer/mixin/KeyboardMixin": {
"onKey(JIIII)V": "Lnet/minecraft/class_309;method_1466(JIIII)V"
},
"com/github/hhhzzzsss/songplayer/mixin/ClientPlayNetworkHandlerMixin": {
"sendPacket(Lnet/minecraft/network/Packet;)V": "Lnet/minecraft/class_634;method_2883(Lnet/minecraft/class_2596;)V",
"onBlockUpdate(Lnet/minecraft/network/packet/s2c/play/BlockUpdateS2CPacket;)V": "Lnet/minecraft/class_634;method_11136(Lnet/minecraft/class_2626;)V",
"onGameJoin(Lnet/minecraft/network/packet/s2c/play/GameJoinS2CPacket;)V": "Lnet/minecraft/class_634;method_11120(Lnet/minecraft/class_2678;)V"
},
"com/github/hhhzzzsss/songplayer/mixin/ClientPlayerEntityMixin": {
"sendChatMessage(Ljava/lang/String;)V": "Lnet/minecraft/class_746;method_3142(Ljava/lang/String;)V"
},
"com/github/hhhzzzsss/songplayer/mixin/MinecraftClientMixin": {
"doItemUse()V": "Lnet/minecraft/class_310;method_1583()V"
}
},
"data": {
"named:intermediary": {
"com/github/hhhzzzsss/songplayer/mixin/KeyboardMixin": {
"onKey(JIIII)V": "Lnet/minecraft/class_309;method_1466(JIIII)V"
},
"com/github/hhhzzzsss/songplayer/mixin/ClientPlayNetworkHandlerMixin": {
"sendPacket(Lnet/minecraft/network/Packet;)V": "Lnet/minecraft/class_634;method_2883(Lnet/minecraft/class_2596;)V",
"onBlockUpdate(Lnet/minecraft/network/packet/s2c/play/BlockUpdateS2CPacket;)V": "Lnet/minecraft/class_634;method_11136(Lnet/minecraft/class_2626;)V",
"onGameJoin(Lnet/minecraft/network/packet/s2c/play/GameJoinS2CPacket;)V": "Lnet/minecraft/class_634;method_11120(Lnet/minecraft/class_2678;)V"
},
"com/github/hhhzzzsss/songplayer/mixin/ClientPlayerEntityMixin": {
"sendChatMessage(Ljava/lang/String;)V": "Lnet/minecraft/class_746;method_3142(Ljava/lang/String;)V"
},
"com/github/hhhzzzsss/songplayer/mixin/MinecraftClientMixin": {
"doItemUse()V": "Lnet/minecraft/class_310;method_1583()V"
}
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,2 @@
v1 named intermediary
FIELD com/github/hhhzzzsss/songplayer/mixin/ClientPlayNetworkHandlerMixin Lnet/minecraft/network/ClientConnection; connection field_3689

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

View file

@ -0,0 +1,37 @@
{
"schemaVersion": 1,
"id": "songplayer",
"version": "1.0.0",
"name": "Song Player",
"description": "Builds and plays noteblocks",
"authors": [
"hhhzzzsss"
],
"contact": {
"homepage": "https://fabricmc.net/",
"sources": "https://github.com/FabricMC/fabric-example-mod"
},
"license": "CC0-1.0",
"icon": "assets/songplayer/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"com.github.hhhzzzsss.songplayer.SongPlayer"
]
},
"mixins": [
"songplayer.mixins.json"
],
"depends": {
"fabricloader": ">=0.7.4",
"fabric": "*",
"minecraft": "1.16.x"
},
"suggests": {
"flamingo": "*"
}
}

View file

@ -0,0 +1,15 @@
{
"required": true,
"minVersion": "0.8",
"package": "com.github.hhhzzzsss.songplayer.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
],
"client": [
"ClientPlayerEntityMixin",
"ClientPlayNetworkHandlerMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,44 @@
com/github/hhhzzzsss/songplayer/SongPlayer.java
com.github.hhhzzzsss.songplayer.SongPlayer
com.github.hhhzzzsss.songplayer.SongPlayer$Mode
com/github/hhhzzzsss/songplayer/mixin/ClientPlayerEntityMixin.java
com.github.hhhzzzsss.songplayer.mixin.ClientPlayerEntityMixin
com/github/hhhzzzsss/songplayer/mixin/KeyboardMixin.java
com.github.hhhzzzsss.songplayer.mixin.KeyboardMixin
com/github/hhhzzzsss/songplayer/mixin/MinecraftClientMixin.java
com.github.hhhzzzsss.songplayer.mixin.MinecraftClientMixin
com/github/hhhzzzsss/songplayer/noteblocks/Stage.java
com.github.hhhzzzsss.songplayer.noteblocks.Stage
com/github/hhhzzzsss/songplayer/song/MidiConverter.java
com.github.hhhzzzsss.songplayer.song.MidiConverter
com.github.hhhzzzsss.songplayer.song.MidiConverter$1
com/github/hhhzzzsss/songplayer/song/NoteEvent.java
com.github.hhhzzzsss.songplayer.song.NoteEvent
com/github/hhhzzzsss/songplayer/noteblocks/BuildingThread.java
com.github.hhhzzzsss.songplayer.noteblocks.BuildingThread
com/github/hhhzzzsss/songplayer/FakePlayerEntity.java
com.github.hhhzzzsss.songplayer.FakePlayerEntity
com/github/hhhzzzsss/songplayer/Freecam.java
com.github.hhhzzzsss.songplayer.Freecam
com/github/hhhzzzsss/songplayer/song/Song.java
com.github.hhhzzzsss.songplayer.song.Song
com/github/hhhzzzsss/songplayer/CommandProcessor.java
com.github.hhhzzzsss.songplayer.CommandProcessor
com.github.hhhzzzsss.songplayer.CommandProcessor$1
com.github.hhhzzzsss.songplayer.CommandProcessor$Command
com.github.hhhzzzsss.songplayer.CommandProcessor$currentCommand
com.github.hhhzzzsss.songplayer.CommandProcessor$gotoCommand
com.github.hhhzzzsss.songplayer.CommandProcessor$helpCommand
com.github.hhhzzzsss.songplayer.CommandProcessor$loopCommand
com.github.hhhzzzsss.songplayer.CommandProcessor$playCommand
com.github.hhhzzzsss.songplayer.CommandProcessor$playurlCommand
com.github.hhhzzzsss.songplayer.CommandProcessor$setCreativeCommandCommand
com.github.hhhzzzsss.songplayer.CommandProcessor$setSurvivalCommandCommand
com.github.hhhzzzsss.songplayer.CommandProcessor$songsCommand
com.github.hhhzzzsss.songplayer.CommandProcessor$stopCommand
com/github/hhhzzzsss/songplayer/mixin/ClientPlayNetworkHandlerMixin.java
com.github.hhhzzzsss.songplayer.mixin.ClientPlayNetworkHandlerMixin
com/github/hhhzzzsss/songplayer/noteblocks/PlayingThread.java
com.github.hhhzzzsss.songplayer.noteblocks.PlayingThread
com/github/hhhzzzsss/songplayer/song/DownloadingThread.java
com.github.hhhzzzsss.songplayer.song.DownloadingThread

View file

@ -0,0 +1,2 @@
Manifest-Version: 1.0

View file

@ -0,0 +1,2 @@
Manifest-Version: 1.0

View file

@ -0,0 +1,2 @@
Manifest-Version: 1.0

View file

@ -0,0 +1,358 @@
package com.github.hhhzzzsss.songplayer;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.github.hhhzzzsss.songplayer.SongPlayer.Mode;
import com.github.hhhzzzsss.songplayer.noteblocks.BuildingThread;
import com.github.hhhzzzsss.songplayer.noteblocks.Stage;
import com.github.hhhzzzsss.songplayer.song.DownloadingThread;
import com.github.hhhzzzsss.songplayer.song.Song;
public class CommandProcessor {
public static ArrayList<Command> commands = new ArrayList<>();
public static void initCommands() {
commands.add(new helpCommand());
commands.add(new playCommand());
commands.add(new playurlCommand());
commands.add(new stopCommand());
commands.add(new gotoCommand());
commands.add(new loopCommand());
commands.add(new currentCommand());
commands.add(new songsCommand());
commands.add(new setCreativeCommandCommand());
commands.add(new setSurvivalCommandCommand());
}
// returns true if it is a command and should be cancelled
public static boolean processChatMessage(String message) {
if (message.startsWith("$")) {
String[] parts = message.substring(1).split(" ", 2);
String name = parts.length>0 ? parts[0] : "";
String args = parts.length>1 ? parts[1] : "";
for (Command c : commands) {
if (c.getName().equalsIgnoreCase(name)) {
boolean success = c.processCommand(args);
if (!success) {
SongPlayer.addChatMessage("§cSyntax - " + c.getSyntax());
}
return true;
}
}
}
return false;
}
private static abstract class Command {
public abstract String getName();
public abstract String getSyntax();
public abstract String getDescription();
public abstract boolean processCommand(String args);
}
private static class helpCommand extends Command {
public String getName() {
return "help";
}
public String getSyntax() {
return "$help [command]";
}
public String getDescription() {
return "Lists commands or explains command";
}
public boolean processCommand(String args) {
if (args.length() == 0) {
StringBuilder helpMessage = new StringBuilder("§6Commands -");
for (Command c : commands) {
helpMessage.append(" $" + c.getName());
}
SongPlayer.addChatMessage(helpMessage.toString());
return true;
}
else {
for (Command c : commands) {
if (c.getName().equalsIgnoreCase(args)) {
SongPlayer.addChatMessage("§6" + c.getName() + ": " + c.getDescription() + " - " + c.getSyntax());
return true;
}
}
SongPlayer.addChatMessage("§cCommand not recognized: " + args);
return true;
}
}
}
private static class playCommand extends Command {
public String getName() {
return "play";
}
public String getSyntax() {
return "$play <song>";
}
public String getDescription() {
return "Plays a song";
}
public boolean processCommand(String args) {
if (SongPlayer.mode != Mode.IDLE) {
SongPlayer.addChatMessage("§cCannot do that while building or playing");
return true;
}
if (args.length() > 0) {
try {
SongPlayer.song = Song.getSongFromFile(args);
}
catch (IOException e) {
SongPlayer.addChatMessage("§cCould not find song §4" + args);
return true;
}
SongPlayer.stage = new Stage();
SongPlayer.stage.movePlayerToStagePosition();
SongPlayer.mode = Mode.BUILDING;
SongPlayer.addChatMessage("§6Starting building.");
SongPlayer.song.position = 0;
(new BuildingThread()).start();
return true;
}
else {
return false;
}
}
}
private static class playurlCommand extends Command {
public String getName() {
return "playurl";
}
public String getSyntax() {
return "$playurl <midi url>";
}
public String getDescription() {
return "Plays a song from a direct link to the midi";
}
public boolean processCommand(String args) {
if (SongPlayer.mode != Mode.IDLE) {
SongPlayer.addChatMessage("§cCannot do that while building or playing");
return true;
}
if (args.length() > 0) {
SongPlayer.stage = new Stage();
SongPlayer.stage.movePlayerToStagePosition();
SongPlayer.addChatMessage("§6Downloading song from url");
SongPlayer.mode = Mode.DOWNLOADING;
(new DownloadingThread(args)).start();
return true;
}
else {
return false;
}
}
}
private static class stopCommand extends Command {
public String getName() {
return "stop";
}
public String getSyntax() {
return "$stop";
}
public String getDescription() {
return "Stops playing";
}
public boolean processCommand(String args) {
if (SongPlayer.mode != Mode.PLAYING && SongPlayer.mode != Mode.BUILDING) {
SongPlayer.addChatMessage("§6No song is currently playing");
return true;
}
if (args.length() == 0) {
SongPlayer.stage.movePlayerToStagePosition();
SongPlayer.mode = Mode.IDLE;
SongPlayer.song.loop = false;
SongPlayer.addChatMessage("§6Stopped playing");
return true;
}
else {
return false;
}
}
}
private static class gotoCommand extends Command {
public String getName() {
return "goto";
}
public String getSyntax() {
return "$goto <mm:ss>";
}
public String getDescription() {
return "Goes to a specific time in the song";
}
public boolean processCommand(String args) {
if (SongPlayer.mode != Mode.PLAYING) {
SongPlayer.addChatMessage("§cNo song is currently playing");
return true;
}
if (args.length() > 0) {
Pattern timestamp_pattern = Pattern.compile("(\\d+):(\\d+)");
Matcher timestamp_matcher = timestamp_pattern.matcher(args);
if (timestamp_matcher.matches()) {
String minutes = timestamp_matcher.group(1);
String seconds = timestamp_matcher.group(2);
SongPlayer.song.gotoTime = Integer.parseInt(minutes)*60*1000 + Integer.parseInt(seconds)*1000;
System.out.println("set time to " + SongPlayer.song.gotoTime);
return true;
}
else {
SongPlayer.addChatMessage("§cNot a valid time stamp");
return true;
}
}
else {
return false;
}
}
}
private static class loopCommand extends Command {
public String getName() {
return "loop";
}
public String getSyntax() {
return "$loop";
}
public String getDescription() {
return "Toggles song looping";
}
public boolean processCommand(String args) {
if (SongPlayer.mode != Mode.PLAYING) {
SongPlayer.addChatMessage("§cNo song is currently playing");
return true;
}
SongPlayer.song.loop = !SongPlayer.song.loop;
if (SongPlayer.song.loop) {
SongPlayer.addChatMessage("§6Enabled looping");
}
else {
SongPlayer.addChatMessage("§6Disabled looping");
}
return true;
}
}
private static class currentCommand extends Command {
public String getName() {
return "current";
}
public String getSyntax() {
return "$current";
}
public String getDescription() {
return "Gets the song that is currently playing";
}
public boolean processCommand(String args) {
if (SongPlayer.mode != Mode.PLAYING) {
SongPlayer.addChatMessage("§6No song is currently playing");
return true;
}
if (args.length() == 0) {
int currTime = (int) (SongPlayer.song.get(SongPlayer.song.position).time/1000);
int totTime = (int) (SongPlayer.song.get(SongPlayer.song.size()-1).time/1000);
int currTimeSeconds = currTime % 60;
int totTimeSeconds = totTime % 60;
int currTimeMinutes = currTime / 60;
int totTimeMinutes = totTime / 60;
SongPlayer.addChatMessage(String.format("§6Currently playing %s §3(%d:%02d/%d:%02d)", SongPlayer.song.name, currTimeMinutes, currTimeSeconds, totTimeMinutes, totTimeSeconds));
return true;
}
else {
return false;
}
}
}
private static class songsCommand extends Command {
public String getName() {
return "songs";
}
public String getSyntax() {
return "$songs";
}
public String getDescription() {
return "Lists available songs";
}
public boolean processCommand(String args) {
if (args.length() == 0) {
StringBuilder sb = new StringBuilder("§6");
boolean firstItem = true;
for (File songFile : SongPlayer.SONG_DIR.listFiles()) {
String fileName = songFile.getName();
if (firstItem) {
firstItem = false;
}
else {
sb.append(", ");
}
sb.append(fileName);
}
SongPlayer.addChatMessage(sb.toString());
return true;
}
else {
return false;
}
}
}
private static class setCreativeCommandCommand extends Command {
public String getName() {
return "setCreativeCommand";
}
public String getSyntax() {
return "$setCreativeCommand";
}
public String getDescription() {
return "Sets the command used to go into creative mode";
}
public boolean processCommand(String args) {
if (args.length() > 0) {
SongPlayer.creativeCommand = args;
SongPlayer.addChatMessage("Set creative command to " + SongPlayer.creativeCommand);
return true;
}
else {
return false;
}
}
}
private static class setSurvivalCommandCommand extends Command {
public String getName() {
return "setSurvivalCommand";
}
public String getSyntax() {
return "$setSurvivalCommand";
}
public String getDescription() {
return "Sets the command used to go into survival mode";
}
public boolean processCommand(String args) {
if (args.length() > 0) {
SongPlayer.survivalCommand = args;
SongPlayer.addChatMessage("Set survival command to " + SongPlayer.survivalCommand);
return true;
}
else {
return false;
}
}
}
}

View file

@ -0,0 +1,35 @@
package com.github.hhhzzzsss.songplayer;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.network.OtherClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.player.PlayerEntity;
public class FakePlayerEntity extends OtherClientPlayerEntity {
ClientPlayerEntity player = SongPlayer.MC.player;
ClientWorld world = SongPlayer.MC.world;
public FakePlayerEntity() {
super(SongPlayer.MC.world, SongPlayer.MC.player.getGameProfile());
copyPositionAndRotation(player);
inventory.clone(player.inventory);
Byte playerModel = player.getDataTracker().get(PlayerEntity.PLAYER_MODEL_PARTS);
getDataTracker().set(PlayerEntity.PLAYER_MODEL_PARTS, playerModel);
headYaw = player.headYaw;
bodyYaw = player.bodyYaw;
capeX = getX();
capeY = getY();
capeZ = getZ();
world.addEntity(getEntityId(), this);
}
public void resetPlayerPosition() {
player.refreshPositionAndAngles(getX(), getY(), getZ(), yaw, pitch);
}
}

View file

@ -0,0 +1,58 @@
package com.github.hhhzzzsss.songplayer;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.text.LiteralText;
import net.minecraft.util.math.Vec3d;
public class Freecam {
private static Freecam instance = null;
public static Freecam getInstance() {
if (instance == null) {
instance = new Freecam();
}
return instance;
}
boolean enabled = false;
private final ClientPlayerEntity player = SongPlayer.MC.player;
private FakePlayerEntity fakePlayer;
private Freecam() {
}
public void enable() {
enabled = true;
fakePlayer = new FakePlayerEntity();
SongPlayer.addChatMessage("Freecam is enabled");
}
public void disable() {
enabled = false;
if (fakePlayer != null) {
fakePlayer.resetPlayerPosition();
fakePlayer.remove();
fakePlayer = null;
player.setVelocity(Vec3d.ZERO);
}
SongPlayer.addChatMessage("Freecam is disabled");
}
public void onGameJoin() {
enabled = false;
fakePlayer = null;
}
public void toggle() {
if (enabled) {
disable();
}
else {
enable();
}
}
public boolean isEnabled() {
return enabled;
}
}

View file

@ -0,0 +1,44 @@
package com.github.hhhzzzsss.songplayer;
import java.io.File;
import com.github.hhhzzzsss.songplayer.noteblocks.Stage;
import com.github.hhhzzzsss.songplayer.song.Song;
import net.fabricmc.api.ModInitializer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.LiteralText;
public class SongPlayer implements ModInitializer {
public static final MinecraftClient MC = MinecraftClient.getInstance();
Freecam freecam;
public static final File SONG_DIR = new File("songs");
public static Song song;
public static Stage stage;
public static String creativeCommand = "/gmc";
public static String survivalCommand = "/gms";
public static enum Mode {
IDLE,
BUILDING,
PLAYING,
DOWNLOADING,
}
public static Mode mode = Mode.IDLE;
@Override
public void onInitialize() {
if (!SONG_DIR.exists()) {
SONG_DIR.mkdir();
}
freecam = Freecam.getInstance();
CommandProcessor.initCommands();
}
public static void addChatMessage(String message) {
MC.player.sendMessage(new LiteralText(message), false);
}
}

View file

@ -0,0 +1,50 @@
package com.github.hhhzzzsss.songplayer.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 com.github.hhhzzzsss.songplayer.SongPlayer;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.Packet;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket;
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
@Mixin(ClientPlayNetworkHandler.class)
public class ClientPlayNetworkHandlerMixin {
@Shadow
private final ClientConnection connection;
public ClientPlayNetworkHandlerMixin() {
connection = null;
}
@Inject(at = @At("HEAD"), method = "sendPacket(Lnet/minecraft/network/Packet;)V", cancellable = true)
private void onSendPacket(Packet<?> packet, CallbackInfo ci) {
/*if (Freecam.getInstance().isEnabled() && packet instanceof PlayerMoveC2SPacket) {
ci.cancel();
}*/
if (SongPlayer.mode != SongPlayer.Mode.IDLE && packet instanceof PlayerMoveC2SPacket) {
connection.send(new PlayerMoveC2SPacket.Both(SongPlayer.stage.position.getX()+0.5, SongPlayer.stage.position.getY(), SongPlayer.stage.position.getZ()+0.5, SongPlayer.MC.player.yaw, SongPlayer.MC.player.pitch, true));
ci.cancel();
}
}
@Inject(at = @At("HEAD"), method = "onGameJoin(Lnet/minecraft/network/packet/s2c/play/GameJoinS2CPacket;)V")
public void onOnGameJoin(GameJoinS2CPacket packet, CallbackInfo ci) {
//Freecam.getInstance().onGameJoin();
SongPlayer.mode = SongPlayer.Mode.IDLE;
}
@Inject(at = @At("HEAD"), method = "onBlockUpdate(Lnet/minecraft/network/packet/s2c/play/BlockUpdateS2CPacket;)V")
public void onOnBlockUpdate(BlockUpdateS2CPacket packet, CallbackInfo ci) {
if (SongPlayer.mode == SongPlayer.Mode.PLAYING && SongPlayer.stage.noteblockPositions.contains(packet.getPos())) {
SongPlayer.stage.rebuild = true;
}
}
}

View file

@ -0,0 +1,21 @@
package com.github.hhhzzzsss.songplayer.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.github.hhhzzzsss.songplayer.CommandProcessor;
import net.minecraft.client.network.ClientPlayerEntity;
@Mixin(ClientPlayerEntity.class)
public class ClientPlayerEntityMixin {
@Inject(at = @At("HEAD"), method = "sendChatMessage(Ljava/lang/String;)V", cancellable=true)
private void onSendChatMessage(String message, CallbackInfo ci) {
boolean isCommand = CommandProcessor.processChatMessage(message);
if (isCommand) {
ci.cancel();
}
}
}

View file

@ -0,0 +1,23 @@
package com.github.hhhzzzsss.songplayer.mixin;
import org.lwjgl.glfw.GLFW;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.github.hhhzzzsss.songplayer.Freecam;
import com.github.hhhzzzsss.songplayer.SongPlayer;
import net.minecraft.client.Keyboard;
import net.minecraft.client.util.InputUtil;
@Mixin(Keyboard.class)
public class KeyboardMixin {
@Inject(at = @At("HEAD"), method = "onKey(JIIII)V")
private void onOnKey(long window, int key, int scancode, int i, int j, CallbackInfo ci) {
if (SongPlayer.MC.currentScreen == null && i == GLFW.GLFW_PRESS && InputUtil.fromKeyCode(key, scancode).getTranslationKey().equals("key.keyboard.p")) {
Freecam.getInstance().toggle();
}
}
}

View file

@ -0,0 +1,26 @@
package com.github.hhhzzzsss.songplayer.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.github.hhhzzzsss.songplayer.SongPlayer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult.Type;
import net.minecraft.util.math.BlockPos;
@Mixin(MinecraftClient.class)
public class MinecraftClientMixin {
@Inject(at = @At("HEAD"), method = "doItemUse()V")
public void onDoItemUse(CallbackInfo ci) {
Type type = SongPlayer.MC.crosshairTarget.getType();
if (type == Type.BLOCK) {
BlockHitResult blockHitResult = (BlockHitResult) SongPlayer.MC.crosshairTarget;
BlockPos pos = blockHitResult.getBlockPos();
System.out.println(blockHitResult.getSide() + ": " + pos.getX() + " " + pos.getY() + " " + pos.getZ());
}
}
}

View file

@ -0,0 +1,185 @@
package com.github.hhhzzzsss.songplayer.noteblocks;
import java.util.ArrayList;
import com.github.hhhzzzsss.songplayer.SongPlayer;
import com.github.hhhzzzsss.songplayer.song.Song;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.GameMode;
public class BuildingThread extends Thread {
private final ClientPlayerEntity player = SongPlayer.MC.player;
private final PlayerInventory inventory = SongPlayer.MC.player.inventory;
private final ClientWorld world = SongPlayer.MC.world;
private final Stage stage = SongPlayer.stage;
private final BlockPos stagePos = SongPlayer.stage.position;
private final Song song = SongPlayer.song;
private final int NOTEBLOCK_BASE_ID = 249;
private final String[] instrumentNames = {"harp", "basedrum", "snare", "hat", "bass", "flute", "bell", "guitar", "chime", "xylophone", "iron_xylophone", "cow_bell", "didgeridoo", "bit", "banjo", "pling"};
private boolean[] missingNotes = new boolean[400];
public void run() {
for (int i=0; i<400; i++) {
missingNotes[i] = song.requiredNotes[i];
}
stage.noteblockPositions.clear();
ArrayList<BlockPos> unusedNoteblockLocations = new ArrayList<>();
for (int dy : new int[] {-1,2}) {
for (int dx = -4; dx <= 4; dx++) {
for (int dz = -4; dz <= 4; dz++) {
if (Math.abs(dx) == 4 && Math.abs(dz) == 4) continue;
BlockPos pos = new BlockPos(stagePos.getX()+dx, stagePos.getY()+dy, stagePos.getZ()+dz);
BlockState bs = world.getBlockState(pos);
int blockId = Block.getRawIdFromState(bs);
if (blockId >= 249 && blockId <= 1048) {
int noteId = (blockId-NOTEBLOCK_BASE_ID)/2;
if (missingNotes[noteId]) {
stage.tunedNoteblocks[noteId] = pos;
missingNotes[noteId] = false;
stage.noteblockPositions.add(pos);
}
else {
unusedNoteblockLocations.add(pos);
}
}
else {
unusedNoteblockLocations.add(pos);
}
}
}
}
int idx = 0;
for (int i=0; i<400; i++) {
if (idx == unusedNoteblockLocations.size()) {
System.out.println("Too many noteblocks!");
break;
}
if (missingNotes[i]) {
stage.tunedNoteblocks[i] = unusedNoteblockLocations.get(idx++);
stage.noteblockPositions.add(stage.tunedNoteblocks[i]);
}
}
player.sendChatMessage(SongPlayer.creativeCommand);
try { //delay in case of block updates
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
while (SongPlayer.MC.interactionManager.getCurrentGameMode() != GameMode.CREATIVE) {
if (SongPlayer.mode != SongPlayer.Mode.BUILDING) {return;}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
player.abilities.allowFlying = true;
player.abilities.flying = true;
SongPlayer.stage.movePlayerToStagePosition();
for (int dy : new int[] {0,1,3}) {
for (int dx = -4; dx <= 4; dx++) {
for (int dz = -4; dz <= 4; dz++) {
if (SongPlayer.mode != SongPlayer.Mode.BUILDING) {return;}
if (Math.abs(dx) == 4 && Math.abs(dz) == 4) continue;
int x = stagePos.getX() + dx;
int y = stagePos.getY() + dy;
int z = stagePos.getZ() + dz;
if (Block.getRawIdFromState(world.getBlockState(new BlockPos(x, y, z))) != 0) {
SongPlayer.MC.interactionManager.attackBlock(new BlockPos(x, y, z), Direction.UP);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
}
}
}
System.out.println("done clearing blocks");
for (int i=0; i<400; i++) if (song.requiredNotes[i]) {
if (SongPlayer.mode != SongPlayer.Mode.BUILDING) {return;}
BlockPos p = stage.tunedNoteblocks[i];
int blockId = Block.getRawIdFromState(world.getBlockState(p));
int currentNoteId = (blockId-NOTEBLOCK_BASE_ID)/2;
int desiredNoteId = i;
if (currentNoteId != desiredNoteId) {
holdNoteblock(desiredNoteId);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
if (blockId != 0) {
SongPlayer.MC.interactionManager.attackBlock(p, Direction.UP);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
placeBlock(p);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
}
System.out.println("done placing blocks");
stage.rebuild = false;
SongPlayer.mode = SongPlayer.Mode.PLAYING;
SongPlayer.addChatMessage("§6Noteblocks are built. Now playing " + song.name + ".");
(new PlayingThread()).start();
}
private void holdNoteblock(int id) {
int instrument = id/25;
int note = id%25;
CompoundTag nbt = new CompoundTag();
nbt.putString("id", "minecraft:note_block");
nbt.putByte("Count", (byte) 1);
CompoundTag tag = new CompoundTag();
CompoundTag bsTag = new CompoundTag();
bsTag.putString("instrument", instrumentNames[instrument]);
bsTag.putString("note", Integer.toString(note));
tag.put("BlockStateTag", bsTag);
nbt.put("tag", tag);
inventory.main.set(inventory.selectedSlot, ItemStack.fromTag(nbt));
SongPlayer.MC.interactionManager.clickCreativeStack(player.getStackInHand(Hand.MAIN_HAND), 36 + inventory.selectedSlot);
}
private void placeBlock(BlockPos p) {
double fx = Math.max(0.0, Math.min(1.0, (stage.position.getX() + 0.5 - p.getX())));
double fy = Math.max(0.0, Math.min(1.0, (stage.position.getY() + 0.0 - p.getY())));
double fz = Math.max(0.0, Math.min(1.0, (stage.position.getZ() + 0.5 - p.getZ())));
fx += p.getX();
fy += p.getY();
fz += p.getZ();
SongPlayer.MC.interactionManager.interactBlock(player, world, Hand.MAIN_HAND, new BlockHitResult(new Vec3d(fx, fy, fz), Direction.UP, p, false));
}
}

View file

@ -0,0 +1,92 @@
package com.github.hhhzzzsss.songplayer.noteblocks;
import com.github.hhhzzzsss.songplayer.SongPlayer;
import com.github.hhhzzzsss.songplayer.song.Song;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.util.math.Direction;
import net.minecraft.world.GameMode;
public class PlayingThread extends Thread{
private final ClientPlayerEntity player = SongPlayer.MC.player;
private final Stage stage = SongPlayer.stage;
private final Song song = SongPlayer.song;
public void run() {
player.sendChatMessage(SongPlayer.survivalCommand);
while (SongPlayer.MC.interactionManager.getCurrentGameMode() != GameMode.SURVIVAL) {
if (SongPlayer.mode != SongPlayer.Mode.PLAYING) {return;}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
stage.rebuild = false;
player.abilities.allowFlying = true;
player.abilities.flying = true;
SongPlayer.stage.movePlayerToStagePosition();
long songStartTime = System.currentTimeMillis() - song.get(song.position).time;
while (song.position < song.size()) {
long playTime = System.currentTimeMillis() - songStartTime;
while (song.position < song.size() && song.get(song.position).time <= playTime) {
if (SongPlayer.mode != SongPlayer.Mode.PLAYING) {return;}
SongPlayer.MC.interactionManager.attackBlock(stage.tunedNoteblocks[song.get(song.position).note], Direction.UP);
SongPlayer.MC.interactionManager.cancelBlockBreaking();
song.position++;
if (stage.rebuild) {
SongPlayer.addChatMessage("§6Stage has been modified. Retuning!");
SongPlayer.mode = SongPlayer.Mode.BUILDING;
(new BuildingThread()).start();
return;
}
if (song.gotoTime > -1) {
for (int i = 0; i < song.size(); i++) {
if (song.get(i).time >= song.gotoTime) {
song.position = i;
song.gotoTime = -1;
SongPlayer.addChatMessage("§6Changed song position");
(new PlayingThread()).start();
return;
}
}
SongPlayer.addChatMessage("§cNot a valid time stamp");
song.gotoTime = -1;
}
}
if (song.position < song.size()) {
playTime = System.currentTimeMillis() - songStartTime;
long sleepTime = playTime - song.get(song.position).time;
if (sleepTime > 0) {
if (sleepTime > 200) {
System.out.println("Big sleep time: " + sleepTime);
}
try {
Thread.sleep(playTime-song.get(song.position).time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
else {
if (song.loop) {
song.position = 0;
songStartTime = System.currentTimeMillis();
}
else {
// Do nothing. While loop condition is false so loop exits.
}
}
}
player.abilities.allowFlying = true;
player.abilities.flying = true;
SongPlayer.stage.movePlayerToStagePosition();
SongPlayer.addChatMessage("§6Finished playing.");
SongPlayer.mode = SongPlayer.Mode.IDLE;
}
}

View file

@ -0,0 +1,27 @@
package com.github.hhhzzzsss.songplayer.noteblocks;
import java.util.HashSet;
import com.github.hhhzzzsss.songplayer.SongPlayer;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
public class Stage {
private final ClientPlayerEntity player = SongPlayer.MC.player;
public BlockPos position;
public BlockPos[] tunedNoteblocks = new BlockPos[400];
public HashSet<BlockPos> noteblockPositions = new HashSet<>();
public boolean rebuild = false;
public Stage() {
position = player.getBlockPos();
}
public void movePlayerToStagePosition() {
player.refreshPositionAndAngles(position.getX() + 0.5, position.getY() + 0.0, position.getZ() + 0.5, player.yaw, player.pitch);
player.setVelocity(Vec3d.ZERO);
}
}

View file

@ -0,0 +1,26 @@
package com.github.hhhzzzsss.songplayer.song;
import com.github.hhhzzzsss.songplayer.SongPlayer;
import com.github.hhhzzzsss.songplayer.noteblocks.BuildingThread;
public class DownloadingThread extends Thread{
private String url;
public DownloadingThread(String url) {
this.url = url;
}
public void run() {
try {
SongPlayer.song = Song.getSongFromUrl(url);
SongPlayer.addChatMessage("§6Finished downloading song");
SongPlayer.mode = SongPlayer.Mode.BUILDING;
SongPlayer.addChatMessage("§6Starting building.");
(new BuildingThread()).start();
return;
} catch (Exception e) {
SongPlayer.addChatMessage("§cError getting song from url: " + e.getMessage());
SongPlayer.mode = SongPlayer.Mode.IDLE;
}
}
}

View file

@ -0,0 +1,230 @@
package com.github.hhhzzzsss.songplayer.song;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;
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 int[] instrument_offsets = new int[] {
54, //harp
0, //basedrum
0, //snare
0, //hat
30, //bass
66, //flute
78, //bell
42, //guitar
78, //chime
78, //xylophone
54, //iron xylophone
66, //cow bell
30, //didgeridoo
54, //bit
54, //banjo
54, //electric piano
};
public static String fileName = "moskau";
public static TreeMap<Long, ArrayList<Integer>> noteMap;
public static TreeMap<Long, ArrayList<Integer>> getMidi(String url) throws Exception {
noteMap = new TreeMap<>();
//SocketAddress addr = new InetSocketAddress("34.94.90.93", 3128);
//Proxy proxy = new Proxy(Proxy.Type.HTTP, addr);
URLConnection conn = (new URL(url)).openConnection();
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0");
BufferedInputStream downloadStream = new BufferedInputStream(conn.getInputStream());
boolean fileTooLarge = true;
byte tempbuf[] = new byte[10240];
for (int i=0; i<1024; i++) {
if (downloadStream.read(tempbuf, 0, 1024) == -1) {
fileTooLarge = false;
break;
}
}
if (fileTooLarge) {
throw new IOException("File too large");
}
downloadStream.close();
downloadStream = new BufferedInputStream(new URL(url).openStream());
Sequence sequence = MidiSystem.getSequence(downloadStream);
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, new Comparator<MidiEvent>() {
@Override
public int compare(MidiEvent a, MidiEvent b) {
return (new Long(a.getTick())).compareTo(b.getTick());
}
});
for (Track track : sequence.getTracks()) {
long microTime = 0;
int[] instrumentIds = new int[16];
//int apparent_mpq = (int) (sequence.getMicrosecondLength()/sequence.getTickLength()*tpq);
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) {
instrumentIds[sm.getChannel()] = sm.getData1();
}
else if (sm.getCommand() == NOTE_ON) {
if (sm.getData2() == 0) continue;
int key = sm.getData1();
long deltaTick = event.getTick() - prevTick;
prevTick = event.getTick();
microTime += (mpq/tpq) * deltaTick;
if (sm.getChannel() == 9) {
processMidiNote(128, sm.getData1(), microTime);
}
else {
processMidiNote(instrumentIds[sm.getChannel()], sm.getData1(), microTime);
}
}
else {
}
}
}
}
downloadStream.close();
return noteMap;
}
public static void processMidiNote(int midiInstrument, int midiPitch, long microTime) {
int minecraftInstrument = -1;
if ((midiInstrument >= 0 && midiInstrument <= 7) || (midiInstrument >= 24 && midiInstrument <= 31)) { //normal
if (midiPitch >= 54 && midiPitch <= 78) {
minecraftInstrument = 0; //piano
}
else if (midiPitch >= 30 && midiPitch <= 54) {
minecraftInstrument = 4; //bass
}
else if (midiPitch >= 78 && midiPitch <= 102) {
minecraftInstrument = 6; //bells
}
}
else if (midiInstrument >= 8 && midiInstrument <= 15) { //chromatic percussion
if (midiPitch >= 54 && midiPitch <= 78) {
minecraftInstrument = 10; //iron xylophone
}
else if (midiPitch >= 78 && midiPitch <= 102) {
minecraftInstrument = 9; //xylophone
}
else if (midiPitch >= 30 && midiPitch <= 54) {
minecraftInstrument = 4; //bass
}
}
else if ((midiInstrument >= 16 && midiInstrument <= 23) || (midiInstrument >= 32 && midiInstrument <= 71) || (midiInstrument >= 80 && midiInstrument <= 111)) { //synth
if (midiPitch >= 54 && midiPitch <= 78) {
minecraftInstrument = 13; //bit
}
else if (midiPitch >= 30 && midiPitch <= 54) { //didgeridoo
minecraftInstrument = 12;
}
else if (midiPitch >= 78 && midiPitch <= 102) { //bells
minecraftInstrument = 6;
}
}
else if ((midiInstrument >= 72 && midiInstrument <= 79)) { //woodwind
if (midiPitch >= 66 && midiPitch <= 90) {
minecraftInstrument = 5; //flute
}
else if (midiPitch >= 30 && midiPitch <= 54) { //didgeridoo
minecraftInstrument = 12;
}
else if (midiPitch >= 54 && midiPitch <= 78) {
minecraftInstrument = 13; //bit
}
else if (midiPitch >= 78 && midiPitch <= 102) { //bells
minecraftInstrument = 6;
}
}
else if (midiInstrument == 128) {
if (midiPitch == 35 || midiPitch == 36 || midiPitch == 41 || midiPitch == 43 || midiPitch == 45 || midiPitch == 57) {
minecraftInstrument = 1; //bass drum
}
else if (midiPitch == 38 || midiPitch == 39 || midiPitch == 40 || midiPitch == 54 || midiPitch == 69 || midiPitch == 70 || midiPitch == 73 || midiPitch == 74 || midiPitch == 78 || midiPitch == 79) {
minecraftInstrument = 2; //snare
}
else if (midiPitch == 37 || midiPitch == 42 || midiPitch == 44 || midiPitch == 46 || midiPitch == 49 || midiPitch == 51 || midiPitch == 52 || midiPitch == 55 || midiPitch == 57 || midiPitch == 59) {
minecraftInstrument = 3; //hat
}
midiPitch = 0;
}
long milliTime = microTime / 1000;
if (minecraftInstrument >= 0) {
int noteId = (midiPitch-instrument_offsets[minecraftInstrument]) + minecraftInstrument*25;
if (!noteMap.containsKey(milliTime)) {
noteMap.put(milliTime, new ArrayList<Integer>());
}
if (!noteMap.get(milliTime).contains(noteId)) {
noteMap.get(milliTime).add(noteId);
}
}
}
}

View file

@ -0,0 +1,10 @@
package com.github.hhhzzzsss.songplayer.song;
public class NoteEvent {
public int note;
public long time;
public NoteEvent(int note, long time) {
this.note = note;
this.time = time;
}
}

View file

@ -0,0 +1,74 @@
package com.github.hhhzzzsss.songplayer.song;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
import com.github.hhhzzzsss.songplayer.SongPlayer;
public class Song {
public ArrayList<NoteEvent> notes = new ArrayList<>();
public String name;
public int position = 0;
public boolean[] requiredNotes = new boolean[400];
public boolean loop = false;
public int gotoTime = -1;
private Song() {}
public NoteEvent get(int i) {
return notes.get(i);
}
public void add(NoteEvent e) {
notes.add(e);
}
public int size() {
return notes.size();
}
public static Song getSongFromFile(String file) throws IOException {
Song song = new Song();
if (file.contains("/") || file.contains("\\")) throw new FileNotFoundException();
File songPath = new File(SongPlayer.SONG_DIR, file);
if (!songPath.exists()) songPath = new File(SongPlayer.SONG_DIR, file + ".txt");
if (!songPath.exists()) throw new FileNotFoundException();
song.notes.clear();
BufferedReader br = new BufferedReader(new FileReader(songPath));
String line;
while ((line = br.readLine()) != null) {
String[] split = line.split(" ");
long time = Long.parseLong(split[0]);
int note = Integer.parseInt(split[1]);
song.requiredNotes[note] = true;
song.notes.add(new NoteEvent(note, time));
}
br.close();
song.name = songPath.getName();
return song;
}
public static Song getSongFromUrl(String url) throws Exception {
Song song = new Song();
song.notes.clear();
TreeMap<Long, ArrayList<Integer>> noteMap = MidiConverter.getMidi(url);
System.out.println(noteMap.size());
for (int i=0; i<400; i++) song.requiredNotes[i] = false;
for (Map.Entry<Long, ArrayList<Integer>> entry : noteMap.entrySet()) {
for (int note : entry.getValue()) {
long time = entry.getKey();
song.requiredNotes[note] = true;
song.add(new NoteEvent(note, time));
}
}
song.name = url.substring(url.lastIndexOf('/')+1, url.length());
return song;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

View file

@ -0,0 +1,37 @@
{
"schemaVersion": 1,
"id": "songplayer",
"version": "${version}",
"name": "Song Player",
"description": "Builds and plays noteblocks",
"authors": [
"hhhzzzsss"
],
"contact": {
"homepage": "https://fabricmc.net/",
"sources": "https://github.com/FabricMC/fabric-example-mod"
},
"license": "CC0-1.0",
"icon": "assets/songplayer/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"com.github.hhhzzzsss.songplayer.SongPlayer"
]
},
"mixins": [
"songplayer.mixins.json"
],
"depends": {
"fabricloader": ">=0.7.4",
"fabric": "*",
"minecraft": "1.16.x"
},
"suggests": {
"flamingo": "*"
}
}

View file

@ -0,0 +1,15 @@
{
"required": true,
"minVersion": "0.8",
"package": "com.github.hhhzzzsss.songplayer.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
],
"client": [
"ClientPlayerEntityMixin",
"ClientPlayNetworkHandlerMixin"
],
"injectors": {
"defaultRequire": 1
}
}