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.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";

View file

@ -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) {

View file

@ -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() {

View file

@ -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<Integer, BlockPos> noteblockPositions = new HashMap<>();
// Not used in survival-only mode
public LinkedList<BlockPos> requiredBreaks = new LinkedList<>();
public TreeSet<Integer> missingNotes = new TreeSet<>();
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() {
position = player.getBlockPos();
}
@ -195,6 +202,82 @@ public class Stage {
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) {
for (int dx = -4; dx <= 4; dx++) {
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
// 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;

View file

@ -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();