Update Song Player with a lot of new features
**Async and, client tps and lag independent playback** Playback should work in any conditions. No matter how fast/slow or laggy or inconsistent the client is. The client itself may not hear it in the correct timing, but other players will. The way song ticks are progressed was overhauled and simplified as well. This enables changing the speed of the song or seeking it. **Overhauled tuning** Tuning got overhauled and is now much quicker. It also will try to tune the least amount possible, which makes tuning when changing songs a lot quicker. It assumes changes going through and should work on any ping. Though initial wrong ping information combined with very bad or fluctuating ping can cause the tuning to overshoot and never finish (should be very unlikely and resolve itself after some time). **Added a few variables to control playback better** Variables like didSongReachEnd, missingInstrumentBlocks and some others are used to have more fine grained control from other mods (for implementing a playback queue or getting more info about tuning issues). I'd like them preserved but can understand if they do not make much sense. speed is also not changed in the mod self right now, but i think there could be some UI element added to have fun with it. Same for instrumentMap. **Tick listener is always enabled** I stumbled upon some crashes when it was removing / adding it, so I made the tick listener permanent. It should not have any significant performance implications anyway. **Added packet rate limit** When playing back, the own packets are being kept track of. If they get very high, the client will stop sending "cosmetic" packets and if they get close to the packet rate limit of servers, it'll interrupt playback for brief periods to prevent getting packet kicked. This was added to not get kicked for playing "Rush E" or setting very high "speed"s. **Reduce amount of swining** Does not swing arm for every note played but roughly every serverside tick. This reduces wasted packets no one is going to see anyway. **Use server side distance check** Use the same check the server uses to determine if a noteblock can be reached or not. Increases range to the max allowed. **Other changes** I probably forgot some other changes here as well. Many were added a while back and there were probably numerous other fixes for stability’s sake. So excuse me for not recounting every single one.
This commit is contained in:
parent
5fbb54de7f
commit
ad8f94ee27
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user