Survival only mode

This commit is contained in:
hhhzzzsss 2024-05-30 22:11:38 -05:00
parent 44c614d6f0
commit e0c87ffda0
5 changed files with 237 additions and 69 deletions

View file

@ -9,17 +9,14 @@ import com.github.hhhzzzsss.songplayer.song.Playlist;
import com.github.hhhzzzsss.songplayer.song.Song; import com.github.hhhzzzsss.songplayer.song.Song;
import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.block.BlockState;
import net.minecraft.command.CommandSource; import net.minecraft.command.CommandSource;
import net.minecraft.component.DataComponentTypes; import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.NbtComponent; import net.minecraft.component.type.NbtComponent;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.state.property.Property;
import net.minecraft.text.*; import net.minecraft.text.*;
import net.minecraft.util.Formatting; import net.minecraft.util.Formatting;
import net.minecraft.util.Hand; import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.world.GameMode; import net.minecraft.world.GameMode;
import java.io.IOException; import java.io.IOException;
@ -61,6 +58,7 @@ public class CommandProcessor {
commands.add(new cleanupLastStageCommand()); commands.add(new cleanupLastStageCommand());
commands.add(new announcementCommand()); commands.add(new announcementCommand());
commands.add(new songItemCommand()); commands.add(new songItemCommand());
commands.add(new toggleSurvivalOnly());
commands.add(new testSongCommand()); commands.add(new testSongCommand());
for (Command command : commands) { for (Command command : commands) {
@ -1003,7 +1001,6 @@ public class CommandProcessor {
return true; return true;
} }
if (SongPlayer.MC.player.getPos().squaredDistanceTo(lastStage.getOriginBottomCenter()) > 3*3) { if (SongPlayer.MC.player.getPos().squaredDistanceTo(lastStage.getOriginBottomCenter()) > 3*3) {
System.out.println(SongPlayer.MC.player.getPos().squaredDistanceTo(lastStage.getOriginBottomCenter()));
String coordStr = String.format( String coordStr = String.format(
"%d %d %d", "%d %d %d",
lastStage.position.getX(), lastStage.position.getY(), lastStage.position.getZ() 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 { private static class testSongCommand extends Command {
public String getName() { public String getName() {
return "testSong"; return "testSong";

View file

@ -27,6 +27,7 @@ public class Config {
public boolean doAnnouncement = false; public boolean doAnnouncement = false;
public String announcementMessage = "&6Now playing: &3[name]"; public String announcementMessage = "&6Now playing: &3[name]";
public boolean autoCleanup = false; public boolean autoCleanup = false;
public boolean survivalOnly = false;
public static Config getConfig() { public static Config getConfig() {
if (config == null) { if (config == null) {

View file

@ -150,7 +150,7 @@ public class SongHandler {
// Otherwise, handle cleanup if necessary // Otherwise, handle cleanup if necessary
else { else {
if (dirty) { if (dirty) {
if (Config.getConfig().autoCleanup && originalBlocks.size() != 0) { if (Config.getConfig().autoCleanup && originalBlocks.size() != 0 && !Config.getConfig().survivalOnly) {
partialResetAndCleanup(); partialResetAndCleanup();
} else { } else {
restoreStateAndReset(); restoreStateAndReset();
@ -198,12 +198,12 @@ public class SongHandler {
dirty = true; dirty = true;
currentSong = song; currentSong = song;
building = true; building = true;
setCreativeIfNeeded(); if (!Config.getConfig().survivalOnly) setCreativeIfNeeded();
if (Config.getConfig().doAnnouncement) { if (Config.getConfig().doAnnouncement) {
sendMessage(Config.getConfig().announcementMessage.replaceAll("\\[name\\]", song.name)); sendMessage(Config.getConfig().announcementMessage.replaceAll("\\[name\\]", song.name));
} }
prepareStage(); prepareStage();
getAndSaveBuildSlot(); if (!Config.getConfig().survivalOnly) getAndSaveBuildSlot();
SongPlayer.addChatMessage("§6Building noteblocks"); SongPlayer.addChatMessage("§6Building noteblocks");
} }
@ -259,52 +259,73 @@ public class SongHandler {
return; return;
} }
ClientWorld world = SongPlayer.MC.world; ClientWorld world = SongPlayer.MC.world;
if (SongPlayer.MC.interactionManager.getCurrentGameMode() != GameMode.CREATIVE) { if (!Config.getConfig().survivalOnly && SongPlayer.MC.interactionManager.getCurrentGameMode() != GameMode.CREATIVE) {
return; return;
} }
if (stage.nothingToBuild()) { if (stage.nothingToBuild()) { // If there's nothing to build, wait for end delay then check build status
if (buildEndDelay > 0) { if (buildEndDelay > 0) { // Wait for end delay
buildEndDelay--; buildEndDelay--;
return; return;
} else { } else { // Check build status when end delay is over
stage.checkBuildStatus(currentSong); if (!Config.getConfig().survivalOnly) {
recordStageBlocks(); stage.checkBuildStatus(currentSong);
recordStageBlocks();
} else {
try {
stage.checkSurvivalBuildStatus(currentSong);
} catch (Stage.NotEnoughInstrumentsException e) {
e.giveInstrumentSummary();
reset();
return;
}
}
stage.sendMovementPacketToStagePosition(); stage.sendMovementPacketToStagePosition();
} }
} }
if (!stage.requiredBreaks.isEmpty()) { if (stage.nothingToBuild()) { // If there's still nothing to build after checking build status, switch to playing
for (int i=0; i<5; i++) { if (!Config.getConfig().survivalOnly) restoreBuildSlot();
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();
building = false; building = false;
setSurvivalIfNeeded();
stage.sendMovementPacketToStagePosition(); stage.sendMovementPacketToStagePosition();
SongPlayer.addChatMessage("§6Now playing §3" + currentSong.name); 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() { private void setBuildProgressDisplay() {
MutableText buildText = Text.empty() MutableText buildText = Text.empty()
@ -339,12 +360,22 @@ public class SongHandler {
if (tick) { if (tick) {
if (stage.hasBreakingModification()) { if (stage.hasBreakingModification()) {
stage.checkBuildStatus(currentSong); if (!Config.getConfig().survivalOnly) {
recordStageBlocks(); 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 if (!stage.nothingToBuild()) { // Switch to building
building = true; building = true;
setCreativeIfNeeded(); if (!Config.getConfig().survivalOnly) setCreativeIfNeeded();
stage.sendMovementPacketToStagePosition(); stage.sendMovementPacketToStagePosition();
currentSong.pause(); currentSong.pause();
buildStartDelay = 20; buildStartDelay = 20;
@ -354,7 +385,7 @@ public class SongHandler {
int instrumentId = note / 25; int instrumentId = note / 25;
System.out.println("Missing note: " + Instrument.getInstrumentFromId(instrumentId).name() + ":" + pitch); System.out.println("Missing note: " + Instrument.getInstrumentFromId(instrumentId).name() + ":" + pitch);
} }
getAndSaveBuildSlot(); if (!Config.getConfig().survivalOnly) getAndSaveBuildSlot();
SongPlayer.addChatMessage("§6Stage was altered. Rebuilding!"); SongPlayer.addChatMessage("§6Stage was altered. Rebuilding!");
return; return;
} }
@ -585,10 +616,6 @@ public class SongHandler {
cleanupPlaceList = cleanupPlaceList.reversed(); cleanupPlaceList = cleanupPlaceList.reversed();
cleanupTotalBlocksToPlace = cleanupPlaceList.size(); cleanupTotalBlocksToPlace = cleanupPlaceList.size();
for (BlockPos bp : cleanupPlaceList) {
System.out.println(world.getBlockState(bp).getBlock() + " " + originalBlocks.get(bp).getBlock());
}
boolean noNecessaryBreaks = cleanupBreakList.stream().allMatch( boolean noNecessaryBreaks = cleanupBreakList.stream().allMatch(
bp -> world.getBlockState(bp).getBlock().getDefaultState().equals(originalBlocks.get(bp).getBlock().getDefaultState()) bp -> world.getBlockState(bp).getBlock().getDefaultState().equals(originalBlocks.get(bp).getBlock().getDefaultState())
); );
@ -624,7 +651,7 @@ public class SongHandler {
if (lastStage != null) { if (lastStage != null) {
lastStage.movePlayerToStagePosition(); lastStage.movePlayerToStagePosition();
} }
if (originalGamemode != SongPlayer.MC.interactionManager.getCurrentGameMode()) { if (originalGamemode != SongPlayer.MC.interactionManager.getCurrentGameMode() && !Config.getConfig().survivalOnly) {
if (originalGamemode == GameMode.CREATIVE) { if (originalGamemode == GameMode.CREATIVE) {
sendGamemodeCommand(Config.getConfig().creativeCommand); sendGamemodeCommand(Config.getConfig().creativeCommand);
} }
@ -635,7 +662,7 @@ public class SongHandler {
if (SongPlayer.MC.player.getAbilities().allowFlying == false) { if (SongPlayer.MC.player.getAbilities().allowFlying == false) {
SongPlayer.MC.player.getAbilities().flying = false; SongPlayer.MC.player.getAbilities().flying = false;
} }
restoreBuildSlot(); if (!Config.getConfig().survivalOnly) restoreBuildSlot();
reset(); reset();
} }
public void partialResetAndCleanup() { public void partialResetAndCleanup() {

View file

@ -2,6 +2,7 @@ package com.github.hhhzzzsss.songplayer.playing;
import com.github.hhhzzzsss.songplayer.Config; import com.github.hhhzzzsss.songplayer.Config;
import com.github.hhhzzzsss.songplayer.SongPlayer; import com.github.hhhzzzsss.songplayer.SongPlayer;
import com.github.hhhzzzsss.songplayer.song.Instrument;
import com.github.hhhzzzsss.songplayer.song.Song; import com.github.hhhzzzsss.songplayer.song.Song;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -25,10 +26,16 @@ public class Stage {
public BlockPos position; public BlockPos position;
public HashMap<Integer, BlockPos> noteblockPositions = new HashMap<>(); public HashMap<Integer, BlockPos> noteblockPositions = new HashMap<>();
// Not used in survival-only mode
public LinkedList<BlockPos> requiredBreaks = new LinkedList<>(); public LinkedList<BlockPos> requiredBreaks = new LinkedList<>();
public TreeSet<Integer> missingNotes = new TreeSet<>(); public TreeSet<Integer> missingNotes = new TreeSet<>();
public int totalMissingNotes = 0; public int totalMissingNotes = 0;
// Only used in survival-only mode
public LinkedList<BlockPos> untunedNoteblocks = new LinkedList<>();
public LinkedList<BlockPos> requiredClicks = new LinkedList<>();
public int totalUntunedNoteblocks = 0;
public Stage() { public Stage() {
position = player.getBlockPos(); position = player.getBlockPos();
} }
@ -195,6 +202,82 @@ public class Stage {
totalMissingNotes = missingNotes.size(); totalMissingNotes = missingNotes.size();
} }
public void checkSurvivalBuildStatus(Song song) throws NotEnoughInstrumentsException {
noteblockPositions.clear();
Map<BlockPos, Integer>[] 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<BlockPos, Integer> 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<BlockPos> noteblockLocations, Collection<BlockPos> breakLocations) { void loadDefaultBlocks(Collection<BlockPos> noteblockLocations, Collection<BlockPos> breakLocations) {
for (int dx = -4; dx <= 4; dx++) { for (int dx = -4; dx <= 4; dx++) {
for (int dz = -4; dz <= 4; dz++) { for (int dz = -4; dz <= 4; dz++) {
@ -425,6 +508,29 @@ public class Stage {
} }
} }
Map<BlockPos, Integer>[] loadSurvivalBlocks() {
Map<BlockPos, Integer>[] 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 // 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 // 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) { boolean withinBreakingDist(int dx, int dy, int dz) {
@ -434,7 +540,11 @@ public class Stage {
} }
public boolean nothingToBuild() { 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; private static final int WRONG_INSTRUMENT_TOLERANCE = 3;

View file

@ -1,29 +1,31 @@
package com.github.hhhzzzsss.songplayer.song; package com.github.hhhzzzsss.songplayer.song;
public enum Instrument { public enum Instrument {
HARP(0, 54), HARP(0, 54, "Dirt/Other"),
BASEDRUM(1, 0), BASEDRUM(1, 0, "Any Stone"),
SNARE(2, 0), SNARE(2, 0, "Sand/Gravel"),
HAT(3, 0), HAT(3, 0, "Glass"),
BASS(4, 30), BASS(4, 30, "Any Wood"),
FLUTE(5, 66), FLUTE(5, 66, "Clay"),
BELL(6, 78), BELL(6, 78, "Block of Gold"),
GUITAR(7, 42), GUITAR(7, 42, "Wool"),
CHIME(8, 78), CHIME(8, 78, "Packed Ice"),
XYLOPHONE(9, 78), XYLOPHONE(9, 78, "Bone Block"),
IRON_XYLOPHONE(10, 54), IRON_XYLOPHONE(10, 54, "Block of Iron"),
COW_BELL(11, 66), COW_BELL(11, 66, "Soul Sand"),
DIDGERIDOO(12, 30), DIDGERIDOO(12, 30, "Pumpkin"),
BIT(13, 54), BIT(13, 54, "Block of Emerald"),
BANJO(14, 54), BANJO(14, 54, "Hay Bale"),
PLING(15, 54); PLING(15, 54, "Glowstone");
public final int instrumentId; public final int instrumentId;
public final int offset; public final int offset;
public final String material;
Instrument(int instrumentId, int offset) { Instrument(int instrumentId, int offset, String material) {
this.instrumentId = instrumentId; this.instrumentId = instrumentId;
this.offset = offset; this.offset = offset;
this.material = material;
} }
private static Instrument[] values = values(); private static Instrument[] values = values();