Merge pull request #21 from EnderKill98/feature/bugfixes-and-improvements

Bugfixes and improvements
This commit is contained in:
SemmieDev 2024-04-24 08:58:56 +02:00 committed by GitHub
commit 88800c055f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 205 additions and 6 deletions

View File

@ -9,6 +9,7 @@ import java.util.ArrayList;
@me.shedaniel.autoconfig.annotation.Config.Gui.Background("textures/block/note_block.png")
public class Config implements ConfigData {
public boolean hideWarning;
@ConfigEntry.Gui.Tooltip(count = 2) public boolean disableAsyncPlayback;
@ConfigEntry.Gui.Excluded @ConfigEntry.Gui.Tooltip(count = 2) public boolean monoNoteBlocks;
@ConfigEntry.Gui.Excluded

View File

@ -5,19 +5,35 @@ 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;
import net.minecraft.block.enums.Instrument;
import net.minecraft.client.MinecraftClient;
import net.minecraft.command.CommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.jetbrains.annotations.Nullable;
import semmiedev.disc_jockey.gui.screen.DiscJockeyScreen;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
public class DiscjockeyCommand {
public static void register(CommandDispatcher<FabricClientCommandSource> commandDispatcher) {
final ArrayList<String> instrumentNames = new ArrayList<>();
for (Instrument instrument : Instrument.values()) {
instrumentNames.add(instrument.toString().toLowerCase());
}
final ArrayList<String> instrumentNamesAndAll = new ArrayList<>(instrumentNames);
instrumentNamesAndAll.add("all");
final ArrayList<String> instrumentNamesAndNothing = new ArrayList<>(instrumentNames);
instrumentNamesAndNothing.add("nothing");
commandDispatcher.register(
literal("discjockey")
.executes(context -> {
@ -98,7 +114,130 @@ public class DiscjockeyCommand {
}
})
)
.then(literal("remapInstruments")
.executes(context -> {
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".instrument_info"));
return 0;
})
.then(literal("map")
.then(argument("originalInstrument", StringArgumentType.word())
.suggests((context, builder) -> CommandSource.suggestMatching(instrumentNamesAndAll, builder))
.then(argument("newInstrument", StringArgumentType.word())
.suggests((context, builder) -> CommandSource.suggestMatching(instrumentNamesAndNothing, builder))
.executes(context -> {
String originalInstrumentStr = StringArgumentType.getString(context, "originalInstrument");
String newInstrumentStr = StringArgumentType.getString(context, "newInstrument");
@Nullable Instrument originalInstrument = null, newInstrument = null;
for(Instrument maybeInstrument : Instrument.values()) {
if(maybeInstrument.toString().equalsIgnoreCase(originalInstrumentStr)) {
originalInstrument = maybeInstrument;
}
if(maybeInstrument.toString().equalsIgnoreCase(newInstrumentStr)) {
newInstrument = maybeInstrument;
}
}
if(originalInstrument == null && !originalInstrumentStr.equalsIgnoreCase("all")) {
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".invalid_instrument", originalInstrumentStr));
return 0;
}
if(newInstrument == null && !newInstrumentStr.equalsIgnoreCase("nothing")) {
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".invalid_instrument", newInstrumentStr));
return 0;
}
// (originalInstrument == null) means: all instruments
// (newInstrument == null) means: nothing (represented by null in hashmap, so no special handling below)
if(originalInstrument == null) {
// All instruments
for(Instrument instrument : Instrument.values()) {
Main.SONG_PLAYER.instrumentMap.put(instrument, newInstrument);
}
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".instrument_mapped_all", newInstrumentStr.toLowerCase()));
}else {
Main.SONG_PLAYER.instrumentMap.put(originalInstrument, newInstrument);
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".instrument_mapped", originalInstrumentStr.toLowerCase(), newInstrumentStr.toLowerCase()));
}
return 1;
})
)
)
)
.then(literal("unmap")
.then(argument("instrument", StringArgumentType.word())
.suggests((context, builder) -> CommandSource.suggestMatching(instrumentNames, builder))
.executes(context -> {
String instrumentStr = StringArgumentType.getString(context, "instrument");
Instrument instrument = null;
for(Instrument maybeInstrument : Instrument.values()) {
if(maybeInstrument.toString().equalsIgnoreCase(instrumentStr)) {
instrument = maybeInstrument;
break;
}
}
if(instrument == null) {
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".invalid_instrument", instrumentStr));
return 0;
}
Main.SONG_PLAYER.instrumentMap.remove(instrument);
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".instrument_unmapped", instrumentStr.toLowerCase()));
return 1;
})
)
)
.then(literal("show")
.executes(context -> {
if(Main.SONG_PLAYER.instrumentMap.isEmpty()) {
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".no_mapped_instruments"));
return 1;
}
StringBuilder maps = new StringBuilder();
for(Map.Entry<Instrument, Instrument> entry : Main.SONG_PLAYER.instrumentMap.entrySet()) {
if(maps.length() > 0) {
maps.append(", ");
}
maps
.append(entry.getKey().toString().toLowerCase())
.append("->")
.append(entry.getValue() == null ? "nothing" : entry.getValue().toString().toLowerCase());
}
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".mapped_instruments", maps.toString()));
return 1;
})
)
.then(literal("clear")
.executes(context -> {
Main.SONG_PLAYER.instrumentMap.clear();
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".instrument_maps_cleared"));
return 1;
})
)
)
.then(literal("loop")
.executes(context -> {
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".loop_status", Main.SONG_PLAYER.loopSong ? "yes" : "no"));
return 1;
})
.then(literal("yes")
.executes(context -> {
Main.SONG_PLAYER.loopSong = true;
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".loop_enabled"));
return 1;
}))
.then(literal("no")
.executes(context -> {
Main.SONG_PLAYER.loopSong = false;
context.getSource().sendFeedback(Text.translatable(Main.MOD_ID + ".loop_disabled"));
return 1;
}))
)
);
}

