diff --git a/src/main/java/com/github/hhhzzzsss/songplayer/CommandProcessor.java b/src/main/java/com/github/hhhzzzsss/songplayer/CommandProcessor.java index 71b8f9d..cc155ba 100644 --- a/src/main/java/com/github/hhhzzzsss/songplayer/CommandProcessor.java +++ b/src/main/java/com/github/hhhzzzsss/songplayer/CommandProcessor.java @@ -9,17 +9,14 @@ import com.github.hhhzzzsss.songplayer.song.Playlist; import com.github.hhhzzzsss.songplayer.song.Song; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; -import net.minecraft.block.BlockState; import net.minecraft.command.CommandSource; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.NbtComponent; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; -import net.minecraft.state.property.Property; import net.minecraft.text.*; import net.minecraft.util.Formatting; import net.minecraft.util.Hand; -import net.minecraft.util.hit.BlockHitResult; import net.minecraft.world.GameMode; import java.io.IOException; @@ -61,6 +58,7 @@ public class CommandProcessor { commands.add(new cleanupLastStageCommand()); commands.add(new announcementCommand()); commands.add(new songItemCommand()); + commands.add(new toggleSurvivalOnly()); commands.add(new testSongCommand()); for (Command command : commands) { @@ -1003,7 +1001,6 @@ public class CommandProcessor { return true; } if (SongPlayer.MC.player.getPos().squaredDistanceTo(lastStage.getOriginBottomCenter()) > 3*3) { - System.out.println(SongPlayer.MC.player.getPos().squaredDistanceTo(lastStage.getOriginBottomCenter())); String coordStr = String.format( "%d %d %d", lastStage.position.getX(), lastStage.position.getY(), lastStage.position.getZ() @@ -1165,6 +1162,37 @@ public class CommandProcessor { } } + private static class toggleSurvivalOnly extends Command { + public String getName() { + return "toggleSurvivalOnly"; + } + public String[] getAliases() { + return new String[]{"survivalOnly"}; + } + public String[] getSyntax() { + return new String[0]; + } + public String getDescription() { + return "Enables or disables survival-only mode, in which automatic noteblock placement is disabled and automatic tuning is done by right-clicking.."; + } + public boolean processCommand(String args) { + if (args.length() == 0) { + Config.getConfig().survivalOnly = !Config.getConfig().survivalOnly; + if (Config.getConfig().survivalOnly) { + SongPlayer.addChatMessage("§6Enabled survival only mode"); + } + else { + SongPlayer.addChatMessage("§6Disabled survival only mode"); + } + Config.saveConfigWithErrorHandling(); + return true; + } + else { + return false; + } + } + } + private static class testSongCommand extends Command { public String getName() { return "testSong"; diff --git a/src/main/java/com/github/hhhzzzsss/songplayer/Config.java b/src/main/java/com/github/hhhzzzsss/songplayer/Config.java index a71555b..9b15391 100644 --- a/src/main/java/com/github/hhhzzzsss/songplayer/Config.java +++ b/src/main/java/com/github/hhhzzzsss/songplayer/Config.java @@ -27,6 +27,7 @@ public class Config { public boolean doAnnouncement = false; public String announcementMessage = "&6Now playing: &3[name]"; public boolean autoCleanup = false; + public boolean survivalOnly = false; public static Config getConfig() { if (config == null) { diff --git a/src/main/java/com/github/hhhzzzsss/songplayer/playing/SongHandler.java b/src/main/java/com/github/hhhzzzsss/songplayer/playing/SongHandler.java index dc4c5cd..e0e17a6 100644 --- a/src/main/java/com/github/hhhzzzsss/songplayer/playing/SongHandler.java +++ b/src/main/java/com/github/hhhzzzsss/songplayer/playing/SongHandler.java @@ -150,7 +150,7 @@ public class SongHandler { // Otherwise, handle cleanup if necessary else { if (dirty) { - if (Config.getConfig().autoCleanup && originalBlocks.size() != 0) { + if (Config.getConfig().autoCleanup && originalBlocks.size() != 0 && !Config.getConfig().survivalOnly) { partialResetAndCleanup(); } else { restoreStateAndReset(); @@ -198,12 +198,12 @@ public class SongHandler { dirty = true; currentSong = song; building = true; - setCreativeIfNeeded(); + if (!Config.getConfig().survivalOnly) setCreativeIfNeeded(); if (Config.getConfig().doAnnouncement) { sendMessage(Config.getConfig().announcementMessage.replaceAll("\\[name\\]", song.name)); } prepareStage(); - getAndSaveBuildSlot(); + if (!Config.getConfig().survivalOnly) getAndSaveBuildSlot(); SongPlayer.addChatMessage("§6Building noteblocks"); } @@ -259,52 +259,73 @@ public class SongHandler { return; } ClientWorld world = SongPlayer.MC.world; - if (SongPlayer.MC.interactionManager.getCurrentGameMode() != GameMode.CREATIVE) { + if (!Config.getConfig().survivalOnly && SongPlayer.MC.interactionManager.getCurrentGameMode() != GameMode.CREATIVE) { return; } - if (stage.nothingToBuild()) { - if (buildEndDelay > 0) { + if (stage.nothingToBuild()) { // If there's nothing to build, wait for end delay then check build status + if (buildEndDelay > 0) { // Wait for end delay buildEndDelay--; return; - } else { - stage.checkBuildStatus(currentSong); - recordStageBlocks(); + } else { // Check build status when end delay is over + if (!Config.getConfig().survivalOnly) { + stage.checkBuildStatus(currentSong); + recordStageBlocks(); + } else { + try { + stage.checkSurvivalBuildStatus(currentSong); + } catch (Stage.NotEnoughInstrumentsException e) { + e.giveInstrumentSummary(); + reset(); + return; + } + } stage.sendMovementPacketToStagePosition(); } } - if (!stage.requiredBreaks.isEmpty()) { - for (int i=0; i<5; i++) { - if (stage.requiredBreaks.isEmpty()) break; - BlockPos bp = stage.requiredBreaks.poll(); - attackBlock(bp); - } - buildEndDelay = 20; - } else if (!stage.missingNotes.isEmpty()) { - int desiredNoteId = stage.missingNotes.pollFirst(); - BlockPos bp = stage.noteblockPositions.get(desiredNoteId); - if (bp == null) { - return; - } - int blockId = Block.getRawIdFromState(world.getBlockState(bp)); - int currentNoteId = (blockId-SongPlayer.NOTEBLOCK_BASE_ID)/2; - if (currentNoteId != desiredNoteId) { - holdNoteblock(desiredNoteId, buildSlot); - if (blockId != 0) { - attackBlock(bp); - } - placeBlock(bp); - } - buildCooldown = 0; // No cooldown, so it places a block every tick - buildEndDelay = 20; - } else { // Switch to playing - restoreBuildSlot(); + if (stage.nothingToBuild()) { // If there's still nothing to build after checking build status, switch to playing + if (!Config.getConfig().survivalOnly) restoreBuildSlot(); building = false; - setSurvivalIfNeeded(); stage.sendMovementPacketToStagePosition(); SongPlayer.addChatMessage("§6Now playing §3" + currentSong.name); } + + if (!Config.getConfig().survivalOnly) { // Regular mode + if (!stage.requiredBreaks.isEmpty()) { + for (int i = 0; i < 5; i++) { + if (stage.requiredBreaks.isEmpty()) break; + BlockPos bp = stage.requiredBreaks.poll(); + attackBlock(bp); + } + buildEndDelay = 20; + } else if (!stage.missingNotes.isEmpty()) { + int desiredNoteId = stage.missingNotes.pollFirst(); + BlockPos bp = stage.noteblockPositions.get(desiredNoteId); + if (bp == null) { + return; + } + int blockId = Block.getRawIdFromState(world.getBlockState(bp)); + int currentNoteId = (blockId - SongPlayer.NOTEBLOCK_BASE_ID) / 2; + if (currentNoteId != desiredNoteId) { + holdNoteblock(desiredNoteId, buildSlot); + if (blockId != 0) { + attackBlock(bp); + } + placeBlock(bp); + } + buildCooldown = 0; // No cooldown, so it places a block every tick + buildEndDelay = 20; + } + } else { // Survival only mode + if (!stage.requiredClicks.isEmpty()) { + BlockPos bp = stage.requiredClicks.pollFirst(); + if (SongPlayer.MC.world.getBlockState(bp).getBlock() == Blocks.NOTE_BLOCK) { + placeBlock(bp); + } + buildEndDelay = 20; + } + } } private void setBuildProgressDisplay() { MutableText buildText = Text.empty() @@ -339,12 +360,22 @@ public class SongHandler { if (tick) { if (stage.hasBreakingModification()) { - stage.checkBuildStatus(currentSong); - recordStageBlocks(); + if (!Config.getConfig().survivalOnly) { + stage.checkBuildStatus(currentSong); + recordStageBlocks(); + } else { + try { + stage.checkSurvivalBuildStatus(currentSong); + } catch (Stage.NotEnoughInstrumentsException e) { + SongPlayer.addChatMessage("§6Stopped because stage is missing instruments required for song."); + reset(); + return; + } + } } if (!stage.nothingToBuild()) { // Switch to building building = true; - setCreativeIfNeeded(); + if (!Config.getConfig().survivalOnly) setCreativeIfNeeded(); stage.sendMovementPacketToStagePosition(); currentSong.pause(); buildStartDelay = 20; @@ -354,7 +385,7 @@ public class SongHandler { int instrumentId = note / 25; System.out.println("Missing note: " + Instrument.getInstrumentFromId(instrumentId).name() + ":" + pitch); } - getAndSaveBuildSlot(); + if (!Config.getConfig().survivalOnly) getAndSaveBuildSlot(); SongPlayer.addChatMessage("§6Stage was altered. Rebuilding!"); return; } @@ -585,10 +616,6 @@ public class SongHandler { cleanupPlaceList = cleanupPlaceList.reversed(); cleanupTotalBlocksToPlace = cleanupPlaceList.size(); - for (BlockPos bp : cleanupPlaceList) { - System.out.println(world.getBlockState(bp).getBlock() + " " + originalBlocks.get(bp).getBlock()); - } - boolean noNecessaryBreaks = cleanupBreakList.stream().allMatch( bp -> world.getBlockState(bp).getBlock().getDefaultState().equals(originalBlocks.get(bp).getBlock().getDefaultState()) ); @@ -624,7 +651,7 @@ public class SongHandler { if (lastStage != null) { lastStage.movePlayerToStagePosition(); } - if (originalGamemode != SongPlayer.MC.interactionManager.getCurrentGameMode()) { + if (originalGamemode != SongPlayer.MC.interactionManager.getCurrentGameMode() && !Config.getConfig().survivalOnly) { if (originalGamemode == GameMode.CREATIVE) { sendGamemodeCommand(Config.getConfig().creativeCommand); } @@ -635,7 +662,7 @@ public class SongHandler { if (SongPlayer.MC.player.getAbilities().allowFlying == false) { SongPlayer.MC.player.getAbilities().flying = false; } - restoreBuildSlot(); + if (!Config.getConfig().survivalOnly) restoreBuildSlot(); reset(); } public void partialResetAndCleanup() { diff --git a/src/main/java/com/github/hhhzzzsss/songplayer/playing/Stage.java b/src/main/java/com/github/hhhzzzsss/songplayer/playing/Stage.java index ae2ef29..be5b7fe 100644 --- a/src/main/java/com/github/hhhzzzsss/songplayer/playing/Stage.java +++ b/src/main/java/com/github/hhhzzzsss/songplayer/playing/Stage.java @@ -2,6 +2,7 @@ package com.github.hhhzzzsss.songplayer.playing; import com.github.hhhzzzsss.songplayer.Config; import com.github.hhhzzzsss.songplayer.SongPlayer; +import com.github.hhhzzzsss.songplayer.song.Instrument; import com.github.hhhzzzsss.songplayer.song.Song; import net.minecraft.block.Block; import net.minecraft.block.BlockState; @@ -25,10 +26,16 @@ public class Stage { public BlockPos position; public HashMap noteblockPositions = new HashMap<>(); + // Not used in survival-only mode public LinkedList requiredBreaks = new LinkedList<>(); public TreeSet missingNotes = new TreeSet<>(); public int totalMissingNotes = 0; - + + // Only used in survival-only mode + public LinkedList untunedNoteblocks = new LinkedList<>(); + public LinkedList requiredClicks = new LinkedList<>(); + public int totalUntunedNoteblocks = 0; + public Stage() { position = player.getBlockPos(); } @@ -195,6 +202,82 @@ public class Stage { totalMissingNotes = missingNotes.size(); } + public void checkSurvivalBuildStatus(Song song) throws NotEnoughInstrumentsException { + noteblockPositions.clear(); + + Map[] instrumentMap = loadSurvivalBlocks(); + + int[] requiredInstruments = new int[16]; + boolean hasMissing = false; + for (int instrumentId = 0; instrumentId < 16; instrumentId++) { + for (int pitch = 0; pitch < 25; pitch++) { + int noteId = instrumentId*25 + pitch; + if (song.requiredNotes[noteId]) { + requiredInstruments[instrumentId]++; + } + } + if (requiredInstruments[instrumentId] > instrumentMap[instrumentId].size()) { + hasMissing = true; + } + } + + if (hasMissing) { + int[] foundInstruments = new int[16]; + for (int i = 0; i < 16; i++) { + foundInstruments[i] = instrumentMap[i].size(); + } + throw new NotEnoughInstrumentsException(requiredInstruments, foundInstruments); + } + + for (int noteid=0; noteid<400; noteid++) { + if (song.requiredNotes[noteid]) { + int instrumentId = noteid / 25; + int targetPitch = noteid % 25; + Map.Entry closest = instrumentMap[instrumentId].entrySet() + .stream() + .min((a, b) -> { + int adist = (targetPitch - a.getValue() + 25) % 25; + int bdist = (targetPitch - b.getValue() + 25) % 25; + return Integer.compare(adist, bdist); + }) + .get(); + BlockPos bp = closest.getKey(); + int closestPitch = closest.getValue(); + instrumentMap[instrumentId].remove(bp); + noteblockPositions.put(noteid, bp); + int repetitions = (targetPitch - closestPitch + 25) % 25; + for (int i = 0; i < repetitions; i++) { + requiredClicks.add(bp); + } + } + } + } + + public class NotEnoughInstrumentsException extends Exception { + public int[] requiredInstruments; + public int[] foundInstruments; + public NotEnoughInstrumentsException(int[] requiredInstruments, int[] foundInstruments) { + this.requiredInstruments = requiredInstruments; + this.foundInstruments = foundInstruments; + } + public void giveInstrumentSummary() { + SongPlayer.addChatMessage("§c------------------------------"); + SongPlayer.addChatMessage("§cMissing instruments required to play song:"); + for (int instrumentId = 0; instrumentId < 16; instrumentId++) { + if (requiredInstruments[instrumentId] > 0) { + Instrument instrument = Instrument.getInstrumentFromId(instrumentId); + SongPlayer.addChatMessage(String.format( + " §3%s (%s): §%s%d/%d", + instrument.name(), instrument.material, + foundInstruments[instrumentId] < requiredInstruments[instrumentId] ? "c" : "a", + foundInstruments[instrumentId], requiredInstruments[instrumentId] + )); + } + } + SongPlayer.addChatMessage("§c------------------------------"); + } + } + void loadDefaultBlocks(Collection noteblockLocations, Collection breakLocations) { for (int dx = -4; dx <= 4; dx++) { for (int dz = -4; dz <= 4; dz++) { @@ -425,6 +508,29 @@ public class Stage { } } + Map[] loadSurvivalBlocks() { + Map[] instrumentMap = new Map[16]; + for (int i = 0; i < 16; i++) { + instrumentMap[i] = new TreeMap<>(); + } + for (int dx = -5; dx <= 5; dx++) { + for (int dz = -5; dz <= 5; dz++) { + for (int dy : new int[]{-1, 0, 1, 2, -2, 3, -3, 4, -4, 5, 6}) { + BlockPos bp = position.add(dx, dy, dz); + BlockState bs = SongPlayer.MC.world.getBlockState(bp); + int blockId = Block.getRawIdFromState(bs); + if (blockId >= SongPlayer.NOTEBLOCK_BASE_ID && blockId < SongPlayer.NOTEBLOCK_BASE_ID + 800) { + int noteId = (blockId - SongPlayer.NOTEBLOCK_BASE_ID) / 2; + int instrument = noteId / 25; + int pitch = noteId % 25; + instrumentMap[instrument].put(bp, pitch); + } + } + } + } + return instrumentMap; + } + // This doesn't check for whether the block above the noteblock position is also reachable // Usually there is sky above you though so hopefully this doesn't cause a problem most of the time boolean withinBreakingDist(int dx, int dy, int dz) { @@ -434,7 +540,11 @@ public class Stage { } public boolean nothingToBuild() { - return requiredBreaks.isEmpty() && missingNotes.isEmpty(); + if (!Config.getConfig().survivalOnly) { + return requiredBreaks.isEmpty() && missingNotes.isEmpty(); + } else { + return requiredClicks.isEmpty(); + } } private static final int WRONG_INSTRUMENT_TOLERANCE = 3; diff --git a/src/main/java/com/github/hhhzzzsss/songplayer/song/Instrument.java b/src/main/java/com/github/hhhzzzsss/songplayer/song/Instrument.java index 2047466..bdd8bc3 100644 --- a/src/main/java/com/github/hhhzzzsss/songplayer/song/Instrument.java +++ b/src/main/java/com/github/hhhzzzsss/songplayer/song/Instrument.java @@ -1,29 +1,31 @@ package com.github.hhhzzzsss.songplayer.song; public enum Instrument { - HARP(0, 54), - BASEDRUM(1, 0), - SNARE(2, 0), - HAT(3, 0), - BASS(4, 30), - FLUTE(5, 66), - BELL(6, 78), - GUITAR(7, 42), - CHIME(8, 78), - XYLOPHONE(9, 78), - IRON_XYLOPHONE(10, 54), - COW_BELL(11, 66), - DIDGERIDOO(12, 30), - BIT(13, 54), - BANJO(14, 54), - PLING(15, 54); + HARP(0, 54, "Dirt/Other"), + BASEDRUM(1, 0, "Any Stone"), + SNARE(2, 0, "Sand/Gravel"), + HAT(3, 0, "Glass"), + BASS(4, 30, "Any Wood"), + FLUTE(5, 66, "Clay"), + BELL(6, 78, "Block of Gold"), + GUITAR(7, 42, "Wool"), + CHIME(8, 78, "Packed Ice"), + XYLOPHONE(9, 78, "Bone Block"), + IRON_XYLOPHONE(10, 54, "Block of Iron"), + COW_BELL(11, 66, "Soul Sand"), + DIDGERIDOO(12, 30, "Pumpkin"), + BIT(13, 54, "Block of Emerald"), + BANJO(14, 54, "Hay Bale"), + PLING(15, 54, "Glowstone"); public final int instrumentId; public final int offset; + public final String material; - Instrument(int instrumentId, int offset) { + Instrument(int instrumentId, int offset, String material) { this.instrumentId = instrumentId; this.offset = offset; + this.material = material; } private static Instrument[] values = values();