Merge pull request #9 from EnderKill98/feature/make-mergable

Add numerous new features to Disc Jockey
This commit is contained in:
SemmieDev 2023-06-17 16:14:27 +02:00 committed by GitHub
commit 1469132322
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 400 additions and 88 deletions

View File

@ -1,6 +1,7 @@
package semmiedev.disc_jockey;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
@ -9,6 +10,7 @@ import net.minecraft.command.CommandSource;
import net.minecraft.text.Text;
import semmiedev.disc_jockey.gui.screen.DiscJockeyScreen;
import java.util.Arrays;
import java.util.Optional;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
@ -66,15 +68,57 @@ public class DiscjockeyCommand {
return 0;
})
)
.then(literal("speed")
.then(argument("speed", FloatArgumentType.floatArg(0.0001F, 15.0F))
.suggests((context, builder) -> CommandSource.suggestMatching(Arrays.asList("0.5", "0.75", "1", "1.25", "1.5", "2"), builder))
.executes(context -> {
float newSpeed = FloatArgumentType.getFloat(context, "speed");
Main.SONG_PLAYER.speed = newSpeed;
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".speed_changed", Main.SONG_PLAYER.speed));
return 0;
})
)
)
.then(literal("info")
.executes(context -> {
if (!Main.SONG_PLAYER.running) {
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".info_not_running", Main.SONG_PLAYER.speed));
return 0;
}
if (!Main.SONG_PLAYER.tuned) {
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".info_tuning", Main.SONG_PLAYER.song.displayName, Main.SONG_PLAYER.speed));
return 0;
}else if(!Main.SONG_PLAYER.didSongReachEnd) {
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".info_playing", formatTimestamp((int) Main.SONG_PLAYER.getSongElapsedSeconds()), formatTimestamp((int) Main.SONG_PLAYER.song.getLengthInSeconds()), Main.SONG_PLAYER.song.displayName, Main.SONG_PLAYER.speed));
return 0;
}else {
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".info_finished", Main.SONG_PLAYER.song != null ? Main.SONG_PLAYER.song.displayName : "???", Main.SONG_PLAYER.speed));
return 0;
}
})
)
);
}
private static boolean isLoading(CommandContext<FabricClientCommandSource> context) {
if (SongLoader.loadingSongs) {
context.getSource().sendError(Text.translatable(Main.MOD_ID+".still_loading"));
context.getSource().sendError(Text.translatable(Main.MOD_ID + ".still_loading"));
SongLoader.showToast = true;
return true;
}
return false;
}
private static String padZeroes(int number, int length) {
StringBuilder builder = new StringBuilder("" + number);
while(builder.length() < length)
builder.insert(0, '0');
return builder.toString();
}
private static String formatTimestamp(int seconds) {
return padZeroes(seconds / 60, 2) + ":" + padZeroes(seconds % 60, 2);
}
}

View File

@ -22,4 +22,22 @@ public class Song {
public String toString() {
return displayName;
}
public double millisecondsToTicks(long milliseconds) {
// From NBS Format: The tempo of the song multiplied by 100 (for example, 1225 instead of 12.25). Measured in ticks per second.
double songSpeed = (tempo / 100.0) / 20.0; // 20 Ticks per second (temp / 100 = 20) would be 1x speed
double oneMsTo20TickFraction = 1.0 / 50.0;
return milliseconds * oneMsTo20TickFraction * songSpeed;
}
public double ticksToMilliseconds(double ticks) {
double songSpeed = (tempo / 100.0) / 20.0;
double oneMsTo20TickFraction = 1.0 / 50.0;
return ticks / oneMsTo20TickFraction / songSpeed;
}
public double getLengthInSeconds() {
return ticksToMilliseconds(length) / 1000.0;
}
}

View File

@ -29,8 +29,8 @@ public class SongLoader {
Song song = null;
try {
song = loadSong(file);
} catch (IOException exception) {
Main.LOGGER.error("Unable to read song "+file.getName(), exception);
} catch (Exception exception) {
Main.LOGGER.error("Unable to read or parse song " + file.getName(), exception);
}
if (song != null) SONGS.add(song);
}