View File

@ -21,6 +21,7 @@ import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.*;
import net.minecraft.world.GameMode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
@ -60,13 +61,19 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
private int tuneInitialUntunedBlocks = -1;
private HashMap<BlockPos, Pair<Integer, Long>> notePredictions = new HashMap<>();
public boolean didSongReachEnd = false;
public boolean loopSong = false;
public SongPlayer() {
Main.TICK_LISTENERS.add(this);
}
public @NotNull HashMap<Instrument, Instrument> instrumentMap = new HashMap<>(); // Toy
public @NotNull HashMap<Instrument, @Nullable Instrument> instrumentMap = new HashMap<>(); // Toy
public synchronized void startPlaybackThread() {
if(Main.config.disableAsyncPlayback) {
playbackThread = null;
return;
}
this.playbackThread = new Thread(() -> {
Thread ownThread = this.playbackThread;
while(ownThread == this.playbackThread) {
@ -150,7 +157,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
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));
client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.invalid_game_mode", gameMode == null ? "unknown" : gameMode.getTranslatableName()).formatted(Formatting.RED));
stop();
return;
}
@ -158,7 +165,12 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
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));
@Nullable BlockPos blockPos = noteBlocks.get(Note.INSTRUMENTS[(byte)(note >> Note.INSTRUMENT_SHIFT)]).get((byte)(note >> Note.NOTE_SHIFT));
if(blockPos == null) {
// Instrument got likely mapped to "nothing". Skip it
index++;
continue;
}
if (!canInteractWith(client.player, blockPos)) {
stop();
client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.to_far").formatted(Formatting.RED));
@ -200,6 +212,9 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
if (index >= song.notes.length) {
stop();
didSongReachEnd = true;
if(loopSong) {
start(song);
}
break;
}
} else {
@ -264,6 +279,13 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
if(!instrumentMap.isEmpty()) {
HashMap<Instrument, ArrayList<BlockPos>> newNoteblocksForInstrument = new HashMap<>();
for(Instrument orig : noteblocksForInstrument.keySet()) {
Instrument mappedInstrument = instrumentMap.getOrDefault(orig, orig);
if(mappedInstrument == null) {
// Instrument got likely mapped to "nothing"
newNoteblocksForInstrument.put(orig, null);
continue;
}
newNoteblocksForInstrument.put(orig, noteblocksForInstrument.getOrDefault(instrumentMap.getOrDefault(orig, orig), new ArrayList<>()));
}
noteblocksForInstrument = newNoteblocksForInstrument;
@ -273,6 +295,12 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
ArrayList<Note> capturedNotes = new ArrayList<>();
for(Note note : song.uniqueNotes) {
ArrayList<BlockPos> availableBlocks = noteblocksForInstrument.get(note.instrument);
if(availableBlocks == null) {
// Note was mapped to "nothing". Pretend it got captured, but just ignore it
capturedNotes.add(note);
getNotes(note.instrument).put(note.note, null);
continue;
}
BlockPos bestBlockPos = null;
int bestBlockTuningSteps = Integer.MAX_VALUE;
for(BlockPos blockPos : availableBlocks) {
@ -301,7 +329,9 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
HashMap<Block, Integer> missing = new HashMap<>();
for (Note note : missingNotes) {
Block block = Note.INSTRUMENT_BLOCKS.get(instrumentMap.getOrDefault(note.instrument, note.instrument));
Instrument mappedInstrument = instrumentMap.getOrDefault(note.instrument, note.instrument);
if(mappedInstrument == null) continue; // Ignore if mapped to nothing
Block block = Note.INSTRUMENT_BLOCKS.get(mappedInstrument);
Integer got = missing.get(block);
if (got == null) got = 0;
missing.put(block, got + 1);
@ -322,7 +352,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
}
if(lastInteractAt != -1L) {
// Paper allows 8 interacts per 300 ms
// Paper allows 8 interacts per 300 ms (actually 9 it turns out, but lets keep it a bit lower anyway)
availableInteracts += ((System.currentTimeMillis() - lastInteractAt) / (310.0 / 8.0));
availableInteracts = Math.min(8f, Math.max(0f, availableInteracts));
}else {
@ -360,7 +390,14 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
if(tuneInitialUntunedBlocks == -1 || tuneInitialUntunedBlocks < untunedNotes.size())
tuneInitialUntunedBlocks = untunedNotes.size();
if(untunedNotes.isEmpty() && fullyTunedBlocks == song.uniqueNotes.size()) {
int existingUniqueNotesCount = 0;
for(Note n : song.uniqueNotes) {
if(noteBlocks.get(n.instrument).get(n.note) != null)
existingUniqueNotesCount++;
}
System.out.println("existingUniqueNotesCount = " + existingUniqueNotesCount);
if(untunedNotes.isEmpty() && fullyTunedBlocks == existingUniqueNotesCount) {
// 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;
@ -417,6 +454,14 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
//client.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.LookAndOnGround(((float) (System.currentTimeMillis() % 2000)) * (360f/2000f), (1 - roughTuneProgress) * 180 - 90, true));
client.player.swingHand(Hand.MAIN_HAND);
}
}else if((playbackThread == null || !playbackThread.isAlive()) && running && Main.config.disableAsyncPlayback) {
// Sync playback (off by default). Replacement for playback thread
try {
tickPlayback();
}catch (Exception ex) {
ex.printStackTrace();
stop();
}
}
}

View File

@ -23,11 +23,25 @@
"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.instrument_info": "This maps instruments to be played by noteblocks for a different instrument instead.",
"disc_jockey.invalid_instrument": "Invalid instrument: %s",
"disc_jockey.instrument_mapped": "Mapped %s to %s",
"disc_jockey.instrument_mapped_all": "Mapped all instruments to %s",
"disc_jockey.instrument_unmapped": "Unmapped %s",
"disc_jockey.mapped_instruments": "Mapped instruments: %s",
"disc_jockey.no_mapped_instruments": "No instruments mapped, yet.",
"disc_jockey.instrument_maps_cleared": "Instrument mappings cleared.",
"disc_jockey.loop_status": "Loop song: %s",
"disc_jockey.loop_enabled": "Enabled looping of current song.",
"disc_jockey.loop_disabled": "Disabled looping of current song.",
"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",
"text.autoconfig.disc_jockey.title": "Disc Jockey",
"text.autoconfig.disc_jockey.option.hideWarning": "Hide Warning",
"text.autoconfig.disc_jockey.option.disableAsyncPlayback": "Disable Async Playback",
"text.autoconfig.disc_jockey.option.disableAsyncPlayback.@Tooltip[0]": "Will force notes to play synchronously with client ticks instead of in a separate thread.",
"text.autoconfig.disc_jockey.option.disableAsyncPlayback.@Tooltip[1]": "This can lead to performance loss, especially when you client has low or inconsistent fps but can fix issues when playback does not happen at all.",
"text.autoconfig.disc_jockey.option.monoNoteBlocks": "Non-Directional Note Block Sounds",
"text.autoconfig.disc_jockey.option.monoNoteBlocks.@Tooltip[0]": "Makes all note block sounds when playing a song non-directional, creating a more pleasurable listening experience (clientside)",
"text.autoconfig.disc_jockey.option.monoNoteBlocks.@Tooltip[1]": "If you don't know what that means, I recommend you just try it and hear the difference"