Completely rehauled SongPlayer's code

This commit is contained in:
Harry Zhou 2022-06-26 21:41:19 -05:00
parent 2e80d56e09
commit 1da7485578
21 changed files with 1090 additions and 778 deletions

View file

@ -1,27 +1,21 @@
package com.github.hhhzzzsss.songplayer;
import com.github.hhhzzzsss.songplayer.noteblocks.SongHandler;
import com.github.hhhzzzsss.songplayer.song.Song;
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;
import net.minecraft.entity.Entity;
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 skipCommand());
commands.add(new gotoCommand());
commands.add(new loopCommand());
commands.add(new currentCommand());
@ -94,67 +88,14 @@ public class CommandProcessor {
return "play";
}
public String getSyntax() {
return "$play <song>";
return "$play <song or url>";
}
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;
}
catch (Exception e) {
SongPlayer.addChatMessage("§cError getting song: " + e.getMessage());
e.printStackTrace();
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();
SongHandler.getInstance().loadSong(args);
return true;
}
else {
@ -174,18 +115,15 @@ public class CommandProcessor {
return "Stops playing";
}
public boolean processCommand(String args) {
if (SongPlayer.mode != Mode.PLAYING && SongPlayer.mode != Mode.BUILDING) {
if (SongHandler.getInstance().currentSong == null && SongHandler.getInstance().songQueue.isEmpty()) {
SongPlayer.addChatMessage("§6No song is currently playing");
return true;
}
if (args.length() == 0) {
if (SongPlayer.fakePlayer != null) {
SongPlayer.fakePlayer.remove(Entity.RemovalReason.DISCARDED);
SongPlayer.fakePlayer = null;
}
SongPlayer.stage.movePlayerToStagePosition();
SongPlayer.mode = Mode.IDLE;
SongPlayer.song.loop = false;
if (SongHandler.getInstance().stage != null) {
SongHandler.getInstance().stage.movePlayerToStagePosition();
}
SongHandler.getInstance().cleanup();
SongPlayer.addChatMessage("§6Stopped playing");
return true;
}
@ -194,6 +132,31 @@ public class CommandProcessor {
}
}
}
private static class skipCommand extends Command {
public String getName() {
return "skip";
}
public String getSyntax() {
return "$skip";
}
public String getDescription() {
return "Skips current song";
}
public boolean processCommand(String args) {
if (SongHandler.getInstance().currentSong == null) {
SongPlayer.addChatMessage("§6No song is currently playing");
return true;
}
if (args.length() == 0) {
SongHandler.getInstance().currentSong = null;
return true;
}
else {
return false;
}
}
}
private static class gotoCommand extends Command {
public String getName() {
@ -206,7 +169,7 @@ public class CommandProcessor {
return "Goes to a specific time in the song";
}
public boolean processCommand(String args) {
if (SongPlayer.mode != Mode.PLAYING) {
if (SongHandler.getInstance().currentSong == null) {
SongPlayer.addChatMessage("§cNo song is currently playing");
return true;
}
@ -218,8 +181,8 @@ public class CommandProcessor {
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);
SongHandler.getInstance().currentSong.setTime(Integer.parseInt(minutes)*60*1000 + Integer.parseInt(seconds)*1000);
SongPlayer.addChatMessage("§6Set song time to §3" + minutes + ":" + seconds);
return true;
}
else {
@ -244,13 +207,13 @@ public class CommandProcessor {
return "Toggles song looping";
}
public boolean processCommand(String args) {
if (SongPlayer.mode != Mode.PLAYING) {
if (SongHandler.getInstance().currentSong == null) {
SongPlayer.addChatMessage("§cNo song is currently playing");
return true;
}
SongPlayer.song.loop = !SongPlayer.song.loop;
if (SongPlayer.song.loop) {
SongHandler.getInstance().currentSong.looping = !SongHandler.getInstance().currentSong.looping;
if (SongHandler.getInstance().currentSong.looping) {
SongPlayer.addChatMessage("§6Enabled looping");
}
else {
@ -271,18 +234,19 @@ public class CommandProcessor {
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");
if (SongHandler.getInstance().currentSong == null) {
SongPlayer.addChatMessage("§cNo song is currently playing");
return true;
}
Song currentSong = SongHandler.getInstance().currentSong;
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 currTime = (int) (currentSong.time/1000);
int totTime = (int) (currentSong.length/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));
SongPlayer.addChatMessage(String.format("§6Currently playing %s §3(%d:%02d/%d:%02d)", currentSong.name, currTimeMinutes, currTimeSeconds, totTimeMinutes, totTimeSeconds));
return true;
}
else {
@ -340,7 +304,7 @@ public class CommandProcessor {
if (SongPlayer.creativeCommand.startsWith("/")) {
SongPlayer.creativeCommand = SongPlayer.creativeCommand.substring(1);
}
SongPlayer.addChatMessage("§6Set creative command to /" + SongPlayer.creativeCommand);
SongPlayer.addChatMessage("§6Set creative command to §3/" + SongPlayer.creativeCommand);
return true;
}
else {
@ -365,7 +329,7 @@ public class CommandProcessor {
if (SongPlayer.survivalCommand.startsWith("/")) {
SongPlayer.survivalCommand = SongPlayer.survivalCommand.substring(1);
}
SongPlayer.addChatMessage("§6Set survival command to /" + SongPlayer.survivalCommand);
SongPlayer.addChatMessage("§6Set survival command to §3/" + SongPlayer.survivalCommand);
return true;
}
else {
@ -388,18 +352,9 @@ public class CommandProcessor {
if (args.length() == 0) {
SongPlayer.showFakePlayer = !SongPlayer.showFakePlayer;
if (SongPlayer.showFakePlayer) {
if (SongPlayer.mode == Mode.PLAYING || SongPlayer.mode == Mode.BUILDING) {
if (SongPlayer.fakePlayer != null) {
SongPlayer.fakePlayer.remove(Entity.RemovalReason.DISCARDED);
}
SongPlayer.fakePlayer = new FakePlayerEntity();
}
SongPlayer.addChatMessage("§6Enabled fake player");
}
else {
if (SongPlayer.fakePlayer != null) {
SongPlayer.fakePlayer.remove(Entity.RemovalReason.DISCARDED);
}
SongPlayer.addChatMessage("§6Disabled fake player");
}
return true;

View file

@ -1,5 +1,7 @@
package com.github.hhhzzzsss.songplayer;
import com.github.hhhzzzsss.songplayer.noteblocks.SongHandler;
import com.github.hhhzzzsss.songplayer.noteblocks.Stage;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.network.OtherClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
@ -34,8 +36,9 @@ public class FakePlayerEntity extends OtherClientPlayerEntity {
}
public void copyStagePosAndPlayerLook() {
if (SongPlayer.stage != null) {
refreshPositionAndAngles(SongPlayer.stage.position.getX()+0.5, SongPlayer.stage.position.getY(), SongPlayer.stage.position.getZ()+0.5, player.getYaw(), player.getPitch());
Stage stage = SongHandler.getInstance().stage;
if (stage != null) {
refreshPositionAndAngles(stage.position.getX()+0.5, stage.position.getY(), stage.position.getZ()+0.5, player.getYaw(), player.getPitch());
headYaw = player.headYaw;
bodyYaw = player.bodyYaw;
}

View file

@ -1,58 +0,0 @@
package com.github.hhhzzzsss.songplayer;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.entity.Entity;
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(Entity.RemovalReason.DISCARDED);
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

@ -6,42 +6,40 @@ import com.github.hhhzzzsss.songplayer.noteblocks.Stage;
import com.github.hhhzzzsss.songplayer.song.Song;
import net.fabricmc.api.ModInitializer;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.LiteralTextContent;
import net.minecraft.entity.Entity;
import net.minecraft.text.Text;
public class SongPlayer implements ModInitializer {
public static final MinecraftClient MC = MinecraftClient.getInstance();
Freecam freecam;
public static final int NOTEBLOCK_BASE_ID = Block.getRawIdFromState(Blocks.NOTE_BLOCK.getDefaultState());
public static final File SONG_DIR = new File("songs");
public static Song song;
public static Stage stage;
public static boolean showFakePlayer = false;
public static FakePlayerEntity fakePlayer;
public static String creativeCommand = "/gmc";
public static String survivalCommand = "/gms";
public static enum Mode {
IDLE,
BUILDING,
PLAYING,
DOWNLOADING,
}
public static Mode mode = Mode.IDLE;
public static String creativeCommand = "gmc";
public static String survivalCommand = "gms";
@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(Text.of(message), false);
}
public static void removeFakePlayer() {
if (fakePlayer != null) {
fakePlayer.remove(Entity.RemovalReason.DISCARDED);
fakePlayer = null;
}
}
}

View file

@ -1,5 +1,8 @@
package com.github.hhhzzzsss.songplayer.mixin;
import com.github.hhhzzzsss.songplayer.noteblocks.SongHandler;
import com.github.hhhzzzsss.songplayer.noteblocks.Stage;
import net.minecraft.network.packet.s2c.play.PlayerRespawnS2CPacket;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@ -26,11 +29,9 @@ public class ClientPlayNetworkHandlerMixin {
@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.Full(SongPlayer.stage.position.getX()+0.5, SongPlayer.stage.position.getY(), SongPlayer.stage.position.getZ()+0.5, SongPlayer.MC.player.getYaw(), SongPlayer.MC.player.getPitch(), true));
Stage stage = SongHandler.getInstance().stage;
if (stage != null && packet instanceof PlayerMoveC2SPacket) {
connection.send(new PlayerMoveC2SPacket.Full(stage.position.getX()+0.5, stage.position.getY(), stage.position.getZ()+0.5, SongPlayer.MC.player.getYaw(), SongPlayer.MC.player.getPitch(), true));
if (SongPlayer.fakePlayer != null) {
SongPlayer.fakePlayer.copyStagePosAndPlayerLook();
}
@ -38,16 +39,13 @@ public class ClientPlayNetworkHandlerMixin {
}
}
@Inject(at = @At("HEAD"), method = "onGameJoin(Lnet/minecraft/network/packet/s2c/play/GameJoinS2CPacket;)V")
@Inject(at = @At("TAIL"), 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;
SongHandler.getInstance().cleanup();
}
@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;
}
@Inject(at = @At("TAIL"), method = "onPlayerRespawn(Lnet/minecraft/network/packet/s2c/play/PlayerRespawnS2CPacket;)V")
public void onOnPlayerRespawn(PlayerRespawnS2CPacket packet, CallbackInfo ci) {
SongHandler.getInstance().cleanup();
}
}

View file

@ -1,23 +0,0 @@
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

@ -1,5 +1,6 @@
package com.github.hhhzzzsss.songplayer.mixin;
import com.github.hhhzzzsss.songplayer.noteblocks.SongHandler;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@ -14,13 +15,12 @@ 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());
@Inject(at = @At("HEAD"), method = "render(Z)V")
public void onRender(boolean tick, CallbackInfo ci) {
if (SongPlayer.MC.world != null && SongPlayer.MC.player != null && SongPlayer.MC.interactionManager != null) {
SongHandler.getInstance().onRenderIngame(tick);
} else {
SongHandler.getInstance().onNotIngame();
}
}
}

View file

@ -1,194 +0,0 @@
package com.github.hhhzzzsss.songplayer.noteblocks;
import java.util.ArrayList;
import com.github.hhhzzzsss.songplayer.FakePlayerEntity;
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.block.Blocks;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
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.getInventory();
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 = Block.getRawIdFromState(Blocks.NOTE_BLOCK.getDefaultState());
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 >= NOTEBLOCK_BASE_ID && blockId < NOTEBLOCK_BASE_ID+800) {
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.sendCommand(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.getAbilities().allowFlying = true;
player.getAbilities().flying = true;
SongPlayer.stage.movePlayerToStagePosition();
if (SongPlayer.showFakePlayer) {
if (SongPlayer.fakePlayer != null) {
SongPlayer.fakePlayer.remove(Entity.RemovalReason.DISCARDED);
}
SongPlayer.fakePlayer = new FakePlayerEntity();
}
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;
NbtCompound nbt = new NbtCompound();
nbt.putString("id", "minecraft:note_block");
nbt.putByte("Count", (byte) 1);
NbtCompound tag = new NbtCompound();
NbtCompound bsTag = new NbtCompound();
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.fromNbt(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, Hand.MAIN_HAND, new BlockHitResult(new Vec3d(fx, fy, fz), Direction.UP, p, false));
}
}

View file

@ -1,97 +0,0 @@
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.entity.Entity;
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.sendCommand(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.getAbilities().allowFlying = true;
player.getAbilities().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.getAbilities().allowFlying = true;
player.getAbilities().flying = true;
SongPlayer.stage.movePlayerToStagePosition();
if (SongPlayer.fakePlayer != null) {
SongPlayer.fakePlayer.remove(Entity.RemovalReason.DISCARDED);
SongPlayer.fakePlayer = null;
}
SongPlayer.addChatMessage("§6Finished playing.");
SongPlayer.mode = SongPlayer.Mode.IDLE;
}
}

View file

@ -0,0 +1,286 @@
package com.github.hhhzzzsss.songplayer.noteblocks;
import com.github.hhhzzzsss.songplayer.FakePlayerEntity;
import com.github.hhhzzzsss.songplayer.SongPlayer;
import com.github.hhhzzzsss.songplayer.song.Instrument;
import com.github.hhhzzzsss.songplayer.song.Note;
import com.github.hhhzzzsss.songplayer.song.Song;
import com.github.hhhzzzsss.songplayer.song.SongLoaderThread;
import net.minecraft.block.Block;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
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;
import java.io.IOException;
import java.util.LinkedList;
public class SongHandler {
private static SongHandler instance = null;
public static SongHandler getInstance() {
if (instance == null) {
instance = new SongHandler();
}
return instance;
}
public SongLoaderThread loaderThread = null;
public LinkedList<Song> songQueue = new LinkedList<>();
public Song currentSong = null;
public Stage stage = null;
public boolean building = false;
public void onRenderIngame(boolean tick) {
if (currentSong == null && songQueue.size() > 0) {
setSong(songQueue.poll());
}
if (loaderThread != null && !loaderThread.isAlive()) {
if (loaderThread.exception != null) {
SongPlayer.addChatMessage("§cFailed to load song: §4" + loaderThread.exception.getMessage());
} else {
if (currentSong == null) {
setSong(loaderThread.song);
} else {
queueSong(loaderThread.song);
}
}
loaderThread = null;
}
if (currentSong == null) {
if (stage != null || SongPlayer.fakePlayer != null) {
if (stage != null) {
stage.movePlayerToStagePosition();
}
cleanup();
}
return;
}
if (stage == null) {
stage = new Stage();
stage.movePlayerToStagePosition();
}
if (SongPlayer.showFakePlayer && SongPlayer.fakePlayer == null) {
SongPlayer.fakePlayer = new FakePlayerEntity();
SongPlayer.fakePlayer.copyStagePosAndPlayerLook();
}
if (!SongPlayer.showFakePlayer && SongPlayer.fakePlayer != null) {
SongPlayer.removeFakePlayer();
}
checkCommandCache();
SongPlayer.MC.player.getAbilities().allowFlying = true;
if (building) {
if (tick) {
handleBuilding();
}
} else {
// Check if stage was broken
handlePlaying(tick);
}
}
public void loadSong(String location) {
if (loaderThread != null) {
SongPlayer.addChatMessage("§cAlready loading a song, cannot load another");
} else {
try {
loaderThread = new SongLoaderThread(location);
SongPlayer.addChatMessage("§6Loading §3" + location + "");
loaderThread.start();
} catch (IOException e) {
SongPlayer.addChatMessage("§cFailed to load song: §4" + e.getMessage());
}
}
}
private void setSong(Song song) {
currentSong = song;
building = true;
setCreativeIfNeeded();
if (stage != null) {
stage.movePlayerToStagePosition();
}
SongPlayer.addChatMessage("§6Building noteblocks");
}
private void queueSong(Song song) {
songQueue.add(song);
SongPlayer.addChatMessage("§6Added song to queue: §3" + song.name);
}
// Runs every tick
private int buildStartDelay = 0;
private int buildEndDelay = 0;
private int buildCooldown = 0;
private void handleBuilding() {
if (buildStartDelay > 0) {
buildStartDelay--;
return;
}
if (buildCooldown > 0) {
buildCooldown--;
return;
}
ClientWorld world = SongPlayer.MC.world;
ClientPlayerEntity player = SongPlayer.MC.player;
if (SongPlayer.MC.interactionManager.getCurrentGameMode() != GameMode.CREATIVE) {
return;
}
if (stage.nothingToBuild()) {
if (buildEndDelay > 0) {
buildEndDelay--;
return;
} else {
stage.checkBuildStatus(currentSong);
}
}
if (!stage.requiredBreaks.isEmpty()) {
for (int i=0; i<5; i++) {
if (stage.requiredBreaks.isEmpty()) break;
BlockPos bp = stage.requiredBreaks.poll();
SongPlayer.MC.interactionManager.attackBlock(bp, Direction.UP);
}
buildEndDelay = 40;
return;
} else if (!stage.missingNotes.isEmpty()) {
int desiredNoteId = stage.missingNotes.pollFirst();
BlockPos bp = stage.noteblockPositions.get(desiredNoteId);
int blockId = Block.getRawIdFromState(world.getBlockState(bp));
int currentNoteId = (blockId-SongPlayer.NOTEBLOCK_BASE_ID)/2;
if (currentNoteId != desiredNoteId) {
holdNoteblock(desiredNoteId);
if (blockId != 0) {
attackBlock(bp);
}
placeBlock(bp);
}
buildCooldown = 4;
buildEndDelay = 40;
} else {
building = false;
setSurvivalIfNeeded();
stage.movePlayerToStagePosition();
SongPlayer.addChatMessage("§6Now playing §3" + currentSong.name);
return;
}
}
// Runs every frame
private void handlePlaying(boolean tick) {
if (SongPlayer.MC.interactionManager.getCurrentGameMode() != GameMode.SURVIVAL) {
currentSong.pause();
return;
}
if (tick) {
stage.checkBuildStatus(currentSong);
if (!stage.missingNotes.isEmpty()) {
building = true;
setCreativeIfNeeded();
stage.movePlayerToStagePosition();
currentSong.pause();
buildStartDelay = 40;
System.out.println("Total missing notes: " + stage.missingNotes.size());
for (int note : stage.missingNotes) {
int pitch = note % 25;
int instrumentId = note / 25;
System.out.println("Missing note: " + Instrument.getInstrumentFromId(instrumentId).name() + ":" + pitch);
}
SongPlayer.addChatMessage("§6Stage was altered. Rebuilding!");
return;
}
}
currentSong.play();
currentSong.advanceTime();
while (currentSong.reachedNextNote()) {
Note note = currentSong.getNextNote();
attackBlock(stage.noteblockPositions.get(note.noteId));
}
if (currentSong.finished()) {
currentSong = null;
}
}
public void cleanup() {
currentSong = null;
songQueue.clear();
stage = null;
SongPlayer.removeFakePlayer();
}
public void onNotIngame() {
currentSong = null;
songQueue.clear();
}
private long lastCommandTime = System.currentTimeMillis();
private String cachedCommand = null;
private void sendGamemodeCommand(String command) {
cachedCommand = command;
}
private void checkCommandCache() {
if (cachedCommand != null && System.currentTimeMillis() >= lastCommandTime + 1500) {
SongPlayer.MC.player.sendCommand(cachedCommand);
cachedCommand = null;
lastCommandTime = System.currentTimeMillis();
}
}
private void setCreativeIfNeeded() {
cachedCommand = null;
if (SongPlayer.MC.interactionManager.getCurrentGameMode() != GameMode.CREATIVE) {
sendGamemodeCommand(SongPlayer.creativeCommand);
}
}
private void setSurvivalIfNeeded() {
cachedCommand = null;
if (SongPlayer.MC.interactionManager.getCurrentGameMode() != GameMode.SURVIVAL) {
sendGamemodeCommand(SongPlayer.survivalCommand);
}
}
private final String[] instrumentNames = {"harp", "basedrum", "snare", "hat", "bass", "flute", "bell", "guitar", "chime", "xylophone", "iron_xylophone", "cow_bell", "didgeridoo", "bit", "banjo", "pling"};
private void holdNoteblock(int id) {
PlayerInventory inventory = SongPlayer.MC.player.getInventory();
int instrument = id/25;
int note = id%25;
NbtCompound nbt = new NbtCompound();
nbt.putString("id", "minecraft:note_block");
nbt.putByte("Count", (byte) 1);
NbtCompound tag = new NbtCompound();
NbtCompound bsTag = new NbtCompound();
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.fromNbt(nbt));
SongPlayer.MC.interactionManager.clickCreativeStack(SongPlayer.MC.player.getStackInHand(Hand.MAIN_HAND), 36 + inventory.selectedSlot);
}
private void placeBlock(BlockPos bp) {
double fx = Math.max(0.0, Math.min(1.0, (stage.position.getX() + 0.5 - bp.getX())));
double fy = Math.max(0.0, Math.min(1.0, (stage.position.getY() + 0.0 - bp.getY())));
double fz = Math.max(0.0, Math.min(1.0, (stage.position.getZ() + 0.5 - bp.getZ())));
fx += bp.getX();
fy += bp.getY();
fz += bp.getZ();
SongPlayer.MC.interactionManager.interactBlock(SongPlayer.MC.player, Hand.MAIN_HAND, new BlockHitResult(new Vec3d(fx, fy, fz), Direction.UP, bp, false));
}
private void attackBlock(BlockPos bp) {
SongPlayer.MC.interactionManager.attackBlock(bp, Direction.UP);
}
}

View file

@ -1,9 +1,14 @@
package com.github.hhhzzzsss.songplayer.noteblocks;
import java.util.HashSet;
import java.util.*;
import java.util.stream.Collectors;
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.block.Blocks;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
@ -12,16 +17,151 @@ 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 BlockPos[] tunedNoteblocks = new BlockPos[400];
public HashMap<Integer, BlockPos> noteblockPositions = new HashMap<>();
public boolean rebuild = false;
public LinkedList<BlockPos> requiredBreaks = new LinkedList<>();
public TreeSet<Integer> missingNotes = new TreeSet<>();
public Stage() {
position = player.getBlockPos();
}
public void movePlayerToStagePosition() {
player.getAbilities().allowFlying = true;
player.getAbilities().flying = true;
player.refreshPositionAndAngles(position.getX() + 0.5, position.getY() + 0.0, position.getZ() + 0.5, player.getYaw(), player.getPitch());
player.setVelocity(Vec3d.ZERO);
}
public void checkBuildStatus(Song song) {
noteblockPositions.clear();
missingNotes.clear();
// Add all required notes to missingNotes
for (int i=0; i<400; i++) {
if (song.requiredNotes[i]) {
missingNotes.add(i);
}
}
ArrayList<BlockPos> noteblockLocations = new ArrayList<>();
ArrayList<BlockPos> breakLocations = new ArrayList<>();
for (int dx = -4; dx <= 4; dx++) {
for (int dz = -4; dz <= 4; dz++) {
if (Math.abs(dx) == 4 && Math.abs(dz) == 4) {
noteblockLocations.add(new BlockPos(position.getX() + dx, position.getY() + 0, position.getZ() + dz));
breakLocations.add(new BlockPos(position.getX() + dx, position.getY() + 1, position.getZ() + dz));
} else {
noteblockLocations.add(new BlockPos(position.getX() + dx, position.getY() - 1, position.getZ() + dz));
noteblockLocations.add(new BlockPos(position.getX() + dx, position.getY() + 2, position.getZ() + dz));
breakLocations.add(new BlockPos(position.getX() + dx, position.getY() + 0, position.getZ() + dz));
breakLocations.add(new BlockPos(position.getX() + dx, position.getY() + 1, position.getZ() + dz));
breakLocations.add(new BlockPos(position.getX() + dx, position.getY() + 3, position.getZ() + dz));
}
}
}
// Sorting noteblock and break locations
noteblockLocations.sort((a, b) -> {
// First sort by y
if (a.getY() < b.getY()) {
return -1;
} else if (a.getY() > b.getY()) {
return 1;
}
// Then sort by horizontal distance
int a_dx = a.getX() - position.getX();
int a_dz = a.getZ() - position.getZ();
int b_dx = b.getX() - position.getX();
int b_dz = b.getZ() - position.getZ();
int a_dist = a_dx*a_dx + a_dz*a_dz;
int b_dist = b_dx*b_dx + b_dz*b_dz;
if (a_dist < b_dist) {
return -1;
} else if (a_dist > b_dist) {
return 1;
}
// Finally sort by angle
double a_angle = Math.atan2(a_dz, a_dx);
double b_angle = Math.atan2(b_dz, b_dx);
if (a_angle < b_angle) {
return -1;
} else if (a_angle > b_angle) {
return 1;
} else {
return 0;
}
});
requiredBreaks = breakLocations
.stream()
.filter((bp) -> Block.getRawIdFromState(SongPlayer.MC.world.getBlockState(bp)) != 0)
.sorted((a, b) -> {
// First sort by y
if (a.getY() < b.getY()) {
return -1;
} else if (a.getY() > b.getY()) {
return 1;
}
// Then sort by horizontal distance
int a_dx = a.getX() - position.getX();
int a_dz = a.getZ() - position.getZ();
int b_dx = b.getX() - position.getX();
int b_dz = b.getZ() - position.getZ();
int a_dist = a_dx*a_dx + a_dz*a_dz;
int b_dist = b_dx*b_dx + b_dz*b_dz;
if (a_dist < b_dist) {
return -1;
} else if (a_dist > b_dist) {
return 1;
}
// Finally sort by angle
double a_angle = Math.atan2(a_dz, a_dx);
double b_angle = Math.atan2(b_dz, b_dx);
if (a_angle < b_angle) {
return -1;
} else if (a_angle > b_angle) {
return 1;
} else {
return 0;
}
})
.collect(Collectors.toCollection(LinkedList::new));
// Remove already-existing notes from missingNotes, adding their positions to noteblockPositions, and create a list of unused noteblock locations
ArrayList<BlockPos> unusedNoteblockLocations = new ArrayList<>();
for (BlockPos nbPos : noteblockLocations) {
BlockState bs = SongPlayer.MC.world.getBlockState(nbPos);
int blockId = Block.getRawIdFromState(bs);
if (blockId >= SongPlayer.NOTEBLOCK_BASE_ID && blockId < SongPlayer.NOTEBLOCK_BASE_ID+800) {
int noteId = (blockId-SongPlayer.NOTEBLOCK_BASE_ID)/2;
if (missingNotes.contains(noteId)) {
// stage.tunedNoteblocks[noteId] = pos;
missingNotes.remove(noteId);
noteblockPositions.put(noteId, nbPos);
}
else {
unusedNoteblockLocations.add(nbPos);
}
}
else {
unusedNoteblockLocations.add(nbPos);
}
}
// Populate missing noteblocks into the unused noteblock locations
int idx = 0;
for (int noteId : missingNotes) {
if (idx >= unusedNoteblockLocations.size()) {
System.out.println("Too many noteblocks!");
break;
}
noteblockPositions.put(noteId, unusedNoteblockLocations.get(idx++));
}
}
public boolean nothingToBuild() {
return requiredBreaks.isEmpty() && missingNotes.isEmpty();
}
}

View file

@ -0,0 +1,67 @@
package com.github.hhhzzzsss.songplayer.song;
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.CertificateException;
import java.security.cert.X509Certificate;
public class DownloadUtils {
private static class DefaultTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
@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");
BufferedInputStream downloadStream = new BufferedInputStream(conn.getInputStream());
ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
try {
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();
} finally {
// Closing a ByteArrayInputStream has no effect, so I do not close it.
downloadStream.close();
}
}
public static InputStream DownloadToInputStream(URL url, int maxSize) throws KeyManagementException, NoSuchAlgorithmException, IOException {
return new ByteArrayInputStream(DownloadToByteArray(url, maxSize));
}
}

View file

@ -1,30 +0,0 @@
package com.github.hhhzzzsss.songplayer.song;
import java.io.BufferedInputStream;
import java.net.URL;
import java.net.URLConnection;
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,33 @@
package com.github.hhhzzzsss.songplayer.song;
public enum Instrument {
HARP(0, 54),
BASEDRUM(1, 0),
SNARE(2, 0),
HAT(3, 0),
BASS(4, 30),
FLUTE(5, 66),
BELL(6, 78),
GUITAR(7, 42),
CHIME(8, 78),
XYLOPHONE(9, 78),
IRON_XYLOPHONE(10, 54),
COW_BELL(11, 66),
DIDGERIDOO(12, 30),
BIT(13, 54),
BANJO(14, 54),
PLING(15, 54);
public final int instrumentId;
public final int offset;
Instrument(int instrumentId, int offset) {
this.instrumentId = instrumentId;
this.offset = offset;
}
private static Instrument[] values = values();
public static Instrument getInstrumentFromId(int instrumentId) {
return values[instrumentId];
}
}

View file

@ -1,61 +1,37 @@
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.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.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;
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 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 Song getSongFromUrl(URL url) throws IOException, InvalidMidiDataException, URISyntaxException, NoSuchAlgorithmException, KeyManagementException {
Sequence sequence = MidiSystem.getSequence(DownloadUtils.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 TreeMap<Long, ArrayList<Integer>> getMidi(BufferedInputStream downloadStream) throws Exception {
noteMap = new TreeMap<>();
Sequence sequence = MidiSystem.getSequence(downloadStream);
public static Song getSong(Sequence sequence, String name) {
Song song = new Song(name);
long tpq = sequence.getResolution();
@ -73,18 +49,12 @@ public class MidiConverter {
}
}
Collections.sort(tempoEvents, new Comparator<MidiEvent>() {
@Override
public int compare(MidiEvent a, MidiEvent b) {
return (new Long(a.getTick())).compareTo(b.getTick());
}
});
Collections.sort(tempoEvents, (a, b) -> Long.compare(a.getTick(), 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;
@ -112,100 +82,126 @@ public class MidiConverter {
}
else if (sm.getCommand() == NOTE_ON) {
if (sm.getData2() == 0) continue;
int key = sm.getData1();
int pitch = sm.getData1();
long deltaTick = event.getTick() - prevTick;
prevTick = event.getTick();
microTime += (mpq/tpq) * deltaTick;
Note note;
if (sm.getChannel() == 9) {
processMidiNote(128, sm.getData1(), microTime);
note = getMidiPercussionNote(pitch, microTime);
}
else {
processMidiNote(instrumentIds[sm.getChannel()], sm.getData1(), microTime);
note = getMidiInstrumentNote(instrumentIds[sm.getChannel()], pitch, microTime);
}
if (note != null) {
song.add(note);
}
long time = microTime / 1000L;
if (time > song.length) {
song.length = time;
}
}
else {
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();
downloadStream.close();
return noteMap;
return song;
}
public static void processMidiNote(int midiInstrument, int midiPitch, long microTime) {
int minecraftInstrument = -1;
public static Note getMidiInstrumentNote(int midiInstrument, int midiPitch, long microTime) {
Instrument instrument = null;
if ((midiInstrument >= 0 && midiInstrument <= 7) || (midiInstrument >= 24 && midiInstrument <= 31)) { //normal
if (midiPitch >= 54 && midiPitch <= 78) {
minecraftInstrument = 0; //piano
instrument = Instrument.HARP;
}
else if (midiPitch >= 30 && midiPitch <= 54) {
minecraftInstrument = 4; //bass
instrument = Instrument.BASS;
}
else if (midiPitch >= 78 && midiPitch <= 102) {
minecraftInstrument = 6; //bells
instrument = Instrument.BELL;
}
}
else if (midiInstrument >= 8 && midiInstrument <= 15) { //chromatic percussion
if (midiPitch >= 54 && midiPitch <= 78) {
minecraftInstrument = 10; //iron xylophone
instrument = Instrument.IRON_XYLOPHONE;
}
else if (midiPitch >= 78 && midiPitch <= 102) {
minecraftInstrument = 9; //xylophone
instrument = Instrument.XYLOPHONE;
}
else if (midiPitch >= 30 && midiPitch <= 54) {
minecraftInstrument = 4; //bass
instrument = Instrument.BASS;
}
}
else if ((midiInstrument >= 16 && midiInstrument <= 23) || (midiInstrument >= 32 && midiInstrument <= 71) || (midiInstrument >= 80 && midiInstrument <= 111)) { //synth
if (midiPitch >= 54 && midiPitch <= 78) {
minecraftInstrument = 13; //bit
instrument = Instrument.BIT;
}
else if (midiPitch >= 30 && midiPitch <= 54) { //didgeridoo
minecraftInstrument = 12;
else if (midiPitch >= 30 && midiPitch <= 54) {
instrument = Instrument.DIDGERIDOO;
}
else if (midiPitch >= 78 && midiPitch <= 102) { //bells
minecraftInstrument = 6;
else if (midiPitch >= 78 && midiPitch <= 102) {
instrument = Instrument.BELL;
}
}
else if ((midiInstrument >= 72 && midiInstrument <= 79)) { //woodwind
if (midiPitch >= 66 && midiPitch <= 90) {
minecraftInstrument = 5; //flute
instrument = Instrument.FLUTE;
}
else if (midiPitch >= 30 && midiPitch <= 54) { //didgeridoo
minecraftInstrument = 12;
else if (midiPitch >= 30 && midiPitch <= 54) {
instrument = Instrument.DIDGERIDOO;
}
else if (midiPitch >= 54 && midiPitch <= 78) {
minecraftInstrument = 13; //bit
instrument = Instrument.BIT;
}
else if (midiPitch >= 78 && midiPitch <= 102) { //bells
minecraftInstrument = 6;
else if (midiPitch >= 78 && midiPitch <= 102) {
instrument = Instrument.BELL;
}
}
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;
if (instrument == null) {
return null;
}
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);
}
int pitch = midiPitch-instrument.offset;
int noteId = pitch + instrument.instrumentId*25;
long time = microTime / 1000L;
return new Note(noteId, time);
}
private static Note getMidiPercussionNote(int midiPitch, long microTime) {
Instrument instrument = null;
if (midiPitch == 35 || midiPitch == 36 || midiPitch == 41 || midiPitch == 43 || midiPitch == 45 || midiPitch == 57) {
instrument = Instrument.BASEDRUM;
}
else if (midiPitch == 38 || midiPitch == 39 || midiPitch == 40 || midiPitch == 54 || midiPitch == 69 || midiPitch == 70 || midiPitch == 73 || midiPitch == 74 || midiPitch == 78 || midiPitch == 79) {
instrument = Instrument.SNARE;
}
else if (midiPitch == 37 || midiPitch == 42 || midiPitch == 44 || midiPitch == 46 || midiPitch == 49 || midiPitch == 51 || midiPitch == 52 || midiPitch == 55 || midiPitch == 57 || midiPitch == 59) {
instrument = Instrument.HAT;
}
if (instrument == null) {
return null;
}
int pitch = 0;
int noteId = pitch + instrument.instrumentId*25;
long time = microTime / 1000L;
return new Note(noteId, time);
}
}

View file

@ -0,0 +1,178 @@
package com.github.hhhzzzsss.songplayer.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;
}
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);
}
}
Song song = new Song(songName.trim().length() > 0 ? songName : fileName);
if (loop > 0) {
song.looping = true;
song.loopPosition = getMilliTime(loopStartTick, tempo);
}
for (NBSNote note : nbsNotes) {
Instrument instrument;
if (note.instrument < instrumentIndex.length) {
instrument = instrumentIndex[note.instrument];
}
else {
continue;
}
if (note.key < 33 || note.key > 57) {
continue;
}
byte layerVolume = 100;
if (nbsLayers.size() > note.layer) {
layerVolume = nbsLayers.get(note.layer).volume;
}
int pitch = note.key-33;
int noteId = pitch + instrument.instrumentId*25;
song.add(new Note(noteId, 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,23 @@
package com.github.hhhzzzsss.songplayer.song;
public class Note implements Comparable<Note> {
public int noteId;
public long time;
public Note(int note, long time) {
this.noteId = note;
this.time = time;
}
@Override
public int compareTo(Note other) {
if (time < other.time) {
return -1;
}
else if (time > other.time) {
return 1;
}
else {
return 0;
}
}
}

View file

@ -1,10 +0,0 @@
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

@ -1,145 +1,104 @@
package com.github.hhhzzzsss.songplayer.song;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import com.github.hhhzzzsss.songplayer.SongPlayer;
import java.util.Collections;
public class Song {
public ArrayList<NoteEvent> notes = new ArrayList<>();
public ArrayList<Note> notes = new ArrayList<>();
public String name;
public int position = 0;
public int position = 0; // Current note index
public boolean[] requiredNotes = new boolean[400];
public boolean loop = false;
public int gotoTime = -1;
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 loopPosition = 0; // Milliseconds into the song to start looping
public long time = 0; // Time since start of song
private Song() {}
public Song(String name) {
this.name = name;
}
public NoteEvent get(int i) {
public Note get(int i) {
return notes.get(i);
}
public void add(NoteEvent e) {
public void add(Note e) {
notes.add(e);
requiredNotes[e.noteId] = true;
}
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;
advanceTime();
}
}
public void setTime(long t) {
time = t;
startTime = System.currentTimeMillis() - time;
position = 0;
while (reachedNextNote()) {
getNextNote();
}
}
public void advanceTime() {
time = System.currentTimeMillis() - startTime;
}
public boolean reachedNextNote() {
if (position < notes.size()) {
return notes.get(position).time <= this.time;
} else {
if (looping) {
return notes.get(0).time + length <= this.time;
} else {
return false;
}
}
}
public Note getNextNote() {
if (position >= notes.size()) {
if (looping) {
loop();
} else {
return null;
}
}
return notes.get(position++);
}
public boolean finished() {
return time > length;
}
private void loop() {
position = 0;
startTime += length;
time -= length;
}
public int size() {
return notes.size();
}
public static Song getSongFromFile(String file) throws Exception {
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()) {
songPath = new File(SongPlayer.SONG_DIR, file + ".mid");
}
if (!songPath.exists()) {
songPath = new File(SongPlayer.SONG_DIR, file + ".midi");
}
if (!songPath.exists()) throw new FileNotFoundException();
boolean isMidi = false;
String extension = getExtension(songPath);
if (extension.equalsIgnoreCase("mid") || extension.equalsIgnoreCase("midi")) isMidi = true;
if (isMidi) {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(songPath));
song.notes.clear();
TreeMap<Long, ArrayList<Integer>> noteMap = MidiConverter.getMidi(bis);
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 = songPath.getName();
return song;
}
else {
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 {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom());
SSLContext.setDefault(ctx);
URLConnection conn = new URL(url).openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0");
BufferedInputStream downloadStream = new BufferedInputStream(conn.getInputStream());
Song song = new Song();
song.notes.clear();
TreeMap<Long, ArrayList<Integer>> noteMap = MidiConverter.getMidi(downloadStream);
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;
}
private static class DefaultTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
private static String getExtension(File file) {
String name = file.getName();
if(name.lastIndexOf(".") != -1 && name.lastIndexOf(".") != 0)
return name.substring(name.lastIndexOf(".") + 1);
else
return "";
}
}

View file

@ -0,0 +1,87 @@
package com.github.hhhzzzsss.songplayer.song;
import com.github.hhhzzzsss.songplayer.SongPlayer;
import java.io.File;
import java.io.IOException;
import java.net.URL;
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 Exception exception;
public Song song;
private boolean isUrl = false;
public SongLoaderThread(String location) throws IOException {
this.location = location;
if (location.startsWith("http://") || location.startsWith("https://")) {
isUrl = true;
songUrl = new URL(location);
}
else if (location.contains("/") || location.contains("\\")) {
throw new IOException("Invalid characters in song name: " + location);
}
else if (getSongFile(location).exists()) {
songPath = getSongFile(location);
}
else if (getSongFile(location+".mid").exists()) {
songPath = getSongFile(location+".mid");
}
else if (getSongFile(location+".midi").exists()) {
songPath = getSongFile(location+".midi");
}
else if (getSongFile(location+".nbs").exists()) {
songPath = getSongFile(location+".nbs");
}
else {
throw new IOException("Could not find song: " + location);
}
}
public void run() {
try {
byte[] bytes;
String name;
if (isUrl) {
bytes = DownloadUtils.DownloadToByteArray(songUrl, 10*1024*1024);
name = Paths.get(songUrl.toURI().getPath()).getFileName().toString();
}
else {
bytes = Files.readAllBytes(songPath.toPath());
name = songPath.getName();
}
try {
song = MidiConverter.getSongFromBytes(bytes, location);
}
catch (Exception e) {}
if (song == null) {
try {
song = NBSConverter.getSongFromBytes(bytes, name);
}
catch (Exception e) {
e.printStackTrace();
}
}
if (song == null) {
throw new IOException("Invalid song format");
}
}
catch (Exception e) {
exception = e;
}
}
private File getSongFile(String name) {
return new File(SongPlayer.SONG_DIR, name);
}
}

View file

@ -7,7 +7,8 @@
],
"client": [
"ClientPlayerEntityMixin",
"ClientPlayNetworkHandlerMixin"
"ClientPlayNetworkHandlerMixin",
"MinecraftClientMixin"
],
"injectors": {
"defaultRequire": 1