|
| 1 | +/* |
| 2 | + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) |
| 3 | + * |
| 4 | + * Copyright (c) 2015 - 2025 CCBlueX |
| 5 | + * |
| 6 | + * LiquidBounce is free software: you can redistribute it and/or modify |
| 7 | + * it under the terms of the GNU General Public License as published by |
| 8 | + * the Free Software Foundation, either version 3 of the License, or |
| 9 | + * (at your option) any later version. |
| 10 | + * |
| 11 | + * LiquidBounce is distributed in the hope that it will be useful, |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | + * GNU General Public License for more details. |
| 15 | + * |
| 16 | + * You should have received a copy of the GNU General Public License |
| 17 | + * along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>. |
| 18 | + */ |
| 19 | +package net.ccbluex.liquidbounce.features.module.modules.`fun`.notebot |
| 20 | + |
| 21 | +import kotlinx.coroutines.Dispatchers |
| 22 | +import kotlinx.coroutines.withContext |
| 23 | +import net.ccbluex.liquidbounce.config.types.nesting.Configurable |
| 24 | +import net.ccbluex.liquidbounce.event.events.PacketEvent |
| 25 | +import net.ccbluex.liquidbounce.event.handler |
| 26 | +import net.ccbluex.liquidbounce.event.tickHandler |
| 27 | +import net.ccbluex.liquidbounce.features.module.Category |
| 28 | +import net.ccbluex.liquidbounce.features.module.ClientModule |
| 29 | +import net.ccbluex.liquidbounce.features.module.modules.`fun`.notebot.nbs.InstrumentNote |
| 30 | +import net.ccbluex.liquidbounce.features.module.modules.`fun`.notebot.nbs.NbsLoader |
| 31 | +import net.ccbluex.liquidbounce.features.module.modules.`fun`.notebot.nbs.NbsNoteBlock |
| 32 | +import net.ccbluex.liquidbounce.features.module.modules.`fun`.notebot.nbs.SongData |
| 33 | +import net.ccbluex.liquidbounce.features.module.modules.world.packetmine.ModulePacketMine |
| 34 | +import net.ccbluex.liquidbounce.render.engine.type.Color4b |
| 35 | +import net.ccbluex.liquidbounce.utils.aiming.RotationsConfigurable |
| 36 | +import net.ccbluex.liquidbounce.utils.client.* |
| 37 | +import net.minecraft.block.enums.NoteBlockInstrument |
| 38 | +import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket |
| 39 | +import net.minecraft.text.MutableText |
| 40 | +import net.minecraft.util.Formatting |
| 41 | +import net.minecraft.util.math.MathHelper |
| 42 | +import java.util.* |
| 43 | + |
| 44 | +/** |
| 45 | + * Notebot Module |
| 46 | + * |
| 47 | + * Automatically plays note block songs from NBS files. |
| 48 | + * |
| 49 | + * @author ccetl |
| 50 | + */ |
| 51 | +object ModuleNotebot : ClientModule("Notebot", Category.FUN, disableOnQuit = true) { |
| 52 | + |
| 53 | + private val song = file("Song", supportedExtensions = setOf("nbs")) |
| 54 | + private val pianoOnly by boolean("PianoOnly", false) |
| 55 | + val reuseBlocks by boolean("ReuseBlocks", true).onChanged { enabled = false } |
| 56 | + val range by float("Range", 6f, 1f..6f) |
| 57 | + val rotationsConfigurable = RotationsConfigurable(this) |
| 58 | + val ignoreOpenInventory by boolean("IgnoreOpenInventory", true) |
| 59 | + |
| 60 | + private object StartDelay : Configurable("StartDelay") { |
| 61 | + val test by int("Test", 0, 0..20, "ticks") |
| 62 | + val tune by int("Tune", 0, 0..20, "ticks") |
| 63 | + val play by int("Play", 2, 0..20, "ticks") |
| 64 | + } |
| 65 | + |
| 66 | + init { |
| 67 | + tree(StartDelay) |
| 68 | + } |
| 69 | + |
| 70 | + val renderer = tree(NotebotRenderer) |
| 71 | + |
| 72 | + var engine: NotebotEngine? = null |
| 73 | + private set |
| 74 | + |
| 75 | + @Suppress("unused") |
| 76 | + private val tickHandler = tickHandler { |
| 77 | + engine?.onTick(this) |
| 78 | + } |
| 79 | + |
| 80 | + @Suppress("unused") |
| 81 | + private val packetHandler = handler<PacketEvent> { event -> |
| 82 | + if (event.packet is PlaySoundS2CPacket) { |
| 83 | + this.engine?.handleSoundPacket(event.packet) |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + override suspend fun enabledEffect() { |
| 88 | + val messageMetadata = MessageMetadata(id = "M${this.name}#loaded", remove = false) |
| 89 | + mc.inGameHud.chatHud.removeMessage(messageMetadata.id) |
| 90 | + |
| 91 | + if (!checkRequirements()) { |
| 92 | + this.enabled = false |
| 93 | + return |
| 94 | + } |
| 95 | + |
| 96 | + val songData = loadSongData() |
| 97 | + |
| 98 | + if (songData == null) { |
| 99 | + this.enabled = false |
| 100 | + return |
| 101 | + } |
| 102 | + |
| 103 | + val blocksAndRequirements = NotebotScanner.scanBlocksAndCheckRequirements(songData) |
| 104 | + |
| 105 | + if (!blocksAndRequirements.validateRequirements()) { |
| 106 | + blocksAndRequirements.printRequirements() |
| 107 | + this.enabled = false |
| 108 | + return |
| 109 | + } |
| 110 | + |
| 111 | + this.setRenderedBlocks(blocksAndRequirements.availableBlocks.flatMap { it.value }) |
| 112 | + |
| 113 | + showSongInfo(songData, messageMetadata) |
| 114 | + |
| 115 | + this.engine = NotebotEngine(songData, blocksAndRequirements) |
| 116 | + chat(message("startTesting").formatted(Formatting.GREEN), this) |
| 117 | + } |
| 118 | + |
| 119 | + fun setRenderedBlocks(blocks: List<NoteBlockTracker>) { |
| 120 | + renderer.clearSilently() |
| 121 | + |
| 122 | + blocks.forEach { |
| 123 | + renderer.addBlock(it.pos, false) |
| 124 | + } |
| 125 | + |
| 126 | + renderer.updateAll() |
| 127 | + } |
| 128 | + |
| 129 | + private suspend fun loadSongData(): SongData? { |
| 130 | + chat(message("startLoading").formatted(Formatting.GREEN), this) |
| 131 | + |
| 132 | + val songData = withContext(Dispatchers.IO) { |
| 133 | + NbsLoader.load(song.absoluteFile) |
| 134 | + } |
| 135 | + |
| 136 | + return songData |
| 137 | + } |
| 138 | + |
| 139 | + private fun checkRequirements(): Boolean { |
| 140 | + return when { |
| 141 | + !inGame -> { |
| 142 | + chat(markAsError(message("notInGame")), this) |
| 143 | + false |
| 144 | + } |
| 145 | + |
| 146 | + player.isCreative -> { |
| 147 | + chat(markAsError(message("inCreative")), this) |
| 148 | + false |
| 149 | + } |
| 150 | + |
| 151 | + ModulePacketMine.enabled -> { |
| 152 | + chat(markAsError(message("packetMineEnabled")), this) |
| 153 | + false |
| 154 | + } |
| 155 | + |
| 156 | + else -> true |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + private fun showSongInfo( |
| 161 | + songData: SongData, |
| 162 | + messageMetadata: MessageMetadata |
| 163 | + ) { |
| 164 | + chat( |
| 165 | + regular(message("songInfoName", variable(songData.name))), |
| 166 | + messageMetadata |
| 167 | + ) |
| 168 | + chat( |
| 169 | + regular(message("songInfoTicksPerGameTick", variable(songData.songTicksPerGameTick.toString()))), |
| 170 | + messageMetadata |
| 171 | + ) |
| 172 | + chat( |
| 173 | + regular(message("songInfoTickLength", variable(songData.songTickLength.toString()))), |
| 174 | + messageMetadata |
| 175 | + ) |
| 176 | + chat( |
| 177 | + regular(message("songInfoTotalNotes", variable(songData.nbs.noteBlocks.size.toString()))), |
| 178 | + messageMetadata |
| 179 | + ) |
| 180 | + } |
| 181 | + |
| 182 | + override fun onDisabled() { |
| 183 | + removeProgressMessage() |
| 184 | + |
| 185 | + renderer.reset() |
| 186 | + } |
| 187 | + |
| 188 | + private val progressMessageMetadata = MessageMetadata(id = "M$name#progress", remove = false) |
| 189 | + |
| 190 | + private fun removeProgressMessage() { |
| 191 | + mc.inGameHud.chatHud.removeMessage(progressMessageMetadata.id) |
| 192 | + } |
| 193 | + |
| 194 | + fun sendNewProgressMessage(name: MutableText, progress: Int, total: Int) { |
| 195 | + removeProgressMessage() |
| 196 | + |
| 197 | + val percent = (progress.toDouble() / total.toDouble() * 100.0).toInt() |
| 198 | + chat( |
| 199 | + variable(name.copy()) |
| 200 | + .append(regular(" [")) |
| 201 | + .append(textLoadingBar(percent)) |
| 202 | + .append(regular("] ")) |
| 203 | + .append(variable(percent.toString())) |
| 204 | + .append(regular("%")), |
| 205 | + metadata = progressMessageMetadata |
| 206 | + ) |
| 207 | + } |
| 208 | + |
| 209 | + fun getPlayedNote(note: NbsNoteBlock): InstrumentNote { |
| 210 | + val noteValue = MathHelper.clamp(note.key - 33, 0, 24) |
| 211 | + val instrument = if (!this.pianoOnly) { |
| 212 | + note.instrument.toInt() |
| 213 | + } else { |
| 214 | + 0 |
| 215 | + } |
| 216 | + |
| 217 | + return InstrumentNote(instrument, noteValue) |
| 218 | + } |
| 219 | + |
| 220 | + fun getRequiredInstruments(songData: SongData): EnumSet<NoteBlockInstrument> { |
| 221 | + if (pianoOnly) { |
| 222 | + return EnumSet.of(NoteBlockInstrument.HARP) |
| 223 | + } |
| 224 | + |
| 225 | + return songData.nbs.noteBlocks |
| 226 | + .mapTo(EnumSet.noneOf(NoteBlockInstrument::class.java)) { |
| 227 | + InstrumentNote.getInstrumentEnumFromId(it.instrument.toInt()) |
| 228 | + } |
| 229 | + } |
| 230 | + |
| 231 | + enum class NotebotStage( |
| 232 | + val stageStartDelay: () -> Int, |
| 233 | + val blockColor: () -> Color4b, |
| 234 | + val blockOutlineColor: () -> Color4b |
| 235 | + ) { |
| 236 | + TEST(StartDelay::test, NotebotRenderer::testColor, NotebotRenderer::outlineTestColor), |
| 237 | + TUNE(StartDelay::tune, NotebotRenderer::tuneColor, NotebotRenderer::outlineTuneColor), |
| 238 | + PLAY(StartDelay::play, NotebotRenderer::colorSetting, NotebotRenderer::outlineColorSetting) |
| 239 | + } |
| 240 | + |
| 241 | + interface NotebotStageHandler { |
| 242 | + val handledStage: NotebotStage |
| 243 | + |
| 244 | + fun onTick(engine: NotebotEngine) |
| 245 | + } |
| 246 | +} |
0 commit comments