View File

@ -8,33 +8,85 @@ import net.minecraft.block.enums.Instrument;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.hud.ChatHud;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import net.minecraft.state.property.Properties;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Hand;
import net.minecraft.util.Pair;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.*;
import net.minecraft.world.GameMode;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class SongPlayer implements ClientTickEvents.StartWorldTick {
private static final Box BOX = new Box(0, 0, 0, 1, 1, 1);
private static boolean warned;
public boolean running;
public Song song;
private int index;
private float tick;
private double tick; // Aka song position
private HashMap<Instrument, HashMap<Byte, BlockPos>> noteBlocks = null;
private boolean tuned;
private int tuneDelay = 5;
public boolean tuned;
private long lastPlaybackTickAt = -1L;
public void start(Song song) {
// Used to check and enforce packet rate limits to not get kicked
private long last100MsSpanAt = -1L;
private int last100MsSpanEstimatedPackets = 0;
// At how many packets/100ms should the player just reduce / stop sending packets for a while
final private int last100MsReducePacketsAfter = 300 / 10, last100MsStopPacketsAfter = 450 / 10;
// If higher than current millis, don't send any packets of this kind (temp disable)
private long reducePacketsUntil = -1L, stopPacketsUntil = -1L;
// Use to limit swings and look to only each tick. More will not be visually visible anyway due to interpolation
private long lastLookSentAt = -1L, lastSwingSentAt = -1L;
// The thread executing the tickPlayback method
private Thread playbackThread = null;
public long playbackLoopDelay = 5;
// Just for external debugging purposes
public HashMap<Block, Integer> missingInstrumentBlocks = new HashMap<>();
public float speed = 1.0f; // Toy
private long lastInteractAt = -1;
private float availableInteracts = 8;
private int tuneInitialUntunedBlocks = -1;
private HashMap<BlockPos, Pair<Integer, Long>> notePredictions = new HashMap<>();
public boolean didSongReachEnd = false;
public SongPlayer() {
Main.TICK_LISTENERS.add(this);
}
public @NotNull HashMap<Instrument, Instrument> instrumentMap = new HashMap<>(); // Toy
public synchronized void startPlaybackThread() {
this.playbackThread = new Thread(() -> {
Thread ownThread = this.playbackThread;
while(ownThread == this.playbackThread) {
try {
// Accuracy doesn't really matter at this precision imo
Thread.sleep(playbackLoopDelay);
}catch (Exception ex) {
ex.printStackTrace();
}
tickPlayback();
}
});
this.playbackThread.start();
}
public synchronized void stopPlaybackThread() {
this.playbackThread = null; // Should stop on its own then
}
public synchronized void start(Song song) {
if (!Main.config.hideWarning && !warned) {
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.translatable("disc_jockey.warning").formatted(Formatting.BOLD, Formatting.RED));
warned = true;
@ -42,52 +94,205 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
}
if (running) stop();
this.song = song;
Main.TICK_LISTENERS.add(this);
//Main.LOGGER.info("Song length: " + song.length + " and tempo " + song.tempo);
//Main.TICK_LISTENERS.add(this);
if(this.playbackThread == null) startPlaybackThread();
running = true;
lastPlaybackTickAt = System.currentTimeMillis();
last100MsSpanAt = System.currentTimeMillis();
last100MsSpanEstimatedPackets = 0;
reducePacketsUntil = -1L;
stopPacketsUntil = -1L;
lastLookSentAt = -1L;
lastSwingSentAt = -1L;
missingInstrumentBlocks.clear();
didSongReachEnd = false;
}
public void stop() {
MinecraftClient.getInstance().send(() -> Main.TICK_LISTENERS.remove(this));
public synchronized void stop() {
//MinecraftClient.getInstance().send(() -> Main.TICK_LISTENERS.remove(this));
stopPlaybackThread();
running = false;
index = 0;
tick = 0;
noteBlocks = null;
notePredictions.clear();
tuned = false;
tuneInitialUntunedBlocks = -1;
lastPlaybackTickAt = -1L;
last100MsSpanAt = -1L;
last100MsSpanEstimatedPackets = 0;
reducePacketsUntil = -1L;
stopPacketsUntil = -1L;
lastLookSentAt = -1L;
lastSwingSentAt = -1L;
didSongReachEnd = false; // Change after running stop() if actually ended cleanly
}
public synchronized void tickPlayback() {
if (!running) {
lastPlaybackTickAt = -1L;
last100MsSpanAt = -1L;
return;
}
long previousPlaybackTickAt = lastPlaybackTickAt;
lastPlaybackTickAt = System.currentTimeMillis();
if(last100MsSpanAt != -1L && System.currentTimeMillis() - last100MsSpanAt >= 100) {
last100MsSpanEstimatedPackets = 0;
last100MsSpanAt = System.currentTimeMillis();
}else if (last100MsSpanAt == -1L) {
last100MsSpanAt = System.currentTimeMillis();
last100MsSpanEstimatedPackets = 0;
}
if(noteBlocks != null && tuned) {
while (running) {
MinecraftClient client = MinecraftClient.getInstance();
GameMode gameMode = client.interactionManager == null ? null : client.interactionManager.getCurrentGameMode();
// In the best case, gameMode would only be queried in sync Ticks, no here
if (gameMode == null || !gameMode.isSurvivalLike()) {
client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.invalid_game_mode", gameMode.getTranslatableName()).formatted(Formatting.RED));
stop();
return;
}
long note = song.notes[index];
final long now = System.currentTimeMillis();
if ((short)note <= Math.round(tick)) {
BlockPos blockPos = noteBlocks.get(Note.INSTRUMENTS[(byte)(note >> Note.INSTRUMENT_SHIFT)]).get((byte)(note >> Note.NOTE_SHIFT));
if (!canInteractWith(client.player, blockPos)) {
stop();
client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.to_far").formatted(Formatting.RED));
return;
}
Vec3d unit = Vec3d.ofCenter(blockPos, 0.5).subtract(client.player.getEyePos()).normalize();
if((lastLookSentAt == -1L || now - lastLookSentAt >= 50) && last100MsSpanEstimatedPackets < last100MsReducePacketsAfter && (reducePacketsUntil == -1L || reducePacketsUntil < now)) {
client.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.LookAndOnGround(MathHelper.wrapDegrees((float) (MathHelper.atan2(unit.z, unit.x) * 57.2957763671875) - 90.0f), MathHelper.wrapDegrees((float) (-(MathHelper.atan2(unit.y, Math.sqrt(unit.x * unit.x + unit.z * unit.z)) * 57.2957763671875))), true));
last100MsSpanEstimatedPackets++;
lastLookSentAt = now;
}else if(last100MsSpanEstimatedPackets >= last100MsReducePacketsAfter){
reducePacketsUntil = Math.max(reducePacketsUntil, now + 500);
}
if(last100MsSpanEstimatedPackets < last100MsStopPacketsAfter && (stopPacketsUntil == -1L || stopPacketsUntil < now)) {
// TODO: 5/30/2022 Check if the block needs tuning
//client.interactionManager.attackBlock(blockPos, Direction.UP);
client.player.networkHandler.sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.START_DESTROY_BLOCK, blockPos, Direction.UP, 0));
last100MsSpanEstimatedPackets++;
}else if(last100MsSpanEstimatedPackets >= last100MsStopPacketsAfter) {
Main.LOGGER.info("Stopping all packets for a bit!");
stopPacketsUntil = Math.max(stopPacketsUntil, now + 250);
reducePacketsUntil = Math.max(reducePacketsUntil, now + 10000);
}
if(last100MsSpanEstimatedPackets < last100MsReducePacketsAfter && (reducePacketsUntil == -1L || reducePacketsUntil < now)) {
client.player.networkHandler.sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.ABORT_DESTROY_BLOCK, blockPos, Direction.UP, 0));
last100MsSpanEstimatedPackets++;
}else if(last100MsSpanEstimatedPackets >= last100MsReducePacketsAfter){
reducePacketsUntil = Math.max(reducePacketsUntil, now + 500);
}
if((lastSwingSentAt == -1L || now - lastSwingSentAt >= 50) &&last100MsSpanEstimatedPackets < last100MsReducePacketsAfter && (reducePacketsUntil == -1L || reducePacketsUntil < now)) {
client.executeSync(() -> client.player.swingHand(Hand.MAIN_HAND));
lastSwingSentAt = now;
last100MsSpanEstimatedPackets++;
}else if(last100MsSpanEstimatedPackets >= last100MsReducePacketsAfter){
reducePacketsUntil = Math.max(reducePacketsUntil, now + 500);
}
index++;
if (index >= song.notes.length) {
stop();
didSongReachEnd = true;
break;
}
} else {
break;
}
}
if(running) { // Might not be running anymore (prevent small offset on song, even if that is not played anymore)
long elapsedMs = previousPlaybackTickAt != -1L && lastPlaybackTickAt != -1L ? lastPlaybackTickAt - previousPlaybackTickAt : (16); // Assume 16ms if unknown
tick += song.millisecondsToTicks(elapsedMs) * speed;
}
}
}
// TODO: 6/2/2022 Play note blocks every song tick, instead of every tick. That way the song will sound better
// 11/1/2023 Playback now done in separate thread. Not ideal but better especially when FPS are low.
@Override
public void onStartTick(ClientWorld world) {
if (!running) return;
MinecraftClient client = MinecraftClient.getInstance();
if(world == null || client.world == null || client.player == null) return;
if(song == null || !running) return;
// Clear outdated note predictions
ArrayList<BlockPos> outdatedPredictions = new ArrayList<>();
for(Map.Entry<BlockPos, Pair<Integer, Long>> entry : notePredictions.entrySet()) {
if(entry.getValue().getRight() < System.currentTimeMillis())
outdatedPredictions.add(entry.getKey());
}
for(BlockPos outdatedPrediction : outdatedPredictions) notePredictions.remove(outdatedPrediction);
if (noteBlocks == null) {
noteBlocks = new HashMap<>();
ClientPlayerEntity player = MinecraftClient.getInstance().player;
ClientPlayerEntity player = client.player;
ArrayList<Note> capturedNotes = new ArrayList<>();
Vec3d playerPos = player.getEyePos();
for (int x = -7; x <= 7; x++) {
for (int y = -7; y <= 7; y++) {
for (int z = -7; z <= 7; z++) {
Vec3d vec3d = playerPos.add(x, y, z);
BlockPos blockPos = new BlockPos(MathHelper.floor(vec3d.x), MathHelper.floor(vec3d.y), MathHelper.floor(vec3d.z));
if (intersect(playerPos, MinecraftClient.getInstance().interactionManager.getReachDistance(), BOX.offset(blockPos))) {
// Create list of available noteblock positions per used instrument
HashMap<Instrument, ArrayList<BlockPos>> noteblocksForInstrument = new HashMap<>();
for(Instrument instrument : Instrument.values())
noteblocksForInstrument.put(instrument, new ArrayList<>());
final Vec3d playerPos = player.getEyePos();
final int[] orderedOffsets = new int[] { 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, -6, 6, -7, 7 };
for(Instrument instrument : noteblocksForInstrument.keySet().toArray(new Instrument[0])) {
for (int y : orderedOffsets) {
for (int x : orderedOffsets) {
for (int z : orderedOffsets) {
Vec3d vec3d = playerPos.add(x, y, z);
BlockPos blockPos = new BlockPos(MathHelper.floor(vec3d.x), MathHelper.floor(vec3d.y), MathHelper.floor(vec3d.z));
if (!canInteractWith(player, blockPos))
continue;
BlockState blockState = world.getBlockState(blockPos);
if (blockState.isOf(Blocks.NOTE_BLOCK) && world.isAir(blockPos.up())) {
for (Note note : song.uniqueNotes) {
if (!capturedNotes.contains(note) && blockState.get(Properties.INSTRUMENT) == note.instrument) {
getNotes(note.instrument).put(note.note, blockPos);
capturedNotes.add(note);
break;
}
}
}
if (!blockState.isOf(Blocks.NOTE_BLOCK) || !world.isAir(blockPos.up()))
continue;
if (blockState.get(Properties.INSTRUMENT) == instrument)
noteblocksForInstrument.get(instrument).add(blockPos);
}
}
}
}
// Remap instruments for funzies
if(!instrumentMap.isEmpty()) {
HashMap<Instrument, ArrayList<BlockPos>> newNoteblocksForInstrument = new HashMap<>();
for(Instrument orig : noteblocksForInstrument.keySet()) {
newNoteblocksForInstrument.put(orig, noteblocksForInstrument.getOrDefault(instrumentMap.getOrDefault(orig, orig), new ArrayList<>()));
}
noteblocksForInstrument = newNoteblocksForInstrument;
}
// Find fitting noteblocks with the least amount of adjustments required (to reduce tuning time)
ArrayList<Note> capturedNotes = new ArrayList<>();
for(Note note : song.uniqueNotes) {
ArrayList<BlockPos> availableBlocks = noteblocksForInstrument.get(note.instrument);
BlockPos bestBlockPos = null;
int bestBlockTuningSteps = Integer.MAX_VALUE;
for(BlockPos blockPos : availableBlocks) {
int wantedNote = note.note;
int currentNote = client.world.getBlockState(blockPos).get(Properties.NOTE);
int tuningSteps = wantedNote >= currentNote ? wantedNote - currentNote : (25 - currentNote) + wantedNote;
if(tuningSteps < bestBlockTuningSteps) {
bestBlockPos = blockPos;
bestBlockTuningSteps = tuningSteps;
}
}
if(bestBlockPos != null) {
capturedNotes.add(note);
availableBlocks.remove(bestBlockPos);
getNotes(note.instrument).put(note.note, bestBlockPos);
} // else will be a missing note
}
ArrayList<Note> missingNotes = new ArrayList<>(song.uniqueNotes);
missingNotes.removeAll(capturedNotes);
if (!missingNotes.isEmpty()) {
@ -96,96 +301,136 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
HashMap<Block, Integer> missing = new HashMap<>();
for (Note note : missingNotes) {
Block block = Note.INSTRUMENT_BLOCKS.get(note.instrument);
Block block = Note.INSTRUMENT_BLOCKS.get(instrumentMap.getOrDefault(note.instrument, note.instrument));
Integer got = missing.get(block);
if (got == null) got = 0;
missing.put(block, got + 1);
}
missingInstrumentBlocks = missing;
missing.forEach((block, integer) -> chatHud.addMessage(Text.literal(block.getName().getString()+" × "+integer).formatted(Formatting.RED)));
stop();
}
} else if (!tuned) {
if (tuneDelay > 0) {
tuneDelay--;
return;
//tuned = true;
int ping = 0;
{
PlayerListEntry playerListEntry;
if (client.getNetworkHandler() != null && (playerListEntry = client.getNetworkHandler().getPlayerListEntry(client.player.getGameProfile().getId())) != null)
ping = playerListEntry.getLatency();
}
tuned = true;
MinecraftClient client = MinecraftClient.getInstance();
int tuneAmount = 0;
if(lastInteractAt != -1L) {
// Paper allows 8 interacts per 300 ms
availableInteracts += ((System.currentTimeMillis() - lastInteractAt) / (310.0 / 8.0));
availableInteracts = Math.min(8f, Math.max(0f, availableInteracts));
}else {
availableInteracts = 8f;
lastInteractAt = System.currentTimeMillis();
}
int fullyTunedBlocks = 0;
HashMap<BlockPos, Integer> untunedNotes = new HashMap<>();
for (Note note : song.uniqueNotes) {
if(noteBlocks == null || noteBlocks.get(note.instrument) == null)
continue;
BlockPos blockPos = noteBlocks.get(note.instrument).get(note.note);
if(blockPos == null) continue;
BlockState blockState = world.getBlockState(blockPos);
int assumedNote = notePredictions.containsKey(blockPos) ? notePredictions.get(blockPos).getLeft() : blockState.get(Properties.NOTE);
if (blockState.contains(Properties.NOTE)) {
if (blockState.get(Properties.NOTE) != note.note) {
if (!intersect(client.player.getEyePos(), client.interactionManager.getReachDistance(), BOX.offset(blockPos))) {
if(assumedNote == note.note && blockState.get(Properties.NOTE) == note.note)
fullyTunedBlocks++;
if (assumedNote != note.note) {
if (!canInteractWith(client.player, blockPos)) {
stop();
client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.to_far").formatted(Formatting.RED));
return;
}
Vec3d unit = Vec3d.ofCenter(blockPos, 0.5).subtract(client.player.getEyePos()).normalize();
client.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.LookAndOnGround(MathHelper.wrapDegrees((float)(MathHelper.atan2(unit.z, unit.x) * 57.2957763671875) - 90.0f), MathHelper.wrapDegrees((float)(-(MathHelper.atan2(unit.y, Math.sqrt(unit.x * unit.x + unit.z * unit.z)) * 57.2957763671875))), true));
client.interactionManager.interactBlock(client.player, Hand.MAIN_HAND, new BlockHitResult(Vec3d.of(blockPos), Direction.UP, blockPos, false));
client.player.swingHand(Hand.MAIN_HAND);
tuned = false;
tuneDelay = 5;
if (++tuneAmount == 6) break;
untunedNotes.put(blockPos, blockState.get(Properties.NOTE));
}
} else {
noteBlocks = null;
break;
}
}
} else {
while (running) {
MinecraftClient client = MinecraftClient.getInstance();
GameMode gameMode = client.interactionManager.getCurrentGameMode();
if (!gameMode.isSurvivalLike()) {
client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.invalid_game_mode", gameMode.getTranslatableName()).formatted(Formatting.RED));
stop();
return;
}
long note = song.notes[index];
if ((short)note == Math.round(tick)) {
BlockPos blockPos = noteBlocks.get(Note.INSTRUMENTS[(byte)(note >> Note.INSTRUMENT_SHIFT)]).get((byte)(note >> Note.NOTE_SHIFT));
if (!intersect(client.player.getEyePos(), client.interactionManager.getReachDistance(), BOX.offset(blockPos))) {
stop();
client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.to_far").formatted(Formatting.RED));
return;
}
Vec3d unit = Vec3d.ofCenter(blockPos, 0.5).subtract(client.player.getEyePos()).normalize();
client.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.LookAndOnGround(MathHelper.wrapDegrees((float)(MathHelper.atan2(unit.z, unit.x) * 57.2957763671875) - 90.0f), MathHelper.wrapDegrees((float)(-(MathHelper.atan2(unit.y, Math.sqrt(unit.x * unit.x + unit.z * unit.z)) * 57.2957763671875))), true));
// TODO: 5/30/2022 Check if the block needs tuning
client.interactionManager.attackBlock(blockPos, Direction.UP);
client.player.swingHand(Hand.MAIN_HAND);
if(tuneInitialUntunedBlocks == -1 || tuneInitialUntunedBlocks < untunedNotes.size())
tuneInitialUntunedBlocks = untunedNotes.size();
index++;
if (index >= song.notes.length) {
stop();
break;
}
} else {
break;
if(untunedNotes.isEmpty() && fullyTunedBlocks == song.uniqueNotes.size()) {
// Wait roundrip + 100ms before considering tuned after changing notes (in case the server rejects an interact)
if(lastInteractAt == -1 || System.currentTimeMillis() - lastInteractAt >= ping * 2 + 100) {
tuned = true;
tuneInitialUntunedBlocks = -1;
}
}
tick += song.tempo / 100f / 20f;
BlockPos lastBlockPos = null;
int lastTunedNote = Integer.MIN_VALUE;
float roughTuneProgress = 1 - (untunedNotes.size() / Math.max(tuneInitialUntunedBlocks + 0f, 1f));
while(availableInteracts >= 1f && untunedNotes.size() > 0) {
BlockPos blockPos = null;
int searches = 0;
while(blockPos == null) {
searches++;
// Find higher note
for (Map.Entry<BlockPos, Integer> entry : untunedNotes.entrySet()) {
if (entry.getValue() > lastTunedNote) {
blockPos = entry.getKey();
break;
}
}
// Find higher note or equal
if (blockPos == null) {
for (Map.Entry<BlockPos, Integer> entry : untunedNotes.entrySet()) {
if (entry.getValue() >= lastTunedNote) {
blockPos = entry.getKey();
break;
}
}
}
// Not found. Reset last note
if(blockPos == null)
lastTunedNote = Integer.MIN_VALUE;
if(blockPos == null && searches > 1) {
// Something went wrong. Take any note (one should at least exist here)
blockPos = untunedNotes.keySet().toArray(new BlockPos[0])[0];
break;
}
}
if(blockPos == null) return; // Something went very, very wrong!
lastTunedNote = untunedNotes.get(blockPos);
untunedNotes.remove(blockPos);
int assumedNote = notePredictions.containsKey(blockPos) ? notePredictions.get(blockPos).getLeft() : client.world.getBlockState(blockPos).get(Properties.NOTE);
notePredictions.put(blockPos, new Pair((assumedNote + 1) % 25, System.currentTimeMillis() + ping * 2 + 100));
client.interactionManager.interactBlock(client.player, Hand.MAIN_HAND, new BlockHitResult(Vec3d.of(blockPos), Direction.UP, blockPos, false));
lastInteractAt = System.currentTimeMillis();
availableInteracts -= 1f;
lastBlockPos = blockPos;
}
if(lastBlockPos != null) {
// Turn head into spinning with time and lookup up further the further tuning is progressed
//client.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.LookAndOnGround(((float) (System.currentTimeMillis() % 2000)) * (360f/2000f), (1 - roughTuneProgress) * 180 - 90, true));
client.player.swingHand(Hand.MAIN_HAND);
}
}
}
private boolean intersect(Vec3d pos, double radius, Box box) {
double x = Math.max(box.minX, Math.min(pos.x, box.maxX));
double y = Math.max(box.minY, Math.min(pos.y, box.maxY));
double z = Math.max(box.minZ, Math.min(pos.z, box.maxZ));
double distance = (x - pos.x) * (x - pos.x) + (y - pos.y) * (y - pos.y) + (z - pos.z) * (z - pos.z);
return distance < radius * radius;
}
private HashMap<Byte, BlockPos> getNotes(Instrument instrument) {
return noteBlocks.computeIfAbsent(instrument, k -> new HashMap<>());
}
// The server limits interacts to 6 Blocks from Player Eye to Block Center
private boolean canInteractWith(ClientPlayerEntity player, BlockPos blockPos) {
return player.getEyePos().squaredDistanceTo(new Vec3d(blockPos.getX() + 0.5, blockPos.getY() + 0.5, blockPos.getZ() + 0.5)) <= 6.0*6.0;
}
public double getSongElapsedSeconds() {
if(song == null) return 0;
return song.ticksToMilliseconds(tick) / 1000;
}
}

View File

@ -17,7 +17,12 @@
"disc_jockey.loading_done": "All songs are loaded",
"disc_jockey.song_not_found": " Song '%s' does not exist",
"disc_jockey.not_playing": "Not playing any song",
"disc_jockey.speed_changed": "Changed playback speed to %s",
"disc_jockey.stopped_playing": "Stopped playing '%s'",
"disc_jockey.info_not_running": "No song is playing (Speed: %s)",
"disc_jockey.info_tuning": "Tuning: (Speed: %s)",
"disc_jockey.info_playing": "Playing: [%s/%s] %s (Speed: %s)",
"disc_jockey.info_finished": "Finished: %s (Speed: %s)",
"disc_jockey.warning": "WARNING!!! This mod is very likely to get false flagged as hacks, please contact a server administrator before using this mod! (You can disable this warning in the mod settings)",
"key.category.disc_jockey": "Disc Jockey",
"disc_jockey.key_bind.open_screen": "Open song selection screen",