Merge pull request #9 from EnderKill98/feature/make-mergable
Add numerous new features to Disc Jockey
This commit is contained in:
commit
1469132322
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user