diff --git a/src/main/java/com/lambda/mixin/network/MixinNetHandlerPlayClient.java b/src/main/java/com/lambda/mixin/network/MixinNetHandlerPlayClient.java new file mode 100644 index 000000000..82a85b304 --- /dev/null +++ b/src/main/java/com/lambda/mixin/network/MixinNetHandlerPlayClient.java @@ -0,0 +1,23 @@ +package com.lambda.mixin.network; + +import com.lambda.client.event.LambdaEventBus; +import com.lambda.client.event.events.ChunkDataEvent; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.client.network.NetHandlerPlayClient; +import net.minecraft.network.play.server.SPacketChunkData; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = NetHandlerPlayClient.class) +public class MixinNetHandlerPlayClient { + + @Shadow private WorldClient world; + + @Inject(method = "handleChunkData", at = @At("TAIL")) + public void handleChunkData(SPacketChunkData packetIn, CallbackInfo ci) { + LambdaEventBus.INSTANCE.post(new ChunkDataEvent(packetIn.isFullChunk(), this.world.getChunk(packetIn.getChunkX(), packetIn.getChunkZ()))); + } +} diff --git a/src/main/kotlin/com/lambda/client/command/Args.kt b/src/main/kotlin/com/lambda/client/command/Args.kt index 528bf3082..8034436be 100644 --- a/src/main/kotlin/com/lambda/client/command/Args.kt +++ b/src/main/kotlin/com/lambda/client/command/Args.kt @@ -14,6 +14,7 @@ import com.lambda.client.util.* import com.lambda.client.util.threads.runSafeR import kotlinx.coroutines.Dispatchers import net.minecraft.block.Block +import net.minecraft.entity.EntityList import net.minecraft.item.Item import net.minecraft.util.math.BlockPos import java.io.File @@ -94,6 +95,21 @@ class BlockArg( } } +class EntityArg( + override val name: String +) : AbstractArg(), AutoComplete by StaticPrefixMatch(allEntityNames) { + override suspend fun convertToType(string: String?): String? { + if (string == null) return null + // checks if a valid entity class is registered with this name + return if (EntityList.getClassFromName(string) != null) string else null + } + + private companion object { + val allEntityNames = EntityList.getEntityNameList().map { it.path } + } +} + + class BaritoneBlockArg( override val name: String ) : AbstractArg(), AutoComplete by StaticPrefixMatch(baritoneBlockNames) { diff --git a/src/main/kotlin/com/lambda/client/command/ClientCommand.kt b/src/main/kotlin/com/lambda/client/command/ClientCommand.kt index c0c7fdcd2..4c633a84b 100644 --- a/src/main/kotlin/com/lambda/client/command/ClientCommand.kt +++ b/src/main/kotlin/com/lambda/client/command/ClientCommand.kt @@ -1,7 +1,6 @@ package com.lambda.client.command import com.lambda.client.capeapi.PlayerProfile -import com.lambda.client.command.CommandBuilder import com.lambda.client.command.args.AbstractArg import com.lambda.client.command.utils.BuilderBlock import com.lambda.client.command.utils.ExecuteBlock @@ -51,6 +50,14 @@ abstract class ClientCommand( arg(BlockArg(name), block) } + @CommandBuilder + protected inline fun AbstractArg<*>.entity( + name: String, + entity: BuilderBlock + ) { + arg(EntityArg(name), entity) + } + @CommandBuilder protected inline fun AbstractArg<*>.item( name: String, diff --git a/src/main/kotlin/com/lambda/client/command/commands/SearchCommand.kt b/src/main/kotlin/com/lambda/client/command/commands/SearchCommand.kt index 08798723e..1bcc6b0ea 100644 --- a/src/main/kotlin/com/lambda/client/command/commands/SearchCommand.kt +++ b/src/main/kotlin/com/lambda/client/command/commands/SearchCommand.kt @@ -2,18 +2,34 @@ package com.lambda.client.command.commands import com.lambda.client.command.ClientCommand import com.lambda.client.module.modules.render.Search +import com.lambda.client.util.items.shulkerList +import com.lambda.client.util.items.signsList import com.lambda.client.util.text.MessageSendHelper import com.lambda.client.util.text.formatValue +import net.minecraft.init.Blocks // TODO: Remove once GUI has List object SearchCommand : ClientCommand( name = "search", description = "Manage search blocks" ) { - private val warningBlocks = arrayOf("minecraft:grass", "minecraft:end_stone", "minecraft:lava", "minecraft:bedrock", "minecraft:netherrack", "minecraft:dirt", "minecraft:water", "minecraft:stone") + private val warningBlocks = hashSetOf(Blocks.GRASS, Blocks.END_STONE, Blocks.LAVA, Blocks.FLOWING_LAVA, + Blocks.BEDROCK, Blocks.NETHERRACK, Blocks.DIRT, Blocks.WATER, Blocks.FLOWING_WATER, Blocks.STONE) init { literal("add", "+") { + literal("shulker_box") { + execute("Add all shulker box types to search") { + Search.blockSearchList.editValue { searchList -> shulkerList.map { it.registryName.toString() }.forEach { searchList.add(it) } } + MessageSendHelper.sendChatMessage("All shulker boxes have been added to block search") + } + } + literal("sign") { + execute("Add all signs to search") { + Search.blockSearchList.editValue { searchList -> signsList.map { it.registryName.toString() }.forEach { searchList.add(it) } } + MessageSendHelper.sendChatMessage("All signs have been added to block search") + } + } block("block") { blockArg -> literal("force") { execute("Force add a block to search list") { @@ -21,11 +37,24 @@ object SearchCommand : ClientCommand( addBlock(blockName) } } - + int("dimension") { dimArg -> + execute("Add a block to dimension filter") { + val blockName = blockArg.value.registryName.toString() + val dim = dimArg.value + val dims = Search.blockSearchDimensionFilter.value.find { dimFilter -> dimFilter.searchKey == blockName }?.dim + if (dims != null && !dims.contains(dim)) { + dims.add(dim) + } else { + Search.blockSearchDimensionFilter.value.add(Search.DimensionFilter(blockName, linkedSetOf(dim))) + } + MessageSendHelper.sendChatMessage("Block search filter added for $blockName in dimension ${dimArg.value}") + } + } execute("Add a block to search list") { + val block = blockArg.value val blockName = blockArg.value.registryName.toString() - if (warningBlocks.contains(blockName)) { + if (warningBlocks.contains(block)) { MessageSendHelper.sendWarningMessage("Your world contains lots of ${formatValue(blockName)}, " + "it might cause extreme lag to add it. " + "If you are sure you want to add it run ${formatValue("$prefixName add $blockName force")}" @@ -35,20 +64,93 @@ object SearchCommand : ClientCommand( } } } + entity("entity") { entityArg -> + int("dimension") {dimArg -> + execute("Add an entity to dimension filter") { + val entityName = entityArg.value + val dim = dimArg.value + val dims = Search.entitySearchDimensionFilter.value.find { dimFilter -> dimFilter.searchKey == entityName }?.dim + if (dims != null && !dims.contains(dim)) { + dims.add(dim) + } else { + Search.entitySearchDimensionFilter.value.add(Search.DimensionFilter(entityName, linkedSetOf(dim))) + } + MessageSendHelper.sendChatMessage("Entity search filter added for $entityName in dimension ${dimArg.value}") + } + } + + execute("Add an entity to search list") { + val entityName = entityArg.value + if (Search.entitySearchList.contains(entityName)) { + MessageSendHelper.sendChatMessage("$entityName is already added to search list") + return@execute + } + Search.entitySearchList.editValue { it.add(entityName) } + MessageSendHelper.sendChatMessage("$entityName has been added to search list") + } + } } literal("remove", "-") { + literal("shulker_box") { + execute("Remove all shulker boxes from search") { + Search.blockSearchList.editValue { searchList -> shulkerList.map { it.registryName.toString() }.forEach { searchList.remove(it) } } + MessageSendHelper.sendChatMessage("Removed all shulker boxes from block search") + } + } + literal("sign") { + execute("Remove all signs from search") { + Search.blockSearchList.editValue { searchList -> signsList.map { it.registryName.toString() }.forEach { searchList.remove(it) } } + MessageSendHelper.sendChatMessage("Removed all signs from block search") + } + } block("block") { blockArg -> + int("dimension") {dimArg -> + execute("Remove a block from dimension filter") { + val blockName = blockArg.value.registryName.toString() + val dim = dimArg.value + val dims = Search.blockSearchDimensionFilter.value.find { dimFilter -> dimFilter.searchKey == blockName }?.dim + if (dims != null) { + dims.remove(dim) + if (dims.isEmpty()) { + Search.blockSearchDimensionFilter.value.removeIf { it.searchKey == blockName } + } + } + MessageSendHelper.sendChatMessage("Block search filter removed for $blockName in dimension ${dimArg.value}") + } + } execute("Remove a block from search list") { val blockName = blockArg.value.registryName.toString() - if (!Search.searchList.remove(blockName)) { + if (!Search.blockSearchList.contains(blockName)) { MessageSendHelper.sendErrorMessage("You do not have ${formatValue(blockName)} added to search block list") } else { + Search.blockSearchList.editValue { it.remove(blockName) } MessageSendHelper.sendChatMessage("Removed ${formatValue(blockName)} from search block list") } } } + entity("entity") {entityArg -> + int("dimension") {dimArg -> + execute("Remove an entity from dimension filter") { + val entityName = entityArg.value + val dim = dimArg.value + val dims = Search.entitySearchDimensionFilter.value.find { dimFilter -> dimFilter.searchKey == entityName }?.dim + if (dims != null) { + dims.remove(dim) + if (dims.isEmpty()) { + Search.entitySearchDimensionFilter.value.removeIf { it.searchKey == entityName } + } + } + MessageSendHelper.sendChatMessage("Entity search filter removed for $entityName in dimension ${dimArg.value}") + } + } + execute("Remove an entity from search list") { + val entityName = entityArg.value + Search.entitySearchList.editValue { it.remove(entityName) } + MessageSendHelper.sendChatMessage("Removed $entityName from search list") + } + } } literal("set", "=") { @@ -56,29 +158,54 @@ object SearchCommand : ClientCommand( execute("Set the search list to one block") { val blockName = blockArg.value.registryName.toString() - Search.searchList.clear() - Search.searchList.add(blockName) + Search.blockSearchList.editValue { + it.clear() + it.add(blockName) + } MessageSendHelper.sendChatMessage("Set the search block list to ${formatValue(blockName)}") } } + entity("entity") { entityArg -> + execute("Sets the search list to one entity") { + val entityName = entityArg.value + Search.entitySearchList.editValue { + it.clear() + it.add(entityName) + } + MessageSendHelper.sendChatMessage("Set the entity search list to $entityName") + } + } } literal("reset", "default") { execute("Reset the search list to defaults") { - Search.searchList.resetValue() - MessageSendHelper.sendChatMessage("Reset the search block list to defaults") + Search.blockSearchList.resetValue() + Search.entitySearchList.resetValue() + Search.blockSearchDimensionFilter.resetValue() + Search.entitySearchDimensionFilter.resetValue() + MessageSendHelper.sendChatMessage("Reset the search list to defaults") } } literal("list") { execute("Print search list") { - MessageSendHelper.sendChatMessage(Search.searchList.joinToString()) + MessageSendHelper.sendChatMessage("Blocks: ${Search.blockSearchList.joinToString()}") + if (Search.blockSearchDimensionFilter.value.isNotEmpty()) { + MessageSendHelper.sendChatMessage("Block dimension filter: ${Search.blockSearchDimensionFilter.value}") + } + MessageSendHelper.sendChatMessage("Entities ${Search.entitySearchList.joinToString()}") + if (Search.entitySearchDimensionFilter.value.isNotEmpty()) { + MessageSendHelper.sendChatMessage("Entity dimension filter: ${Search.entitySearchDimensionFilter.value}") + } } } literal("clear") { execute("Set the search list to nothing") { - Search.searchList.clear() + Search.blockSearchList.editValue { it.clear() } + Search.entitySearchList.editValue { it.clear() } + Search.blockSearchDimensionFilter.editValue { it.clear() } + Search.entitySearchDimensionFilter.editValue { it.clear() } MessageSendHelper.sendChatMessage("Cleared the search block list") } } @@ -97,9 +224,10 @@ object SearchCommand : ClientCommand( return } - if (!Search.searchList.add(blockName)) { + if (Search.blockSearchList.contains(blockName)) { MessageSendHelper.sendErrorMessage("${formatValue(blockName)} is already added to the search block list") } else { + Search.blockSearchList.editValue { it.add(blockName) } MessageSendHelper.sendChatMessage("${formatValue(blockName)} has been added to the search block list") } } diff --git a/src/main/kotlin/com/lambda/client/event/events/ChunkDataEvent.kt b/src/main/kotlin/com/lambda/client/event/events/ChunkDataEvent.kt new file mode 100644 index 000000000..ccedc59e3 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/event/events/ChunkDataEvent.kt @@ -0,0 +1,9 @@ +package com.lambda.client.event.events + +import com.lambda.client.event.Event +import net.minecraft.world.chunk.Chunk + +/** + * Event emitted when chunk data is read + */ +class ChunkDataEvent(val isFullChunk: Boolean, val chunk: Chunk): Event \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/module/modules/player/InventoryManager.kt b/src/main/kotlin/com/lambda/client/module/modules/player/InventoryManager.kt index c4cf875d4..a6b292a39 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/player/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/player/InventoryManager.kt @@ -47,7 +47,7 @@ object InventoryManager : Module( private val pauseMovement by setting("Pause Movement", true) private val delay by setting("Delay Ticks", 1, 0..20, 1, unit = " ticks") private val helpMend by setting("Help Mend", false, description = "Helps mending items by replacing the offhand item with low HP items of the same type") - val ejectList = setting(CollectionSetting("Eject List", defaultEjectList)) + val ejectList = setting(CollectionSetting("Eject List", defaultEjectList, String::class.java)) enum class State { IDLE, SAVING_ITEM, HELPING_MEND, REFILLING_BUILDING, REFILLING, EJECTING diff --git a/src/main/kotlin/com/lambda/client/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/client/module/modules/player/Scaffold.kt index 4d4dbab86..b8947199e 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/player/Scaffold.kt @@ -67,8 +67,8 @@ object Scaffold : Module( private val thickness by setting("Outline Thickness", 2f, .25f..4f, .25f, { outline && page == Page.RENDER }, description = "Changes thickness of the outline") private val pendingBlockColor by setting("Pending Color", ColorHolder(0, 0, 255), visibility = { page == Page.RENDER }) - val blockSelectionWhitelist = setting(CollectionSetting("BlockWhitelist", linkedSetOf("minecraft:obsidian"), { false })) - val blockSelectionBlacklist = setting(CollectionSetting("BlockBlacklist", blockBlacklist.map { it.registryName.toString() }.toMutableSet(), { false })) + val blockSelectionWhitelist = setting(CollectionSetting("BlockWhitelist", linkedSetOf("minecraft:obsidian"), String::class.java, { false })) + val blockSelectionBlacklist = setting(CollectionSetting("BlockBlacklist", blockBlacklist.map { it.registryName.toString() }.toMutableSet(), String::class.java, { false })) private enum class Page { GENERAL, RENDER diff --git a/src/main/kotlin/com/lambda/client/module/modules/render/Search.kt b/src/main/kotlin/com/lambda/client/module/modules/render/Search.kt index 6f4563013..f95f04ea4 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/render/Search.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/render/Search.kt @@ -2,11 +2,14 @@ package com.lambda.client.module.modules.render import com.lambda.client.command.CommandManager import com.lambda.client.event.SafeClientEvent +import com.lambda.client.event.events.ChunkDataEvent +import com.lambda.client.event.events.ConnectionEvent +import com.lambda.client.event.events.PacketEvent import com.lambda.client.event.events.RenderWorldEvent import com.lambda.client.module.Category import com.lambda.client.module.Module +import com.lambda.client.module.modules.client.Hud import com.lambda.client.setting.settings.impl.collection.CollectionSetting -import com.lambda.client.util.TickTimer import com.lambda.client.util.color.ColorHolder import com.lambda.client.util.graphics.ESPRenderer import com.lambda.client.util.graphics.GeometryMasks @@ -15,19 +18,28 @@ import com.lambda.client.util.math.VectorUtils.distanceTo import com.lambda.client.util.text.MessageSendHelper import com.lambda.client.util.text.formatValue import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.threads.runSafe import com.lambda.client.util.threads.safeListener -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay +import com.lambda.client.util.world.isWater +import kotlinx.coroutines.Job +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import net.minecraft.block.BlockEnderChest +import net.minecraft.block.BlockShulkerBox import net.minecraft.block.state.IBlockState +import net.minecraft.entity.EntityList +import net.minecraft.entity.item.EntityItemFrame import net.minecraft.init.Blocks -import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.network.play.server.SPacketBlockChange +import net.minecraft.network.play.server.SPacketMultiBlockChange import net.minecraft.util.math.BlockPos import net.minecraft.util.math.ChunkPos -import net.minecraft.util.math.Vec3d import net.minecraft.world.chunk.Chunk -import java.util.* +import net.minecraftforge.fml.common.gameevent.TickEvent +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap import kotlin.collections.set +import kotlin.math.max object Search : Module( name = "Search", @@ -36,143 +48,316 @@ object Search : Module( ) { private val defaultSearchList = linkedSetOf("minecraft:portal", "minecraft:end_portal_frame", "minecraft:bed") - private val updateDelay by setting("Update Delay", 1000, 500..3000, 50) - private val range by setting("Search Range", 128, 0..256, 8) + private val entitySearch by setting("Entity Search", true) + private val blockSearch by setting("Block Search", true) + private val illegalBedrock = setting("Illegal Bedrock", false) + private val illegalNetherWater = setting("Illegal Nether Water", false) + private val range by setting("Search Range", 512, 0..4096, 8) private val yRangeBottom by setting("Top Y", 256, 0..256, 1) private val yRangeTop by setting("Bottom Y", 0, 0..256, 1) - private val maximumBlocks by setting("Maximum Blocks", 256, 16..4096, 128) + private val maximumBlocks by setting("Maximum Blocks", 256, 1..4096, 128, visibility = { blockSearch }) + private val maximumEntities by setting("Maximum Entities", 256, 1..4096, 128, visibility = { entitySearch }) private val filled by setting("Filled", true) private val outline by setting("Outline", true) private val tracer by setting("Tracer", true) - private val customColors by setting("Custom Colors", false) - private val customColor by setting("Custom Color", ColorHolder(155, 144, 255), visibility = { customColors }) + private val entitySearchColor by setting("Entity Search Color", Hud.secondaryColor, visibility = { entitySearch }) + private val autoBlockColor by setting("Block Search Auto Color", true) + private val customBlockColor by setting("Block Search Custom Color", Hud.secondaryColor, visibility = { !autoBlockColor }) private val aFilled by setting("Filled Alpha", 31, 0..255, 1, { filled }) private val aOutline by setting("Outline Alpha", 127, 0..255, 1, { outline }) private val aTracer by setting("Tracer Alpha", 200, 0..255, 1, { tracer }) private val thickness by setting("Line Thickness", 2.0f, 0.25f..5.0f, 0.25f) + private val hideF1 by setting("Hide on F1", true) var overrideWarning by setting("Override Warning", false, { false }) - val searchList = setting(CollectionSetting("Search List", defaultSearchList, { false })) + val blockSearchList = setting(CollectionSetting("Search List", defaultSearchList, String::class.java, { false })) + val entitySearchList = setting(CollectionSetting("Entity Search List", linkedSetOf(EntityList.getKey((EntityItemFrame::class.java))!!.path), String::class.java, { false })) + val blockSearchDimensionFilter = setting(CollectionSetting("Block Dimension Filter", linkedSetOf(), DimensionFilter::class.java, { false })) + val entitySearchDimensionFilter = setting(CollectionSetting("Entity Dimension Filter", linkedSetOf(), DimensionFilter::class.java, { false })) - private val renderer = ESPRenderer() - private val updateTimer = TickTimer() + private val blockRenderer = ESPRenderer() + private val entityRenderer = ESPRenderer() + private val foundBlockMap: ConcurrentMap = ConcurrentHashMap() + private var blockRenderUpdateJob: Job? = null + private var entityRenderUpdateJob: Job? = null + private var blockSearchJob: Job? = null + private var prevDimension = -2 override fun getHudInfo(): String { - return renderer.size.toString() + return (blockRenderer.size + entityRenderer.size).toString() } init { + blockSearchList.editListeners.add { blockSearchListUpdateListener(isEnabled) } + illegalBedrock.listeners.add { blockSearchListUpdateListener(illegalBedrock.value) } + illegalNetherWater.listeners.add { blockSearchListUpdateListener(illegalNetherWater.value) } + onEnable { if (!overrideWarning && ShaderHelper.isIntegratedGraphics) { MessageSendHelper.sendErrorMessage("$chatName Warning: Running Search with an Intel Integrated GPU is not recommended, as it has a &llarge&r impact on performance.") MessageSendHelper.sendWarningMessage("$chatName If you're sure you want to try, run the ${formatValue("${CommandManager.prefix}search override")} command") disable() - return@onEnable + } else { + runSafe { searchAllLoadedChunks() } } } - safeListener { - renderer.render(false) + onDisable { + blockRenderUpdateJob?.cancel() + entityRenderUpdateJob?.cancel() + blockSearchJob?.cancel() + blockRenderer.clear() + entityRenderer.clear() + foundBlockMap.clear() + } - if (updateTimer.tick(updateDelay.toLong())) { - updateRenderer() + safeListener { + if (player.dimension != prevDimension) { + prevDimension = player.dimension + foundBlockMap.clear() + } + if (blockSearch) { + if (!(hideF1 && mc.gameSettings.hideGUI)) { + blockRenderer.render(false) + } + } + if (entitySearch) { + if (!(hideF1 && mc.gameSettings.hideGUI)) { + entityRenderer.render(false) + } } } - } - private fun SafeClientEvent.updateRenderer() { - defaultScope.launch { - val posMap = TreeMap>() - - coroutineScope { - launch { - updateAlpha() + safeListener { + if (blockRenderUpdateJob == null || blockRenderUpdateJob?.isCompleted == true) { + blockRenderUpdateJob = defaultScope.launch { + blockRenderUpdate() } - launch { - val eyePos = player.getPositionEyes(1f) - getBlockPosList(eyePos, posMap) + } + if (entityRenderUpdateJob == null || entityRenderUpdateJob?.isCompleted == true) { + entityRenderUpdateJob = defaultScope.launch { + searchLoadedEntities() } } + } - val renderList = ArrayList>() - val sides = GeometryMasks.Quad.ALL + safeListener { + // We avoid listening to SPacketChunkData directly here as even on PostReceive the chunk is not always + // fully loaded into the world. Chunk load is handled on a separate thread in mc code. + // i.e. world.getChunk(x, z) can and will return an empty chunk in the packet event + defaultScope.launch { + findBlocksInChunk(it.chunk) + .forEach { block -> foundBlockMap[block.first] = block.second } + } + } - for ((index, pair) in posMap.values.withIndex()) { - if (index >= maximumBlocks) break - val bb = pair.second.getSelectedBoundingBox(world, pair.first) - val color = getBlockColor(pair.first, pair.second) + safeListener { + when (it.packet) { + is SPacketMultiBlockChange -> { + it.packet.changedBlocks + .forEach { changedBlock -> handleBlockChange(changedBlock.pos, changedBlock.blockState) } + } - renderList.add(Triple(bb, color, sides)) + is SPacketBlockChange -> { + handleBlockChange(it.packet.blockPosition, it.packet.getBlockState()) + } } + } - renderer.replaceAll(renderList) + safeListener { + blockRenderer.clear() + entityRenderer.clear() + foundBlockMap.clear() } } - private fun updateAlpha() { - renderer.aFilled = if (filled) aFilled else 0 - renderer.aOutline = if (outline) aOutline else 0 - renderer.aTracer = if (tracer) aTracer else 0 - renderer.thickness = thickness + private fun blockSearchListUpdateListener(newBool: Boolean) { + foundBlockMap.entries + .filterNot { blockSearchList.contains(it.value.block.registryName.toString()) } + .forEach { foundBlockMap.remove(it.key) } + if (newBool) runSafe { searchAllLoadedChunks() } + } + + private fun SafeClientEvent.searchLoadedEntities() { + val renderList = world.loadedEntityList + .asSequence() + .filter { + EntityList.getKey(it)?.path?.let { entityName -> + entitySearchList.contains(entityName) + } ?: false + } + .filter { + EntityList.getKey(it)?.path?.let { entityName -> + entitySearchDimensionFilter.value.find { dimFilter -> dimFilter.searchKey == entityName }?.dim + }?.contains(player.dimension) ?: true + } + .sortedBy { + it.distanceTo(player.getPositionEyes(1f)) + } + .take(maximumEntities) + .filter { + it.distanceTo(player.getPositionEyes(1f)) < range + } + .toMutableList() + entityRenderer.clear() + renderList.forEach { entityRenderer.add(it, entitySearchColor) } } - private suspend fun SafeClientEvent.getBlockPosList( - eyePos: Vec3d, - map: MutableMap> - ) { + private fun SafeClientEvent.searchAllLoadedChunks() { val renderDist = mc.gameSettings.renderDistanceChunks val playerChunkPos = ChunkPos(player.position) val chunkPos1 = ChunkPos(playerChunkPos.x - renderDist, playerChunkPos.z - renderDist) val chunkPos2 = ChunkPos(playerChunkPos.x + renderDist, playerChunkPos.z + renderDist) - coroutineScope { - for (x in chunkPos1.x..chunkPos2.x) for (z in chunkPos1.z..chunkPos2.z) { - val chunk = world.getChunk(x, z) - if (!chunk.isLoaded) continue - if (player.distanceTo(chunk.pos) > range + 16) continue + if (blockSearchJob?.isActive != true) { + blockSearchJob = defaultScope.launch { + for (x in chunkPos1.x..chunkPos2.x) for (z in chunkPos1.z..chunkPos2.z) { + if (!isActive) return@launch + runSafe { + val chunk = world.getChunk(x, z) + if (!chunk.isLoaded) return@runSafe - launch { - findBlocksInChunk(chunk, eyePos, map) + findBlocksInChunk(chunk).forEach { pair -> + foundBlockMap[pair.first] = pair.second + } + } } - delay(1L) } } } - private fun findBlocksInChunk(chunk: Chunk, eyePos: Vec3d, map: MutableMap>) { + private fun SafeClientEvent.handleBlockChange(pos: BlockPos, state: IBlockState) { + if (searchQuery(state, pos)) { + foundBlockMap[pos] = state + } else { + foundBlockMap.remove(pos) + } + } + + private fun SafeClientEvent.blockRenderUpdate() { + updateAlpha() + val playerPos = player.position + // unload rendering on block pos > range + foundBlockMap + .filter { + playerPos.distanceTo(it.key) > max(mc.gameSettings.renderDistanceChunks * 16, range) + } + .map { it.key } + .forEach { foundBlockMap.remove(it) } + + val renderList = foundBlockMap + .filter { + blockSearchDimensionFilter.value + .find { + dimFilter -> dimFilter.searchKey == it.value.block.registryName.toString() + }?.dim?.contains(player.dimension) ?: true + } + .map { + player.getPositionEyes(1f).distanceTo(it.key) to it.key + } + .filter { it.first < range } + .take(maximumBlocks) + .flatMap { pair -> + foundBlockMap[pair.second]?.let { bb -> + return@flatMap listOf( + Triple(bb.getSelectedBoundingBox(world, pair.second), + getBlockColor(pair.second, bb), + GeometryMasks.Quad.ALL + ) + ) + } ?: run { + return@flatMap emptyList() + } + } + .toMutableList() + + blockRenderer.replaceAll(renderList) + } + + private fun updateAlpha() { + blockRenderer.aFilled = if (filled) aFilled else 0 + blockRenderer.aOutline = if (outline) aOutline else 0 + blockRenderer.aTracer = if (tracer) aTracer else 0 + blockRenderer.thickness = thickness + entityRenderer.aFilled = if (filled) aFilled else 0 + entityRenderer.aOutline = if (outline) aOutline else 0 + entityRenderer.aTracer = if (tracer) aTracer else 0 + entityRenderer.thickness = thickness + } + + private fun SafeClientEvent.findBlocksInChunk(chunk: Chunk): ArrayList> { val yRange = yRangeTop..yRangeBottom val xRange = (chunk.x shl 4)..(chunk.x shl 4) + 15 val zRange = (chunk.z shl 4)..(chunk.z shl 4) + 15 + val blocks: ArrayList> = ArrayList() for (y in yRange) for (x in xRange) for (z in zRange) { val pos = BlockPos(x, y, z) val blockState = chunk.getBlockState(pos) - val block = blockState.block - - if (block == Blocks.AIR) continue - if (!searchList.contains(block.registryName.toString())) continue + if (searchQuery(blockState, pos)) blocks.add(pos to blockState) + } + return blocks + } - val dist = eyePos.distanceTo(pos) - if (dist > range) continue + private fun SafeClientEvent.searchQuery(state: IBlockState, pos: BlockPos): Boolean { + val block = state.block + if (block == Blocks.AIR) return false + return (blockSearchList.contains(block.registryName.toString()) + && blockSearchDimensionFilter.value.find { dimFilter -> + dimFilter.searchKey == block.registryName.toString() + }?.dim?.contains(player.dimension) ?: true) + || isIllegalBedrock(state, pos) + || isIllegalWater(state) + } - synchronized(map) { - map[dist] = (pos to blockState) + private fun SafeClientEvent.isIllegalBedrock(state: IBlockState, pos: BlockPos): Boolean { + if (!illegalBedrock.value) return false + if (state.block != Blocks.BEDROCK) return false + return when (player.dimension) { + 0 -> { + pos.y >= 5 + } + -1 -> { + pos.y in 5..122 + } + else -> { + false } } } + private fun SafeClientEvent.isIllegalWater(state: IBlockState): Boolean { + if (!illegalNetherWater.value) return false + return player.dimension == -1 && state.isWater + } + private fun SafeClientEvent.getBlockColor(pos: BlockPos, blockState: IBlockState): ColorHolder { val block = blockState.block - - return if (!customColors) { - if (block == Blocks.PORTAL) { - ColorHolder(82, 49, 153) - } else { - val colorInt = blockState.getMapColor(world, pos).colorValue - ColorHolder((colorInt shr 16), (colorInt shr 8 and 255), (colorInt and 255)) + return if (autoBlockColor) { + when (block) { + Blocks.PORTAL -> { + ColorHolder(82, 49, 153) + } + is BlockShulkerBox -> { + val colorInt = block.color.colorValue + ColorHolder((colorInt shr 16), (colorInt shr 8 and 255), (colorInt and 255)) + } + is BlockEnderChest -> { + ColorHolder(64, 49, 114) + } + else -> { + val colorInt = blockState.getMapColor(world, pos).colorValue + ColorHolder((colorInt shr 16), (colorInt shr 8 and 255), (colorInt and 255)) + } } } else { - customColor + customBlockColor + } + } + + data class DimensionFilter(val searchKey: String, val dim: LinkedHashSet) { + override fun toString(): String { + return "$searchKey -> $dim" } } diff --git a/src/main/kotlin/com/lambda/client/module/modules/render/Xray.kt b/src/main/kotlin/com/lambda/client/module/modules/render/Xray.kt index f8cede12d..5fa24ecec 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/render/Xray.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/render/Xray.kt @@ -14,7 +14,7 @@ object Xray : Module( ) { private val defaultVisibleList = linkedSetOf("minecraft:diamond_ore", "minecraft:iron_ore", "minecraft:gold_ore", "minecraft:portal", "minecraft:cobblestone") - val visibleList = setting(CollectionSetting("Visible List", defaultVisibleList, { false })) + val visibleList = setting(CollectionSetting("Visible List", defaultVisibleList, String::class.java, { false })) @JvmStatic fun shouldReplace(state: IBlockState): Boolean { diff --git a/src/main/kotlin/com/lambda/client/setting/settings/impl/collection/CollectionSetting.kt b/src/main/kotlin/com/lambda/client/setting/settings/impl/collection/CollectionSetting.kt index d621471d4..372635c21 100644 --- a/src/main/kotlin/com/lambda/client/setting/settings/impl/collection/CollectionSetting.kt +++ b/src/main/kotlin/com/lambda/client/setting/settings/impl/collection/CollectionSetting.kt @@ -7,6 +7,7 @@ import com.lambda.client.setting.settings.ImmutableSetting class CollectionSetting>( name: String, override val value: T, + entryType: Class, visibility: () -> Boolean = { true }, description: String = "", unit: String = "" @@ -14,7 +15,7 @@ class CollectionSetting>( override val defaultValue: T = valueClass.newInstance() private val lockObject = Any() - private val type = TypeToken.getArray(value.first().javaClass).type + private val type = TypeToken.getArray(entryType).type val editListeners = ArrayList<() -> Unit>() init { @@ -32,6 +33,7 @@ class CollectionSetting>( synchronized(lockObject) { value.clear() value.addAll(defaultValue) + editListeners.forEach { it.invoke() } } } diff --git a/src/main/kotlin/com/lambda/client/util/items/Block.kt b/src/main/kotlin/com/lambda/client/util/items/Block.kt index cd9184b88..942882de5 100644 --- a/src/main/kotlin/com/lambda/client/util/items/Block.kt +++ b/src/main/kotlin/com/lambda/client/util/items/Block.kt @@ -23,6 +23,11 @@ val shulkerList: Set = hashSetOf( Blocks.BLACK_SHULKER_BOX ) +val signsList: Set = hashSetOf( + Blocks.WALL_SIGN, + Blocks.STANDING_SIGN +) + val blockBlacklist: Set = hashSetOf( Blocks.ANVIL, Blocks.BEACON, diff --git a/src/main/resources/mixins.lambda.json b/src/main/resources/mixins.lambda.json index 13f8ec477..464f846b0 100644 --- a/src/main/resources/mixins.lambda.json +++ b/src/main/resources/mixins.lambda.json @@ -51,6 +51,7 @@ "gui.MixinGuiNewChat", "gui.MixinGuiPlayerTabOverlay", "gui.MixinGuiScreen", + "network.MixinNetHandlerPlayClient", "network.MixinNetworkManager", "optifine.MixinConfig", "player.MixinEntityPlayer",