diff --git a/build.gradle b/build.gradle index 87946da5d..b2649727e 100644 --- a/build.gradle +++ b/build.gradle @@ -120,6 +120,16 @@ dependencies { implementation configurations.jarLibs } +sourceSets { + schematica_api { + compileClasspath += main.compileClasspath + } + + main { + compileClasspath += schematica_api.output + } +} + mixin { defaultObfuscationEnv 'searge' sourceSets { diff --git a/src/main/kotlin/com/lambda/client/activity/Activity.kt b/src/main/kotlin/com/lambda/client/activity/Activity.kt new file mode 100644 index 000000000..9f6de57d2 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/Activity.kt @@ -0,0 +1,357 @@ +package com.lambda.client.activity + +import com.lambda.client.LambdaMod +import com.lambda.client.activity.activities.construction.core.BuildStructure +import com.lambda.client.activity.types.AttemptActivity.Companion.checkAttempt +import com.lambda.client.activity.types.BuildActivity +import com.lambda.client.activity.types.DelayedActivity +import com.lambda.client.activity.types.DelayedActivity.Companion.checkDelayed +import com.lambda.client.activity.types.LoopWhileActivity.Companion.checkLoopingUntil +import com.lambda.client.activity.types.RenderAABBActivity +import com.lambda.client.activity.types.RenderAABBActivity.Companion.checkAABBRender +import com.lambda.client.activity.types.RepeatingActivity +import com.lambda.client.activity.types.RepeatingActivity.Companion.checkRepeat +import com.lambda.client.activity.types.RotatingActivity.Companion.checkRotating +import com.lambda.client.activity.types.TimeoutActivity.Companion.checkTimeout +import com.lambda.client.event.LambdaEventBus +import com.lambda.client.event.ListenerManager +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.gui.hudgui.elements.client.ActivityManagerHud +import com.lambda.client.gui.hudgui.elements.client.ActivityManagerHud.maxEntries +import com.lambda.client.manager.managers.ActivityManager +import com.lambda.client.manager.managers.ActivityManager.MAX_DEPTH +import com.lambda.client.module.AbstractModule +import com.lambda.client.util.BaritoneUtils +import com.lambda.client.util.color.ColorHolder +import com.lambda.client.util.graphics.font.TextComponent +import com.lambda.client.util.text.MessageSendHelper +import com.lambda.client.util.text.capitalize +import net.minecraft.entity.Entity +import net.minecraft.item.ItemBlock +import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import org.apache.commons.lang3.time.DurationFormatUtils + +abstract class Activity { + val subActivities = ArrayDeque() + var status = Status.UNINITIALIZED + private var creationTime = 0L + var parent: Activity? = null + var owner: AbstractModule? = null + var depth = 0 + + open fun SafeClientEvent.onInitialize() {} + + open fun SafeClientEvent.onChildInitialize(childActivity: Activity) {} + + open fun SafeClientEvent.onSuccess() {} + + open fun SafeClientEvent.onChildSuccess(childActivity: Activity) {} + + /* Return true to catch the exception */ + open fun SafeClientEvent.onFailure(exception: Exception): Boolean = false + + open fun SafeClientEvent.onChildFailure(childActivities: ArrayDeque, childException: Exception): Boolean = false + + open fun SafeClientEvent.onCancel() {} + + open fun addExtraInfo( + textComponent: TextComponent, + primaryColor: ColorHolder, + secondaryColor: ColorHolder + ) { + } + + open fun getCurrentActivity(): Activity { + subActivities.firstOrNull()?.let { + with(it) { + return getCurrentActivity() + } + } ?: return this@Activity + } + + val activityName get() = this.javaClass.simpleName ?: "Activity" + + val age get() = if (creationTime != 0L) System.currentTimeMillis() - creationTime else 0L + + val allSubActivities: List + get() = run { + val activities = mutableListOf() + + parent?.let { + activities.add(this) + } + + activities.addAll(subActivities.flatMap { it.allSubActivities }) + + activities + } + + val hasNoSubActivities get() = subActivities.isEmpty() + + fun SafeClientEvent.updateActivity() { + when (status) { + Status.UNINITIALIZED -> { + initialize() + } + + Status.RUNNING -> { + if (!ListenerManager.listenerMap.containsKey(this@Activity) + && hasNoSubActivities + && this@Activity !is DelayedActivity + ) success() + } + } + } + + fun SafeClientEvent.updateTypesOnTick(activity: Activity) { + checkTimeout(activity) + checkDelayed(activity) + checkRotating(activity) + checkAABBRender() + } + + fun SafeClientEvent.initialize() { + val activity = this@Activity + + status = Status.RUNNING + creationTime = System.currentTimeMillis() + onInitialize() + + LambdaEventBus.subscribe(activity) + +// if (!owner.isRoot) { +// with(owner) { +// onChildInitialize(activity) +// } +// } + + checkRotating(activity) + +// LambdaMod.LOG.info("${System.currentTimeMillis()} Initialized $name ${System.currentTimeMillis() - ActivityManager.lastActivity.creationTime}ms after last activity creation") + } + + fun SafeClientEvent.success() { + val activity = this@Activity + + LambdaEventBus.unsubscribe(activity) + if (activity !is RepeatingActivity) ListenerManager.unregister(activity) + + if (activity is RenderAABBActivity) { + activity.aabbCompounds.clear() + } + + parent?.let { + with(it) { + onChildSuccess(activity) + subActivities.remove(activity) + } + } + + onSuccess() + checkRepeat(activity) + checkLoopingUntil(activity) + + if (this@Activity !is BuildActivity) BaritoneUtils.primary?.pathingBehavior?.cancelEverything() + +// LambdaMod.LOG.info("${System.currentTimeMillis()} Finalized $name after ${System.currentTimeMillis() - creationTime}ms") +// MessageSendHelper.sendRawChatMessage("$name took ${age}ms") + } + + fun SafeClientEvent.failedWith(exception: Exception) { + val activity = this@Activity + + LambdaMod.LOG.warn("Exception in $activityName: ${exception.message}") + + if (onFailure(exception)) return + + parent?.let { + with(it) { + if (childFailure(ArrayDeque(listOf(activity)), exception)) return + } + } + + if (checkAttempt(activity, exception)) return + + MessageSendHelper.sendErrorMessage("Fatal Exception in $activityName: ${exception.message}") + + with(ActivityManager) { + cancel() + } + } + + fun SafeClientEvent.cancel() { + val activity = this@Activity + + onCancel() + + BaritoneUtils.primary?.pathingBehavior?.cancelEverything() + + subActivities.toList().forEach { + with(it) { + cancel() + } + } + + parent?.let { + with(it) { + LambdaEventBus.unsubscribe(activity) + ListenerManager.unregister(activity) + subActivities.remove(activity) + } + } + } + + private fun SafeClientEvent.childFailure(childActivities: ArrayDeque, childException: Exception): Boolean { + if (onChildFailure(childActivities, childException)) return true + + if (onFailure(childException)) return true + + if (this@Activity is ActivityManager) { + LambdaMod.LOG.warn("Traceback: ${childException.javaClass.simpleName}: ${childException.message}\n ${childActivities.joinToString(separator = "\n ") { it.toString() }}") + return false + } + + childActivities.add(this@Activity) + parent?.let { + with(it) { + childFailure(childActivities, childException) + } + } + + return false + } + + fun Activity.addSubActivities(activities: List, subscribe: Boolean = false, module: AbstractModule? = null) { + if (activities.isEmpty()) return + + if (depth > MAX_DEPTH) { + MessageSendHelper.sendErrorMessage("Activity depth exceeded $MAX_DEPTH!") + ActivityManager.reset() + return + } + + activities.forEach { activity -> + activity.parent = this + activity.owner = module + activity.depth = depth + 1 + if (subscribe) LambdaEventBus.subscribe(activity) + } + + subActivities.addAll(activities) + +// LambdaMod.LOG.info("${System.currentTimeMillis()} Added ${activities.size} sub activities to $name") + } + + fun Activity.addSubActivities(vararg activities: Activity, subscribe: Boolean = false) { + addSubActivities(activities.toList(), subscribe) + } + + fun AbstractModule.addSubActivities(vararg activities: Activity, subscribe: Boolean = false) { + addSubActivities(activities.toList(), subscribe, this) + } + + enum class Status { + RUNNING, + UNINITIALIZED + } + + fun appendInfo(textComponent: TextComponent, primaryColor: ColorHolder, secondaryColor: ColorHolder, details: Boolean) { + if (this !is ActivityManager) { + ListenerManager.listenerMap[this@Activity]?.let { + textComponent.add("SYNC", primaryColor) + } + ListenerManager.asyncListenerMap[this@Activity]?.let { + textComponent.add("ASYNC", primaryColor) + } + + owner?.let { + textComponent.add("Module", secondaryColor) + textComponent.add(it.name, primaryColor) + } + + textComponent.add("Name", secondaryColor) + textComponent.add("${javaClass.simpleName} ", primaryColor) + + if (this is BuildActivity) { + textComponent.add("Context", secondaryColor) + textComponent.add(context.name, primaryColor) + textComponent.add("Availability", secondaryColor) + textComponent.add(availability.name, primaryColor) + textComponent.add("Type", secondaryColor) + textComponent.add(type.name, primaryColor) + } + + textComponent.add("State", secondaryColor) + textComponent.add(status.name, primaryColor) + + if (status == Status.RUNNING) { + textComponent.add("Runtime", secondaryColor) + textComponent.add(DurationFormatUtils.formatDuration(age, "HH:mm:ss,SSS"), primaryColor) + } + + textComponent.add("Hash", secondaryColor) + textComponent.add(hashCode().toString(), primaryColor) + + if (details) { + this::class.java.declaredFields.forEach { field -> + field.isAccessible = true + val name = field.name + val value = field.get(this) ?: return@forEach + + if (ActivityManagerHud.anonymize && (value is BlockPos || value is Vec3d || value is Entity || value is AxisAlignedBB)) return@forEach + + textComponent.add(name.capitalize(), primaryColor) + + when (value) { + is ItemBlock -> { + textComponent.add(value.block.localizedName, secondaryColor) + } + + else -> { + textComponent.add(value.toString(), secondaryColor) + } + } + } + } + } + + addExtraInfo(textComponent, primaryColor, secondaryColor) + textComponent.addLine("") + + val activities = if (this is BuildStructure) { + subActivities + .filterIsInstance() + .sortedWith(buildComparator()) + .filterIsInstance() + } else { + subActivities + } + + activities.take(maxEntries).forEach { + repeat(depth) { + textComponent.add(" ") + } + it.appendInfo(textComponent, primaryColor, secondaryColor, details) + } + if (activities.size > maxEntries) { + repeat(depth) { + textComponent.add(" ") + } + textComponent.addLine("And ${activities.size - maxEntries} more...", primaryColor) + } + } + + override fun toString(): String { +// val properties = this::class.java.declaredFields.joinToString(separator = ", ", prefix = ", ") { +// it.isAccessible = true +// val name = it.name +// val value = it.get(this) +// "$name=$value" +// } + +// return "$activityName: [State=$activityStatus, Runtime=${DurationFormatUtils.formatDuration(age, "HH:mm:ss,SSS")}, SubActivities=${subActivities.size}$properties]" + return "$activityName: [State=$status, Runtime=${DurationFormatUtils.formatDuration(age, "HH:mm:ss,SSS")}, SubActivities=${subActivities.size}]" + } +} diff --git a/src/main/kotlin/com/lambda/client/activity/ActivityUtils.kt b/src/main/kotlin/com/lambda/client/activity/ActivityUtils.kt new file mode 100644 index 000000000..c3fa5e638 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/ActivityUtils.kt @@ -0,0 +1,46 @@ +package com.lambda.client.activity + +import com.lambda.client.activity.activities.storage.core.ContainerWindowTransaction +import com.lambda.client.util.items.getSlots +import net.minecraft.inventory.* +import net.minecraft.item.ItemShulkerBox +import net.minecraft.item.ItemStack +import net.minecraft.util.NonNullList + +fun getShulkerInventory(stack: ItemStack): NonNullList? { + if (stack.item !is ItemShulkerBox) return null + + val shulkerInventory = NonNullList.withSize(27, ItemStack.EMPTY) + + stack.tagCompound?.getCompoundTag("BlockEntityTag")?.let { + if (it.hasKey("Items", 9)) { + ItemStackHelper.loadAllItems(it, shulkerInventory) + return shulkerInventory + } + } + + return shulkerInventory +} + +/** + * Get the slots of a container. + * The first list contains the container slots, the second list contains the player slots. + */ +val Container.seperatedSlots: Pair, List> + get() = when (this) { + is ContainerShulkerBox -> { + getSlots(0..26) to getSlots(27..62) + } + + is ContainerChest -> { + if (inventory.size == 62) { + getSlots(0..26) to getSlots(27..62) + } else { + getSlots(0..53) to getSlots(54..89) + } + } + + else -> { + throw ContainerWindowTransaction.ContainerNotKnownException(this) + } + } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/construction/BuildSchematic.kt b/src/main/kotlin/com/lambda/client/activity/activities/construction/BuildSchematic.kt new file mode 100644 index 000000000..9afe02dc7 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/construction/BuildSchematic.kt @@ -0,0 +1,65 @@ +package com.lambda.client.activity.activities.construction + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.construction.core.BuildStructure +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.math.Direction +import com.lambda.client.util.schematic.LambdaSchematicaHelper +import com.lambda.client.util.schematic.Schematic +import com.lambda.client.util.text.MessageSendHelper +import net.minecraft.block.state.IBlockState +import net.minecraft.util.math.BlockPos + +class BuildSchematic( + private val schematic: Schematic, + private val inLayers: Boolean = true, + private val direction: Direction = Direction.NORTH, + private val offsetMove: BlockPos = BlockPos.ORIGIN, +) : Activity() { + override fun SafeClientEvent.onInitialize() { + if (!LambdaSchematicaHelper.isSchematicaPresent) { + failedWith(SchematicNotPresentException()) + return + } + + val structure = mutableMapOf() + + for (y in schematic.getOrigin().y..schematic.getOrigin().y + schematic.heightY()) { + val layerStructure = mutableMapOf() + + for (x in schematic.getOrigin().x..schematic.getOrigin().x + schematic.widthX()) { + for (z in schematic.getOrigin().z..schematic.getOrigin().z + schematic.lengthZ()) { + val blockPos = BlockPos(x, y, z) + if (!schematic.inSchematic(blockPos)) continue + layerStructure[blockPos] = schematic.desiredState(blockPos) + } + } + + structure.putAll(layerStructure) + + if (!inLayers) continue + + addSubActivities( + BuildStructure( + layerStructure, + direction, + offsetMove + ) + ) + } + + MessageSendHelper.sendChatMessage("$activityName Building ${structure.size} blocks") + + if (inLayers) return + + addSubActivities( + BuildStructure( + structure, + direction, + offsetMove + ) + ) + } + + private class SchematicNotPresentException : Exception("Schematica is not present") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/construction/ClearArea.kt b/src/main/kotlin/com/lambda/client/activity/activities/construction/ClearArea.kt new file mode 100644 index 000000000..ca4afa9ad --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/construction/ClearArea.kt @@ -0,0 +1,77 @@ +package com.lambda.client.activity.activities.construction + +import baritone.api.pathing.goals.GoalBlock +import baritone.api.pathing.goals.GoalXZ +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.construction.core.BuildStructure +import com.lambda.client.activity.activities.storage.types.Area +import com.lambda.client.activity.activities.travel.CustomGoal +import com.lambda.client.activity.types.RenderAABBActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.EntityUtils.flooredPosition +import com.lambda.client.util.color.ColorHolder +import com.lambda.client.util.math.CoordinateConverter.asString +import com.lambda.client.util.text.MessageSendHelper +import net.minecraft.block.state.IBlockState +import net.minecraft.init.Blocks +import net.minecraft.util.EnumFacing +import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.util.math.BlockPos + +class ClearArea( + private val area: Area, + private val layerSize: Int = 1, + private val sliceSize: Int = 1, + private val sliceDirection: EnumFacing = EnumFacing.NORTH, + private val collectAll: Boolean = false, + override val aabbCompounds: MutableSet = mutableSetOf() +) : RenderAABBActivity, Activity() { + + init { + RenderAABBActivity.Companion.RenderAABB( + AxisAlignedBB( + area.minX.toDouble(), area.minY.toDouble(), area.minZ.toDouble(), + (area.maxX + 1).toDouble(), (area.maxY + 1).toDouble(), (area.maxZ + 1).toDouble() + ), + ColorHolder(245, 66, 66) + ).also { aabbCompounds.add(it) } + } + + override fun SafeClientEvent.onInitialize() { + if (player.flooredPosition !in area.containedBlocks) { + val highestNonAirBlock = world.getTopSolidOrLiquidBlock(area.center) + + MessageSendHelper.sendWarningMessage("Player is not in the area $area! Moving to closest position (${highestNonAirBlock.asString()})...") + addSubActivities(CustomGoal(GoalBlock(highestNonAirBlock), + inGoal = { blockPos -> area.containedBlocks.contains(blockPos) }, timeout = 999999L)) + status = Status.UNINITIALIZED + return + } + + val layers = (area.minY..area.maxY).reversed() + val structure = mutableMapOf() + + layers.forEach { y -> + if (y !in 0..world.actualHeight) return@forEach + + (area.minX..area.maxX).forEach { x -> + (area.minZ..area.maxZ).forEach { z -> + structure[BlockPos(x, y, z)] = Blocks.AIR.defaultState + } + } + + if (y.mod(layerSize) == 0 || y == layers.last) { + addSubActivities( + BuildStructure(structure.toMap(), collectAll = collectAll, allowBreakDescend = true) + ) + structure.clear() + } + } + } + + override fun SafeClientEvent.onChildSuccess(childActivity: Activity) { + if (childActivity !is CustomGoal) return + + status = Status.UNINITIALIZED + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/construction/Graffiti.kt b/src/main/kotlin/com/lambda/client/activity/activities/construction/Graffiti.kt new file mode 100644 index 000000000..f1f716b29 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/construction/Graffiti.kt @@ -0,0 +1,48 @@ +package com.lambda.client.activity.activities.construction + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.interaction.AttachItemFrame +import com.lambda.client.activity.activities.interaction.AttachMap +import com.lambda.client.activity.types.LoopWhileActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.module.modules.misc.Graffiti +import com.lambda.client.util.math.VectorUtils +import com.lambda.client.util.math.VectorUtils.distanceTo +import com.lambda.client.util.world.getNeighbour +import net.minecraft.entity.item.EntityItemFrame +import net.minecraft.util.EnumFacing + +class Graffiti( + private val mapID: Int, + override val loopWhile: SafeClientEvent.() -> Boolean = { Graffiti.isEnabled }, + override var currentLoops: Int = 0 +) : LoopWhileActivity, Activity() { + override fun SafeClientEvent.onInitialize() { + val range = 4.95f + + // 1. Check for missing maps in item frames + world.loadedEntityList + .filterIsInstance() + .minByOrNull { player.distanceTo(it.positionVector) } + ?.let { + if (player.distanceTo(it.positionVector) > range) return + + addSubActivities( + AttachMap(it, mapID) + ) + return + } + + // 2. Check for surface to place item frames on and place it + VectorUtils.getBlockPosInSphere(player.positionVector, range) + .mapNotNull { getNeighbour(it, 1, range, true, EnumFacing.HORIZONTALS) } + .minByOrNull { player.distanceTo(it.hitVec) } + ?.let { + if (player.distanceTo(it.hitVec) > range) return + + addSubActivities( + AttachItemFrame(it.pos, it.side) + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/construction/SurroundWithObsidian.kt b/src/main/kotlin/com/lambda/client/activity/activities/construction/SurroundWithObsidian.kt new file mode 100644 index 000000000..74fa08abf --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/construction/SurroundWithObsidian.kt @@ -0,0 +1,33 @@ +package com.lambda.client.activity.activities.construction + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.construction.core.BuildStructure +import com.lambda.client.activity.types.LoopWhileActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.EntityUtils.flooredPosition +import net.minecraft.block.state.IBlockState +import net.minecraft.init.Blocks +import net.minecraft.util.math.BlockPos + +class SurroundWithObsidian( + private val originPos: BlockPos, + override val loopWhile: SafeClientEvent.() -> Boolean = { + originPos == player.flooredPosition + }, + override var currentLoops: Int = 0 +) : LoopWhileActivity, Activity() { + override fun SafeClientEvent.onInitialize() { + val material = Blocks.OBSIDIAN.defaultState + + val structure = mutableMapOf( + originPos.north() to material, + originPos.south() to material, + originPos.east() to material, + originPos.west() to material, + ) + + addSubActivities( + BuildStructure(structure) + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/construction/core/BreakBlock.kt b/src/main/kotlin/com/lambda/client/activity/activities/construction/core/BreakBlock.kt new file mode 100644 index 000000000..29fd41c05 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/construction/core/BreakBlock.kt @@ -0,0 +1,439 @@ +package com.lambda.client.activity.activities.construction.core + +import baritone.api.BaritoneAPI +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.inventory.AcquireItemInActiveHand +import com.lambda.client.activity.activities.inventory.core.SwapOrSwitchToSlot +import com.lambda.client.activity.activities.storage.types.StackSelection +import com.lambda.client.activity.activities.travel.CollectDrops +import com.lambda.client.activity.types.* +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.event.events.PacketEvent +import com.lambda.client.gui.hudgui.elements.client.ActivityManagerHud +import com.lambda.client.manager.managers.ActivityManager +import com.lambda.client.mixin.extension.blockHitDelay +import com.lambda.client.module.modules.client.BuildTools +import com.lambda.client.util.EntityUtils.flooredPosition +import com.lambda.client.util.items.inventorySlots +import com.lambda.client.util.math.CoordinateConverter.asString +import com.lambda.client.util.math.RotationUtils.getRotationTo +import com.lambda.client.util.math.Vec2f +import com.lambda.client.util.math.VectorUtils.distanceTo +import com.lambda.client.util.math.VectorUtils.toVec3dCenter +import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.threads.onMainThreadSafe +import com.lambda.client.util.threads.runSafe +import com.lambda.client.util.threads.safeListener +import com.lambda.client.util.world.getHitVec +import com.lambda.client.util.world.getMiningSide +import com.lambda.client.util.world.isLiquid +import kotlinx.coroutines.launch +import net.minecraft.enchantment.EnchantmentHelper +import net.minecraft.init.Blocks +import net.minecraft.init.Enchantments +import net.minecraft.init.Items +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraft.network.play.server.SPacketBlockChange +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumHand +import net.minecraft.util.math.BlockPos +import net.minecraftforge.fml.common.gameevent.TickEvent +import java.util.* +import kotlin.math.ceil +import kotlin.properties.Delegates + +class BreakBlock( + val blockPos: BlockPos, + private val collectDrops: Boolean = false, + private val minCollectAmount: Int = 1, + private val forceSilk: Boolean = false, + private val forceNoSilk: Boolean = false, + private val forceFortune: Boolean = false, + private val forceNoFortune: Boolean = false, + private val ignoreIgnored: Boolean = false, + override var timeout: Long = Long.MAX_VALUE, // ToDo: Reset timeouted breaks blockstates + override val maxAttempts: Int = 20, + override var usedAttempts: Int = 0, + override val aabbCompounds: MutableSet = mutableSetOf(), + override val overlayTexts: MutableSet = mutableSetOf(), + override var rotation: Vec2f? = null, + override var distance: Double = 1337.0, + override var exposedSides: Int = 0, +) : BuildActivity, TimeoutActivity, AttemptActivity, RotatingActivity, TimedActivity, RenderAABBActivity, RenderOverlayTextActivity, Activity() { + private var side: EnumFacing? = null + private var ticksNeeded = 0 + private var drops: Item = Items.AIR + + override var context: BuildActivity.Context by Delegates.observable(BuildActivity.Context.NONE) { _, old, new -> + if (old == new) return@observable + renderContextAABB.color = new.color + + renderContextOverlay.text = new.name + renderContextOverlay.color = new.color + } + + override var availability: BuildActivity.Availability by Delegates.observable(BuildActivity.Availability.NONE) { _, old, new -> + if (old == new) return@observable + renderAvailabilityAABB.color = new.color + + renderAvailabilityOverlay.text = new.name + renderAvailabilityOverlay.color = new.color + } + + override var type: BuildActivity.Type by Delegates.observable(BuildActivity.Type.BREAK_BLOCK) { _, old, new -> + if (old == new) return@observable + renderTypeAABB.color = new.color + + renderTypeOverlay.text = new.name + renderTypeOverlay.color = new.color + } + + private val renderContextAABB = RenderAABBActivity.Companion.RenderBlockPos( + blockPos, context.color + ).also { aabbCompounds.add(it) } + + private val renderContextOverlay = RenderOverlayTextActivity.Companion.RenderOverlayText( + context.name, context.color, blockPos.toVec3dCenter(), 0 + ).also { overlayTexts.add(it) } + + private val renderAvailabilityAABB = RenderAABBActivity.Companion.RenderBlockPos( + blockPos, availability.color + ).also { aabbCompounds.add(it) } + + private val renderAvailabilityOverlay = RenderOverlayTextActivity.Companion.RenderOverlayText( + availability.name, availability.color, blockPos.toVec3dCenter(), 1 + ).also { overlayTexts.add(it) } + + private val renderTypeAABB = RenderAABBActivity.Companion.RenderBlockPos( + blockPos, type.color + ).also { aabbCompounds.add(it) } + + private val renderTypeOverlay = RenderOverlayTextActivity.Companion.RenderOverlayText( + type.name, type.color, blockPos.toVec3dCenter(), 2 + ).also { overlayTexts.add(it) } + + override var earliestFinish: Long + get() = BuildTools.breakDelay.toLong() + set(_) {} + + init { + runSafe { + if (!world.worldBorder.contains(blockPos) || world.isOutsideBuildHeight(blockPos)) { + // ToDo: add support for placing blocks outside of world border + failedWith(BlockOutsideOfBoundsException(blockPos)) + return@runSafe + } + + updateState() // ToDo: check if needed + } + + safeListener { + if (it.phase != TickEvent.Phase.START) return@safeListener + + updateState() + + if (world.isAirBlock(blockPos)) { + finish() + return@safeListener + } + + if (subActivities.isNotEmpty() + || status == Status.UNINITIALIZED + ) return@safeListener + + resolveAvailability() + } + + safeListener { + if (it.packet !is SPacketBlockChange + || it.packet.blockPosition != blockPos + || it.packet.blockState.block != Blocks.AIR + ) return@safeListener + + defaultScope.launch { + onMainThreadSafe { + finish() + } + } + } + } + + override fun SafeClientEvent.onInitialize() { + context = BuildActivity.Context.NONE + availability = BuildActivity.Availability.NONE + + val currentState = world.getBlockState(blockPos) + + if (!ignoreIgnored && currentState.block in BuildTools.ignoredBlocks + || currentState.getBlockHardness(world, blockPos) < 0 + ) { + success() + return + } + + // ToDo: add silk touch support + drops = currentState.block.getItemDropped( + currentState, + Random(), + EnchantmentHelper.getEnchantmentLevel(Enchantments.FORTUNE, player.heldItemMainhand) + ) ?: Items.AIR + + updateState() + resolveAvailability() + } + + override fun SafeClientEvent.onCancel() { + if (context == BuildActivity.Context.IN_PROGRESS) playerController.resetBlockRemoving() + } + + private fun SafeClientEvent.finish() { +// if (!collectDrops || !autoPathing || drops == Items.AIR) { + if (!collectDrops || drops == Items.AIR) { + ActivityManagerHud.totalBlocksBroken++ + success() + return + } + + if (context == BuildActivity.Context.PICKUP) return + + context = BuildActivity.Context.PICKUP + + addSubActivities( + CollectDrops(drops, minAmount = minCollectAmount) + ) + } + + private fun SafeClientEvent.updateState() { + if (checkLiquids()) return + + updateProperties() + + getMiningSide(blockPos, BuildTools.maxReach)?.let { miningSide -> + val hitVec = getHitVec(blockPos, miningSide) + + /* prevent breaking the block the player is standing on */ + if (player.flooredPosition.down() == blockPos + && !world.getBlockState(blockPos.down()).isSideSolid(world, blockPos.down(), EnumFacing.UP) + ) { + availability = BuildActivity.Availability.BLOCKED_BY_PLAYER + distance = player.distanceTo(hitVec) + exposedSides = EnumFacing.HORIZONTALS.count { world.isAirBlock(blockPos.offset(it)) } + return + } + availability = BuildActivity.Availability.VALID + distance = player.distanceTo(hitVec) + exposedSides = EnumFacing.HORIZONTALS.count { world.isAirBlock(blockPos.offset(it)) } + side = miningSide + rotation = getRotationTo(hitVec) + return + } + + getMiningSide(blockPos)?.let { + availability = BuildActivity.Availability.NOT_IN_RANGE + distance = player.distanceTo(getHitVec(blockPos, it)) + exposedSides = EnumFacing.HORIZONTALS.count { world.isAirBlock(blockPos.offset(it)) } + } ?: run { + availability = BuildActivity.Availability.NOT_EXPOSED + distance = 1337.0 + exposedSides = EnumFacing.HORIZONTALS.count { world.isAirBlock(blockPos.offset(it)) } + } + + playerController.resetBlockRemoving() + side = null + rotation = null + } + + private fun SafeClientEvent.resolveAvailability() { + when (availability) { + BuildActivity.Availability.VALID -> { + tryBreak() + } + + BuildActivity.Availability.BLOCKED_BY_PLAYER, + BuildActivity.Availability.NOT_IN_RANGE -> { + // Wait for player move + timeout = Long.MAX_VALUE // ToDo: find a better way to do this + } + + BuildActivity.Availability.WRONG_ITEM_SELECTED -> { +// acquireOptimalTool() + } + + BuildActivity.Availability.NOT_EXPOSED, + BuildActivity.Availability.NEEDS_LIQUID_HANDLING -> { + // Wait for other tasks to finish + } + + else -> { + // Other cases should not happen + } + } + } + + private fun SafeClientEvent.tryBreak() { + if (ActivityManager.getCurrentActivity() != this@BreakBlock) return + if (!hasOptimalTool()) return + + side?.let { + // if baritone break/place is on while we're breaking it ourselves, we'll get stuck in a loop + // todo: option to use baritone break/place instead of ours + BaritoneAPI.getSettings().allowBreak.value = false + BaritoneAPI.getSettings().allowPlace.value = false + if (!world.isAirBlock(blockPos) && playerController.onPlayerDamageBlock(blockPos, it)) { + if ((ticksNeeded == 1 && player.onGround) || player.capabilities.isCreativeMode) { + playerController.blockHitDelay = 0 + context = BuildActivity.Context.PENDING + } else { + context = BuildActivity.Context.IN_PROGRESS + } + + mc.effectRenderer.addBlockHitEffects(blockPos, it) + player.swingArm(EnumHand.MAIN_HAND) + } + } + } + + // ToDo: 1. currentState.material.isToolNotRequired (if drop is needed it should check if tool has sufficient harvest level) + // ToDo: 2. ForgeHooks.canHarvestBlock(currentState.block, player, world, blockPos) + private fun SafeClientEvent.hasOptimalTool(): Boolean { + if (player.capabilities.isCreativeMode) return true + + val currentState = world.getBlockState(blockPos) + + if (world.isAirBlock(blockPos)) return false + + val currentDestroySpeed = player.heldItemMainhand.getDestroySpeed(currentState) + + player.inventorySlots.maxByOrNull { it.stack.getDestroySpeed(currentState) }?.let { + if (it.stack.getDestroySpeed(currentState) > currentDestroySpeed +// && it.stack != player.heldItemMainhand +// && (!getSilkDrop || EnchantmentHelper.getEnchantmentLevel(Enchantments.SILK_TOUCH, it.stack) > 0) + ) { + context = BuildActivity.Context.RESTOCK + + addSubActivities(SwapOrSwitchToSlot(it)) + return false + } + } + + val availableTools = mutableListOf() + + if (BuildTools.usePickaxe) availableTools.add(Items.DIAMOND_PICKAXE) + if (BuildTools.useShovel) availableTools.add(Items.DIAMOND_SHOVEL) + if (BuildTools.useAxe) availableTools.add(Items.DIAMOND_AXE) + if (BuildTools.useShears) availableTools.add(Items.SHEARS) + if (BuildTools.useSword) availableTools.add(Items.DIAMOND_SWORD) + + availableTools.maxByOrNull { tool -> + tool.getDestroySpeed(ItemStack(tool), currentState) + }?.let { tool -> + val selectedSpeed = tool.getDestroySpeed(ItemStack(tool), currentState) + + if (selectedSpeed > currentDestroySpeed) { + context = BuildActivity.Context.RESTOCK + + addSubActivities(AcquireItemInActiveHand( + StackSelection().apply { + selection = isItem(tool) and when { + forceSilk -> hasEnchantment(Enchantments.SILK_TOUCH) + forceNoSilk -> hasEnchantment(Enchantments.SILK_TOUCH).not() + forceFortune -> hasEnchantment(Enchantments.FORTUNE) + forceNoFortune -> hasEnchantment(Enchantments.FORTUNE).not() + else -> isItem() + } + } + )) + return false + } + + if (selectedSpeed == 1.0f + && player.heldItemMainhand.isItemStackDamageable + ) { + player.inventorySlots.firstOrNull { !it.stack.isItemStackDamageable }?.let { + context = BuildActivity.Context.RESTOCK + + addSubActivities(SwapOrSwitchToSlot(it)) + return false + } + } + } + + return true + } + + private fun SafeClientEvent.checkLiquids(): Boolean { + var foundLiquid = false + + if (world.getBlockState(blockPos).isLiquid) { + (parent as? BuildStructure)?.let { + with(it) { + addLiquidFill(blockPos) + } + } + availability = BuildActivity.Availability.NEEDS_LIQUID_HANDLING + + return true + } + + EnumFacing.entries + .filter { it != EnumFacing.DOWN } + .map { blockPos.offset(it) } + .filter { world.getBlockState(it).isLiquid } + .forEach { pos -> + (parent as? BuildStructure)?.let { + with(it) { + addLiquidFill(pos) + } + } + + foundLiquid = true + } + + if (foundLiquid) { + availability = BuildActivity.Availability.NEEDS_LIQUID_HANDLING + } + + return foundLiquid + } + + private fun SafeClientEvent.updateProperties() { + val currentState = world.getBlockState(blockPos) + + if (!ignoreIgnored && currentState.block in BuildTools.ignoredBlocks) { + success() + return + } + + ticksNeeded = ceil((1 / currentState + .getPlayerRelativeBlockHardness(player, world, blockPos)) * BuildTools.miningSpeedFactor).toInt() + + timeout = ticksNeeded * 50L + 100L + } + + override fun SafeClientEvent.onChildSuccess(childActivity: Activity) { + when (childActivity) { + is CollectDrops -> { + ActivityManagerHud.totalBlocksBroken++ + success() + } + + else -> { + status = Status.UNINITIALIZED + updateState() + } + } + } + + override fun SafeClientEvent.onFailure(exception: Exception): Boolean { + playerController.resetBlockRemoving() + side = null + rotation = null + return false + } + + class NoExposedSideFound : Exception("No exposed side found") + class BlockBreakingException : Exception("Block breaking failed") + class BlockOutsideOfBoundsException(blockPos: BlockPos) : Exception("Block at (${blockPos.asString()}) is outside of world") + class NoFillerMaterialFoundException : Exception("No filler material in inventory found") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/construction/core/BuildStructure.kt b/src/main/kotlin/com/lambda/client/activity/activities/construction/core/BuildStructure.kt new file mode 100644 index 000000000..50272b728 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/construction/core/BuildStructure.kt @@ -0,0 +1,324 @@ +package com.lambda.client.activity.activities.construction.core + +import baritone.api.pathing.goals.Goal +import baritone.api.pathing.goals.GoalBlock +import baritone.api.pathing.goals.GoalInverted +import baritone.api.pathing.goals.GoalXZ +import baritone.process.BuilderProcess.GoalAdjacent +import com.lambda.client.LambdaMod +import com.lambda.client.activity.Activity +import com.lambda.client.activity.types.BuildActivity +import com.lambda.client.activity.types.RenderAABBActivity +import com.lambda.client.activity.types.RepeatingActivity +import com.lambda.client.commons.extension.floorToInt +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.event.events.PacketEvent +import com.lambda.client.manager.managers.ActivityManager +import com.lambda.client.module.modules.client.BuildTools +import com.lambda.client.module.modules.client.BuildTools.autoPathing +import com.lambda.client.module.modules.misc.WorldEater +import com.lambda.client.util.BaritoneUtils +import com.lambda.client.util.EntityUtils.flooredPosition +import com.lambda.client.util.color.ColorHolder +import com.lambda.client.util.items.block +import com.lambda.client.util.items.filterByStack +import com.lambda.client.util.items.inventorySlots +import com.lambda.client.util.math.Direction +import com.lambda.client.util.math.VectorUtils.distanceTo +import com.lambda.client.util.math.VectorUtils.multiply +import com.lambda.client.util.threads.safeListener +import net.minecraft.block.BlockBush +import net.minecraft.block.state.IBlockState +import net.minecraft.entity.item.EntityItem +import net.minecraft.init.Blocks +import net.minecraft.item.ItemShulkerBox +import net.minecraft.network.play.server.SPacketBlockChange +import net.minecraft.network.play.server.SPacketMultiBlockChange +import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.util.math.BlockPos +import net.minecraftforge.event.world.BlockEvent +import net.minecraftforge.fml.common.gameevent.TickEvent +import kotlin.properties.Delegates + +class BuildStructure( + private val structure: Map, + private val direction: Direction = Direction.NORTH, + private val offsetMove: BlockPos = BlockPos.ORIGIN, + private val doPadding: Boolean = false, + private val collectAll: Boolean = false, + private val breakBushes: Boolean = false, + private val allowBreakDescend: Boolean = false, + override val maximumRepeats: Int = 1, + override var repeated: Int = 0, + override val aabbCompounds: MutableSet = mutableSetOf() +) : RepeatingActivity, RenderAABBActivity, Activity() { + private var currentOffset = BlockPos.ORIGIN + private var currentGoal: Goal? by Delegates.observable(null) { _, old, new -> + if (old != new) lastGoalSet = System.currentTimeMillis() + } + private var lastGoalSet: Long = 0L + + private val renderAABB = RenderAABBActivity.Companion.RenderAABB( + AxisAlignedBB(BlockPos.ORIGIN), ColorHolder(255, 255, 255) + ).also { aabbCompounds.add(it) } + + override fun SafeClientEvent.onInitialize() { + structure.forEach { (pos, targetState) -> + createBuildActivity(pos.add(currentOffset), targetState) + } + + currentOffset = currentOffset.add(offsetMove) + + if (subActivities.isEmpty()) success() + } + + private fun SafeClientEvent.withinRangeOfStructure(): Boolean { + return nearestStructureBlock()?.let { it.add(currentOffset).distanceTo(player.position) <= 5 } ?: true + } + + private fun SafeClientEvent.nearestStructureBlock(): BlockPos? { + return structure.keys.minByOrNull { it.add(currentOffset).distanceTo(player.position) } + } + + init { + safeListener { event -> + if (event.phase != TickEvent.Phase.END) return@safeListener + + if (subActivities.isEmpty() && !BaritoneUtils.isPathing) { + // todo: offset pathing like this might not make sense for all structures. we aren't guaranteed to be at a specific position relative to the structure + // we could path to the middle of the structure regardless of shape, but that will be inefficient. also structures are not guaranteed to be small/within render distance + if (autoPathing && !withinRangeOfStructure() && offsetMove != BlockPos.ORIGIN) { + LambdaMod.LOG.info("Structure out of range, pathing by offset") + // todo: improve stop/start stutter pathing + BaritoneUtils.primary?.customGoalProcess?.setGoalAndPath( + GoalXZ(player.posX.floorToInt() + (offsetMove.x * 5), player.posZ.floorToInt() + (offsetMove.z * 5)) + ) + return@safeListener + } + success() + } + + val activity = ActivityManager.getCurrentActivity() + + // no forced moving on other activities than native build activity + if (activity !is BuildActivity || !activity.hasNoSubActivities || activity.context != BuildActivity.Context.NONE) return@safeListener + + // pathing cool-down + if (System.currentTimeMillis() - lastGoalSet < BuildTools.pathingRecomputeTimeout) { + BaritoneUtils.primary?.customGoalProcess?.setGoalAndPath(currentGoal) + return@safeListener + } + + // collect all shulker boxes just to be sure + world.loadedEntityList.filterIsInstance().firstOrNull { + it.item.item is ItemShulkerBox + }?.let { + currentGoal = GoalBlock(it.position) + return@safeListener + } + + val itemsInRange = world.loadedEntityList + .filterIsInstance() + .filter { + player.distanceTo(it.position) <= BuildTools.collectRange + && it.item.item in WorldEater.collectables + } + + // collect drops + if (collectAll && autoPathing + && (itemsInRange.maxOfOrNull { it.item.count } ?: 0) > BuildTools.minimumStackSize + ) { + itemsInRange.maxByOrNull { it.item.count / player.distanceTo(it.position) }?.let { largestStack -> + currentGoal = GoalBlock(largestStack.position) + return@safeListener + } + } + + // move to next activity + when (activity) { + is PlaceBlock -> { + val blockPos = activity.blockPos + + renderAABB.renderAABB = AxisAlignedBB(blockPos).grow(0.1) + + if (!autoPathing) return@safeListener + + currentGoal = if (isInBlockAABB(blockPos)) { + GoalInverted(GoalBlock(blockPos)) + } else { + GoalAdjacent(blockPos, blockPos, true) + } + } + + is BreakBlock -> { + val blockPos = activity.blockPos + + renderAABB.renderAABB = AxisAlignedBB(blockPos).grow(0.1) + + if (!autoPathing) return@safeListener + + currentGoal = if (!allowBreakDescend && isInBlockAABB(blockPos.up())) { + GoalInverted(GoalBlock(blockPos.up())) + } else { + GoalAdjacent(blockPos, blockPos, true) + } + } + } + + currentGoal?.let { goal -> + BaritoneUtils.primary?.customGoalProcess?.setGoalAndPath(goal) + } + } + +// safeListener { } + + /* Listen for any block changes like falling sand */ + safeListener { event -> + handleBlockUpdate(event.pos, event.state) + } + + safeListener { + if (it.packet is SPacketBlockChange) { + handleBlockUpdate(it.packet.blockPosition, it.packet.blockState) + } else if (it.packet is SPacketMultiBlockChange) { + it.packet.changedBlocks.forEach { blockData -> + handleBlockUpdate(blockData.pos, blockData.blockState) + } + } + } + } + + private fun SafeClientEvent.handleBlockUpdate(blockPos: BlockPos, blockState: IBlockState) { + // todo: capture baritone placing support blocks and update structure + structure[blockPos]?.let { targetState -> + if (allSubActivities.any { + when (it) { + is BreakBlock -> it.blockPos == blockPos && targetState == blockState + is PlaceBlock -> it.blockPos == blockPos && targetState == blockState + else -> false + } + }) return +// MessageSendHelper.sendWarningMessage("Block changed at $blockPos") + createBuildActivity(blockPos, targetState) + } + } + + private fun SafeClientEvent.createBuildActivity(blockPos: BlockPos, targetState: IBlockState) { + val currentState = world.getBlockState(blockPos) + + /* is in padding */ + if (doPadding && isInPadding(blockPos)) return + + /* is in desired state */ + if (currentState == targetState) return + + /* block needs to be placed */ + if (targetState != Blocks.AIR.defaultState) { + addSubActivities(PlaceBlock( + blockPos, targetState + ), subscribe = true) + return + } + + /* block is not breakable */ + if (currentState.getBlockHardness(world, blockPos) < 0) return + + /* block is auto breakable like lily-pad or tall grass */ + if (!breakBushes && currentState.block is BlockBush) return + + /* block should be ignored */ + if (currentState.block in BuildTools.ignoredBlocks) return + + /* the only option left is breaking the block */ + addSubActivities(BreakBlock( +// blockPos, collectDrops = collectAll, minCollectAmount = 64 + blockPos + ), subscribe = true) + } + + override fun getCurrentActivity(): Activity { + subActivities + .filterIsInstance() + .sortedWith(buildComparator()) + .firstOrNull()?.let { buildActivity -> + (buildActivity as? Activity)?.let { + with(it) { + return getCurrentActivity() + } + } + } ?: return this + } + + /** + * Find out more about task sorting [here](https://docs.google.com/spreadsheets/d/1oZWV4qNu3Gao-7w3X_v5pjzRcXWjDhURMCKU25IIKEE/edit?usp=sharing) + */ + fun buildComparator() = compareBy { + it.context + }.thenBy { + it.availability + }.thenBy { + it.type + }.thenByDescending { + it.exposedSides + }.thenBy { + it.distance + } + + private fun SafeClientEvent.isInPadding(blockPos: BlockPos) = isBehindPos(player.flooredPosition, blockPos) + + private fun isBehindPos(origin: BlockPos, check: BlockPos): Boolean { + val a = origin.add(direction.counterClockwise(2).directionVec.multiply(100)) + val b = origin.add(direction.clockwise(2).directionVec.multiply(100)) + + return ((b.x - a.x) * (check.z - a.z) - (b.z - a.z) * (check.x - a.x)) > 0 + } + + fun SafeClientEvent.addLiquidFill(liquidPos: BlockPos) { + var exists = false + + subActivities + .filterIsInstance() + .filter { it.blockPos == liquidPos }.forEach { + it.type = BuildActivity.Type.LIQUID_FILL + exists = true + } + + if (exists) return + + val available = player.inventorySlots + .filterByStack { BuildTools.ejectList.contains(it.item.block.registryName.toString()) } + .maxByOrNull { it.stack.count }?.stack?.item?.block ?: Blocks.AIR + + if (available == Blocks.AIR) { + failedWith(BreakBlock.NoFillerMaterialFoundException()) + return + } + + val activity = PlaceBlock(liquidPos, available.defaultState) + + activity.type = BuildActivity.Type.LIQUID_FILL + + addSubActivities(activity) + } + + override fun SafeClientEvent.onChildSuccess(childActivity: Activity) { + when (childActivity) { + is PlaceBlock -> { + val blockPos = childActivity.blockPos + val targetState = structure[blockPos] ?: return + + createBuildActivity(blockPos, targetState) + } + + is BreakBlock -> { + val blockPos = childActivity.blockPos + val targetState = structure[blockPos] ?: return + + createBuildActivity(blockPos, targetState) + } + } + } + + private fun SafeClientEvent.isInBlockAABB(blockPos: BlockPos) = + !world.checkNoEntityCollision(AxisAlignedBB(blockPos), null) +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/construction/core/PlaceBlock.kt b/src/main/kotlin/com/lambda/client/activity/activities/construction/core/PlaceBlock.kt new file mode 100644 index 000000000..db1fe4d0a --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/construction/core/PlaceBlock.kt @@ -0,0 +1,415 @@ +package com.lambda.client.activity.activities.construction.core + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.interaction.Rotate +import com.lambda.client.activity.activities.inventory.AcquireItemInActiveHand +import com.lambda.client.activity.activities.storage.types.StackSelection +import com.lambda.client.activity.types.* +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.event.events.PacketEvent +import com.lambda.client.gui.hudgui.elements.client.ActivityManagerHud +import com.lambda.client.module.modules.client.BuildTools +import com.lambda.client.module.modules.client.BuildTools.directionForce +import com.lambda.client.module.modules.client.BuildTools.placeStrictness +import com.lambda.client.util.items.blockBlacklist +import com.lambda.client.util.math.CoordinateConverter.asString +import com.lambda.client.util.math.RotationUtils.getRotationTo +import com.lambda.client.util.math.Vec2f +import com.lambda.client.util.math.VectorUtils.distanceTo +import com.lambda.client.util.math.VectorUtils.toVec3dCenter +import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.threads.onMainThreadSafe +import com.lambda.client.util.threads.runSafe +import com.lambda.client.util.threads.safeListener +import com.lambda.client.util.world.PlaceInfo +import com.lambda.client.util.world.getNeighbour +import com.lambda.client.util.world.isReplaceable +import kotlinx.coroutines.launch +import net.minecraft.block.* +import net.minecraft.block.state.IBlockState +import net.minecraft.item.ItemStack +import net.minecraft.network.play.client.CPacketEntityAction +import net.minecraft.network.play.server.SPacketBlockChange +import net.minecraft.util.EnumActionResult +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumHand +import net.minecraft.util.IStringSerializable +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import net.minecraftforge.fml.common.gameevent.TickEvent +import kotlin.properties.Delegates + +class PlaceBlock( + val blockPos: BlockPos, + private val targetState: IBlockState, + private val ignoreProperties: Boolean = false, + private val ignoreFacing: Boolean = false, + override var rotation: Vec2f? = null, + override var distance: Double = 1337.0, + override var exposedSides: Int = 0, + override var timeout: Long = Long.MAX_VALUE, // ToDo: Reset timeouted placements blockstates + override val maxAttempts: Int = 8, + override var usedAttempts: Int = 0, + override val aabbCompounds: MutableSet = mutableSetOf(), + override val overlayTexts: MutableSet = mutableSetOf(), +) : RotatingActivity, TimeoutActivity, AttemptActivity, RenderAABBActivity, RenderOverlayTextActivity, BuildActivity, TimedActivity, Activity() { + private var placeInfo: PlaceInfo? = null + private var spoofedDirection = false + + override var context: BuildActivity.Context by Delegates.observable(BuildActivity.Context.NONE) { _, old, new -> + if (old == new) return@observable + renderContextAABB.color = new.color + + renderContextOverlay.text = new.name + renderContextOverlay.color = new.color + } + + override var availability: BuildActivity.Availability by Delegates.observable(BuildActivity.Availability.NONE) { _, old, new -> + if (old == new) return@observable + renderAvailabilityAABB.color = new.color + + renderAvailabilityOverlay.text = new.name + renderAvailabilityOverlay.color = new.color + } + + override var type: BuildActivity.Type by Delegates.observable(BuildActivity.Type.BREAK_BLOCK) { _, old, new -> + if (old == new) return@observable + renderTypeAABB.color = new.color + + renderTypeOverlay.text = new.name + renderTypeOverlay.color = new.color + } + + private val renderContextAABB = RenderAABBActivity.Companion.RenderBlockPos( + blockPos, context.color + ).also { aabbCompounds.add(it) } + + private val renderContextOverlay = RenderOverlayTextActivity.Companion.RenderOverlayText( + context.name, context.color, blockPos.toVec3dCenter(), 0 + ).also { overlayTexts.add(it) } + + private val renderAvailabilityAABB = RenderAABBActivity.Companion.RenderBlockPos( + blockPos, availability.color + ).also { aabbCompounds.add(it) } + + private val renderAvailabilityOverlay = RenderOverlayTextActivity.Companion.RenderOverlayText( + availability.name, availability.color, blockPos.toVec3dCenter(), 1 + ).also { overlayTexts.add(it) } + + private val renderTypeAABB = RenderAABBActivity.Companion.RenderBlockPos( + blockPos, type.color + ).also { aabbCompounds.add(it) } + + private val renderTypeOverlay = RenderOverlayTextActivity.Companion.RenderOverlayText( + type.name, type.color, blockPos.toVec3dCenter(), 2 + ).also { overlayTexts.add(it) } + + override var earliestFinish: Long + get() = BuildTools.placeDelay.toLong() + set(_) {} + + private enum class PlacementOffset(val offset: Vec3d) { + UPPER(Vec3d(0.0, 0.1, 0.0)), + CENTER(Vec3d.ZERO), + LOWER(Vec3d(0.0, -0.1, 0.0)) + } + + private val blocksToOppositeDirection = listOf( + BlockPistonBase::class, + BlockEnderChest::class, + BlockEndPortalFrame::class, + BlockGlazedTerracotta::class, + BlockPumpkin::class, + BlockRedstoneDiode::class, + ) + + init { + runSafe { + if (!world.worldBorder.contains(blockPos) || world.isOutsideBuildHeight(blockPos)) { + // ToDo: add support for placing blocks outside of world border + failedWith(BlockOutsideOfBoundsException(blockPos)) + } + + updateState() + } + + safeListener { + if (it.phase != TickEvent.Phase.START) return@safeListener + + updateState() + } + + safeListener { + if (it.packet !is SPacketBlockChange + || it.packet.blockPosition != blockPos + ) return@safeListener + + defaultScope.launch { + onMainThreadSafe { + if (isInDesiredState(it.packet.blockState)) { + ActivityManagerHud.totalBlocksPlaced++ + + success() + } else { +// failedWith(UnexpectedBlockStateException(blockPos, targetState, it.packet.blockState)) + } + } + } + } + } + + override fun SafeClientEvent.onInitialize() { + updateState() + + if (availability != BuildActivity.Availability.VALID) { + status = Status.UNINITIALIZED + return + } + + placeInfo?.let { placeInfo -> + checkPlace(placeInfo) + } + } + + private fun SafeClientEvent.updateState() { + val allowedSides = EnumFacing.VALUES.toMutableList() + var placementOffset = PlacementOffset.CENTER + +// var allowedRotations = targetState.block.getValidRotations(world, blockPos)?.toMutableSet() + + val currentState = world.getBlockState(blockPos) + + if (context != BuildActivity.Context.PENDING + && isInDesiredState(currentState) + ) success() + + if (!currentState.isReplaceable + && !isInDesiredState(currentState) + && context != BuildActivity.Context.PENDING + && subActivities.filterIsInstance().isEmpty() + ) { + (parent as? BuildStructure)?.let { + with(it) { + addSubActivities( + BreakBlock(blockPos), + subscribe = true + ) + } + } + return + } + + targetState.properties.entries.firstOrNull { it.key.name == "facing" }?.let { entry -> + val direction = entry.value as EnumFacing + + if (targetState.block is BlockButton) { + allowedSides.clear() + allowedSides.add(direction.opposite) + } + } + + targetState.properties.entries.firstOrNull { it.key.name == "half" }?.let { entry -> + val half = entry.value as IStringSerializable + placementOffset = when (half.name) { + "top" -> { + allowedSides.remove(EnumFacing.DOWN) + PlacementOffset.UPPER + } + + else -> { + allowedSides.remove(EnumFacing.UP) + PlacementOffset.LOWER + } + } + } + + targetState.properties.entries.firstOrNull { it.key.name == "axis" }?.let { entry -> + val axis = entry.value as IStringSerializable + + when (axis.name) { + "x" -> allowedSides.removeIf { it.axis != EnumFacing.Axis.X } + "y" -> allowedSides.removeIf { it.axis != EnumFacing.Axis.Y } + "z" -> allowedSides.removeIf { it.axis != EnumFacing.Axis.Z } + else -> {} + } + } + + /* quartz is a special snowflake */ + targetState.properties.entries.firstOrNull { it.key.name == "variant" }?.let { entry -> + when (entry.value) { + BlockQuartz.EnumType.LINES_X -> allowedSides.removeIf { it.axis != EnumFacing.Axis.X } + BlockQuartz.EnumType.LINES_Y -> allowedSides.removeIf { it.axis != EnumFacing.Axis.Y } + BlockQuartz.EnumType.LINES_Z -> allowedSides.removeIf { it.axis != EnumFacing.Axis.Z } + else -> {} + } + } + +// allowedSides.removeIf { +// !targetState.block.canPlaceBlockOnSide(world, blockPos, it.opposite) +// } + + getNeighbour( + blockPos, + attempts = BuildTools.placementSearch, + visibleSideCheck = placeStrictness != BuildTools.PlacementStrictness.ANY, + range = BuildTools.maxReach, + sides = allowedSides.toTypedArray() + )?.let { + if (it.placedPos == blockPos) { + availability = BuildActivity.Availability.VALID + it.hitVec = it.hitVec.add(placementOffset.offset) + distance = player.distanceTo(it.hitVec) + exposedSides = EnumFacing.HORIZONTALS.count { world.isAirBlock(blockPos.offset(it)) } + placeInfo = it + } else { +// action = BuildActivity.BuildAction.NEEDS_SUPPORT +// +// PlaceBlock(it.placedPos, targetState).also { placeBlock -> +// placeBlock.context = BuildActivity.BuildContext.SUPPORT +// addSubActivities(placeBlock) +// } + } + } ?: run { + getNeighbour( + blockPos, + attempts = BuildTools.placementSearch, + visibleSideCheck = false, + range = 256f, + sides = allowedSides.toTypedArray() + )?.let { + availability = BuildActivity.Availability.NOT_IN_RANGE + distance = player.distanceTo(it.hitVec.add(placementOffset.offset)) + exposedSides = EnumFacing.HORIZONTALS.count { world.isAirBlock(blockPos.offset(it)) } + } ?: run { + availability = BuildActivity.Availability.NOT_VISIBLE + distance = 1337.0 + exposedSides = EnumFacing.HORIZONTALS.count { world.isAirBlock(blockPos.offset(it)) } + } + placeInfo = null + rotation = null + } + } + + private fun SafeClientEvent.checkPlace(placeInfo: PlaceInfo) { + /* check if item has required metadata (declares the type) */ + val heldItemStack = player.getHeldItem(EnumHand.MAIN_HAND) + val optimalStack = if (targetState.block is BlockShulkerBox) { + ItemStack(targetState.block, 1, targetState.block.getMetaFromState(targetState)) + } else { + @Suppress("DEPRECATION") + targetState.block.getItem(world, blockPos, targetState) + } + + if (heldItemStack.item != optimalStack.item + || (!ignoreProperties && optimalStack.metadata != heldItemStack.metadata) + ) { + context = BuildActivity.Context.RESTOCK + + addSubActivities(AcquireItemInActiveHand(StackSelection().apply { + selection = isItem(optimalStack.item) and hasMetadata(optimalStack.metadata) + })) + return + } + + targetState.properties.entries.firstOrNull { it.key.name == "facing" }?.let { entry -> + if (ignoreProperties || ignoreFacing) return@let + + var direction = entry.value as EnumFacing + +// BlockDirectional +// BlockHorizontal +// BlockTorch +// BlockLever + + if (targetState.block::class in blocksToOppositeDirection) direction = direction.opposite + + /* rotate block to right direction if possible */ + if (directionForce + && !spoofedDirection + && player.horizontalFacing != direction + ) { + addSubActivities(Rotate(Vec2f(direction.horizontalAngle, player.rotationPitch))) + return + } + } + + /* last check for placement state */ +// val resultingState = targetState.block.getStateForPlacement( +// world, +// it.pos, +// it.side, +// hitVec.x.toFloat(), hitVec.y.toFloat(), hitVec.z.toFloat(), +// heldItem.metadata, +// player, +// EnumHand.MAIN_HAND +// ) +// +// if (resultingState != targetState +// && !spoofedDirection +// && targetState.block !is BlockButton // ToDo: find out why buttons don't work with this +// ) { +// failedWith(PlacementStateException(resultingState, targetState)) +// return +// } + + doPlace(placeInfo) + } + + private fun SafeClientEvent.doPlace(placeInfo: PlaceInfo) { + timeout = 1000L + + val isBlacklisted = world.getBlockState(placeInfo.pos).block in blockBlacklist + + if (isBlacklisted) { + connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.START_SNEAKING)) + } + + rotation = getRotationTo(placeInfo.hitVec) + + val result = playerController.processRightClickBlock( + player, + world, + placeInfo.pos, + placeInfo.side, + placeInfo.hitVec, + EnumHand.MAIN_HAND + ) + + if (result != EnumActionResult.SUCCESS) { + failedWith(ProcessRightClickException(result)) + return + } + + player.swingArm(EnumHand.MAIN_HAND) + + if (isBlacklisted) { + connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.STOP_SNEAKING)) + } + + context = BuildActivity.Context.PENDING + } + + private fun isInDesiredState(currentState: IBlockState) = + currentState == targetState + || (ignoreProperties && currentState.block == targetState.block) +// || (ignoreFacing && currentState.block == targetState.block && currentState.properties == targetState.properties) + + + override fun SafeClientEvent.onChildSuccess(childActivity: Activity) { + spoofedDirection = childActivity is Rotate + status = Status.UNINITIALIZED + } + +// override fun SafeClientEvent.onFailure(exception: Exception): Boolean { +// if (context != BuildActivity.BuildContext.PENDING) return false +// +// world.setBlockState(blockPos, initState) +// return false +// } + + class NoNeighbourException(blockPos: BlockPos) : Exception("No neighbour for (${blockPos.asString()}) found") + class BlockNotPlaceableException(targetState: IBlockState) : Exception("Block $targetState is not placeable") + class ProcessRightClickException(result: EnumActionResult) : Exception("Processing right click failed with result $result") + class UnexpectedBlockStateException(blockPos: BlockPos, expected: IBlockState, actual: IBlockState) : Exception("Unexpected block state at (${blockPos.asString()}) expected $expected but got $actual") + class BlockOutsideOfBoundsException(blockPos: BlockPos) : Exception("Block at (${blockPos.asString()}) is outside of world") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/interaction/AttachItemFrame.kt b/src/main/kotlin/com/lambda/client/activity/activities/interaction/AttachItemFrame.kt new file mode 100644 index 000000000..d89b689bf --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/interaction/AttachItemFrame.kt @@ -0,0 +1,63 @@ +package com.lambda.client.activity.activities.interaction + +import com.lambda.client.LambdaMod +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.inventory.AcquireItemInActiveHand +import com.lambda.client.activity.activities.storage.types.StackSelection +import com.lambda.client.activity.types.RotatingActivity +import com.lambda.client.activity.types.TimeoutActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.event.events.PacketEvent +import com.lambda.client.util.math.RotationUtils.getRotationTo +import com.lambda.client.util.math.Vec2f +import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.threads.onMainThreadSafe +import com.lambda.client.util.threads.safeListener +import com.lambda.client.util.world.getHitVec +import kotlinx.coroutines.launch +import net.minecraft.init.Items +import net.minecraft.network.play.server.SPacketSpawnObject +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumHand +import net.minecraft.util.math.BlockPos + +class AttachItemFrame( + private val placePos: BlockPos, + private val facing: EnumFacing, + override var rotation: Vec2f? = null, + override var timeout: Long = 1000L +) : RotatingActivity, TimeoutActivity, Activity() { + override fun SafeClientEvent.onInitialize() { + rotation = getRotationTo(getHitVec(placePos, facing)) + + addSubActivities( + AcquireItemInActiveHand(StackSelection().apply { selection = isItem(Items.ITEM_FRAME) }) + ) + } + + init { + safeListener { + if (it.packet !is SPacketSpawnObject) return@safeListener + if (it.packet.type != 71) return@safeListener + + defaultScope.launch { + onMainThreadSafe { + success() + } + } + } + } + + override fun SafeClientEvent.onChildSuccess(childActivity: Activity) { + if (childActivity !is AcquireItemInActiveHand) return + + LambdaMod.LOG.info(playerController.processRightClickBlock( + player, + world, + placePos, + facing, + getHitVec(placePos, facing), + EnumHand.MAIN_HAND + )) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/interaction/AttachMap.kt b/src/main/kotlin/com/lambda/client/activity/activities/interaction/AttachMap.kt new file mode 100644 index 000000000..c3b1175a4 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/interaction/AttachMap.kt @@ -0,0 +1,54 @@ +package com.lambda.client.activity.activities.interaction + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.inventory.AcquireItemInActiveHand +import com.lambda.client.activity.activities.storage.types.StackSelection +import com.lambda.client.activity.types.RotatingActivity +import com.lambda.client.activity.types.TimeoutActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.event.events.PacketEvent +import com.lambda.client.util.math.RotationUtils.getRotationToEntity +import com.lambda.client.util.math.Vec2f +import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.threads.onMainThreadSafe +import com.lambda.client.util.threads.safeListener +import kotlinx.coroutines.launch +import net.minecraft.entity.item.EntityItemFrame +import net.minecraft.init.Items +import net.minecraft.network.play.client.CPacketUseEntity +import net.minecraft.network.play.server.SPacketSpawnObject +import net.minecraft.util.EnumHand + +class AttachMap( + private val itemFrame: EntityItemFrame, + private val mapID: Int, + override var rotation: Vec2f? = null, + override var timeout: Long = 1000L +) : RotatingActivity, TimeoutActivity, Activity() { + override fun SafeClientEvent.onInitialize() { + rotation = getRotationToEntity(itemFrame) + + addSubActivities( + AcquireItemInActiveHand(StackSelection().apply { selection = isItem(Items.MAP) and hasDamage(mapID) }) + ) + } + + init { + safeListener { + if (it.packet !is SPacketSpawnObject) return@safeListener + if (it.packet.entityID != itemFrame.entityId) return@safeListener + + defaultScope.launch { + onMainThreadSafe { + success() + } + } + } + } + + override fun SafeClientEvent.onChildSuccess(childActivity: Activity) { + if (childActivity !is AcquireItemInActiveHand) return + + connection.sendPacket(CPacketUseEntity(itemFrame, EnumHand.MAIN_HAND)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/interaction/Rotate.kt b/src/main/kotlin/com/lambda/client/activity/activities/interaction/Rotate.kt new file mode 100644 index 000000000..1b313cfff --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/interaction/Rotate.kt @@ -0,0 +1,19 @@ +package com.lambda.client.activity.activities.interaction + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.types.RotatingActivity +import com.lambda.client.util.math.Vec2f +import com.lambda.client.util.threads.safeListener +import net.minecraftforge.fml.common.gameevent.TickEvent + +class Rotate( + override var rotation: Vec2f?, +) : RotatingActivity, Activity() { + init { + safeListener { + if (it.phase != TickEvent.Phase.END) return@safeListener + + success() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/interaction/UseThrowableOnEntity.kt b/src/main/kotlin/com/lambda/client/activity/activities/interaction/UseThrowableOnEntity.kt new file mode 100644 index 000000000..0b1d6acd8 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/interaction/UseThrowableOnEntity.kt @@ -0,0 +1,45 @@ +package com.lambda.client.activity.activities.interaction + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.types.RotatingActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.math.RotationUtils.getRotationToEntity +import com.lambda.client.util.math.Vec2f +import com.lambda.client.util.threads.safeListener +import net.minecraft.entity.Entity +import net.minecraft.network.play.client.CPacketPlayerTryUseItem +import net.minecraft.util.EnumHand +import net.minecraftforge.fml.common.gameevent.TickEvent + +class UseThrowableOnEntity( + private val targetEntity: Entity, + private val uses: Int = 1, + private val useHand: EnumHand = EnumHand.MAIN_HAND, + override var rotation: Vec2f? = null, +) : RotatingActivity, Activity() { + private var used = 0 + + override fun SafeClientEvent.onInitialize() { + rotation = getRotation() + } + + init { + safeListener { + if (it.phase != TickEvent.Phase.START) return@safeListener + + rotation = getRotation() + + connection.sendPacket(CPacketPlayerTryUseItem(useHand)) + + used++ + + if (used == uses) success() + } + } + + private fun SafeClientEvent.getRotation() = if (targetEntity == player) { + Vec2f(player.rotationYaw, 90f) + } else { + getRotationToEntity(targetEntity) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/interaction/crafting/CraftShulkerBox.kt b/src/main/kotlin/com/lambda/client/activity/activities/interaction/crafting/CraftShulkerBox.kt new file mode 100644 index 000000000..c66261527 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/interaction/crafting/CraftShulkerBox.kt @@ -0,0 +1,18 @@ +package com.lambda.client.activity.activities.interaction.crafting + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.inventory.AcquireItemInActiveHand +import com.lambda.client.activity.activities.storage.types.StackSelection +import com.lambda.client.event.SafeClientEvent +import net.minecraft.init.Blocks +import net.minecraft.init.Items + +class CraftShulkerBox : Activity() { + override fun SafeClientEvent.onInitialize() { + addSubActivities( + AcquireItemInActiveHand(StackSelection().apply { selection = isItem(Items.SHULKER_SHELL) }), + AcquireItemInActiveHand(StackSelection().apply { selection = isBlock(Blocks.CHEST) }), +// InventoryTransaction(0, ) + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/interaction/crafting/CraftSimpleRecipe.kt b/src/main/kotlin/com/lambda/client/activity/activities/interaction/crafting/CraftSimpleRecipe.kt new file mode 100644 index 000000000..8a92d0bff --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/interaction/crafting/CraftSimpleRecipe.kt @@ -0,0 +1,18 @@ +package com.lambda.client.activity.activities.interaction.crafting + +import com.lambda.client.activity.Activity +import com.lambda.client.event.SafeClientEvent +import net.minecraft.item.Item +import net.minecraft.item.ItemShulkerBox + +class CraftSimpleRecipe(val item: Item) : Activity() { + override fun SafeClientEvent.onInitialize() { + when (item) { + is ItemShulkerBox -> { + addSubActivities( + + ) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/interaction/crafting/ReachXPLevel.kt b/src/main/kotlin/com/lambda/client/activity/activities/interaction/crafting/ReachXPLevel.kt new file mode 100644 index 000000000..685fc9505 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/interaction/crafting/ReachXPLevel.kt @@ -0,0 +1,26 @@ +package com.lambda.client.activity.activities.interaction.crafting + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.interaction.UseThrowableOnEntity +import com.lambda.client.activity.activities.inventory.AcquireItemInActiveHand +import com.lambda.client.activity.activities.inventory.TakeOffArmor +import com.lambda.client.activity.activities.storage.types.StackSelection +import com.lambda.client.activity.types.LoopWhileActivity +import com.lambda.client.event.SafeClientEvent +import net.minecraft.init.Items + +class ReachXPLevel( + private val desiredLevel: Int, + override val loopWhile: SafeClientEvent.() -> Boolean = { + player.experienceLevel < desiredLevel + }, + override var currentLoops: Int = 0 +) : LoopWhileActivity, Activity() { + override fun SafeClientEvent.onInitialize() { + addSubActivities( + TakeOffArmor(), + AcquireItemInActiveHand(StackSelection().apply { selection = isItem(Items.EXPERIENCE_BOTTLE) }), + UseThrowableOnEntity(player) + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/inventory/AcquireItemInActiveHand.kt b/src/main/kotlin/com/lambda/client/activity/activities/inventory/AcquireItemInActiveHand.kt new file mode 100644 index 000000000..710ad6722 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/inventory/AcquireItemInActiveHand.kt @@ -0,0 +1,103 @@ +package com.lambda.client.activity.activities.inventory + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.inventory.core.CreativeInventoryAction +import com.lambda.client.activity.activities.inventory.core.SwapOrSwitchToSlot +import com.lambda.client.activity.activities.inventory.core.SwitchToHotbarSlot +import com.lambda.client.activity.activities.storage.BreakDownEnderChests +import com.lambda.client.activity.activities.storage.ShulkerTransaction +import com.lambda.client.activity.activities.storage.types.* +import com.lambda.client.activity.types.AttemptActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.module.modules.client.BuildTools +import com.lambda.client.module.modules.client.BuildTools.pickBlock +import com.lambda.client.util.items.allSlots +import com.lambda.client.util.items.hotbarSlots +import com.lambda.client.util.items.item +import net.minecraft.init.Blocks +import net.minecraft.item.Item +import net.minecraft.item.ItemShulkerBox +import net.minecraft.item.ItemStack + +/** + * [AcquireItemInActiveHand] is an [Activity] that attempts to acquire an [Item] in the player's active hand. + */ +class AcquireItemInActiveHand( + private val order: StackSelection, + private val searchShulkerBoxes: Boolean = true, + private val searchEnderChest: Boolean = true, +) : AttemptActivity, Activity() { + override val maxAttempts = 3 + override var usedAttempts = 0 + + override fun SafeClientEvent.onInitialize() { + // If the item is already in the player's hand, we're done + if (order.selection(player.heldItemMainhand)) { + success() + return + } + + // If the item is in the hotbar, switch to it + player.hotbarSlots.firstOrNull(order.filter)?.let { + addSubActivities(SwitchToHotbarSlot(it)) + return + } + + // If we are in game mode creative, we can just use the creative inventory (if item not yet in hotbar) + if (pickBlock && player.capabilities.isCreativeMode) { + order.optimalStack?.let { + addSubActivities(CreativeInventoryAction(it)) + return + } + } + + // If the item is in the inventory, swap it with the next best slot in hotbar + player.allSlots.firstOrNull(order.filter)?.let { slotFrom -> + addSubActivities(SwapOrSwitchToSlot(slotFrom)) + return + } + + // If the item is in a shulker box, extract it + if (searchShulkerBoxes && order.item !is ItemShulkerBox) { + addSubActivities(ShulkerTransaction(ContainerAction.PULL, order)) + return + } + + // If the item is obsidian, break down ender chests + if (order.item == Blocks.OBSIDIAN.item) { + addSubActivities(BreakDownEnderChests(maximumRepeats = BuildTools.breakDownCycles)) + return + } + +// // If the item is contained in the ender chest, extract it +// if (searchEnderChest) { +// // TODO: Check cached ender chest inventory if item is in there directly or in a shulker box +// player.allSlots.firstOrNull { it.stack.item == Blocks.ENDER_CHEST.item }?.let { slot -> +// addSubActivities(EnderChestTransaction(ShulkerOrder(ContainerAction.PULL, order.item, order.amount), slot)) +// return +// } +// +// addSubActivities(AcquireItemInActiveHand(order, searchEnderChest = false)) +// return +// } + + failedWith(NoItemFoundException(order)) + } + + override fun SafeClientEvent.onSuccess() { + val currentItemStack = player.heldItemMainhand + + if (!order.selection(currentItemStack)) { + failedWith(FailedToMoveItemException(order, currentItemStack)) + } + } + + override fun SafeClientEvent.onChildSuccess(childActivity: Activity) { + if (childActivity !is BreakDownEnderChests) return + + status = Status.UNINITIALIZED + } + + class NoItemFoundException(order: StackSelection) : Exception("No $order found in inventory") + class FailedToMoveItemException(order: StackSelection, currentStack: ItemStack) : Exception("Failed to move ${order.item?.registryName?.path} to hotbar (current item: ${currentStack.item.registryName?.path})") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/inventory/DumpSlot.kt b/src/main/kotlin/com/lambda/client/activity/activities/inventory/DumpSlot.kt new file mode 100644 index 000000000..1ecfe1182 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/inventory/DumpSlot.kt @@ -0,0 +1,13 @@ +package com.lambda.client.activity.activities.inventory + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.inventory.core.InventoryTransaction +import com.lambda.client.event.SafeClientEvent +import net.minecraft.inventory.ClickType +import net.minecraft.inventory.Slot + +class DumpSlot(private val slot: Slot) : Activity() { + override fun SafeClientEvent.onInitialize() { + addSubActivities(InventoryTransaction(player.openContainer.windowId, slot.slotNumber, 1, ClickType.THROW)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/inventory/TakeOffArmor.kt b/src/main/kotlin/com/lambda/client/activity/activities/inventory/TakeOffArmor.kt new file mode 100644 index 000000000..952ddb8d4 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/inventory/TakeOffArmor.kt @@ -0,0 +1,17 @@ +package com.lambda.client.activity.activities.inventory + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.inventory.core.QuickMoveSlot +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.items.armorSlots +import com.lambda.client.util.items.offhandSlot + +class TakeOffArmor : Activity() { + override fun SafeClientEvent.onInitialize() { + (player.armorSlots + player.offhandSlot).forEach { slot -> + if (slot.stack.isEmpty) return@forEach + + addSubActivities(QuickMoveSlot(slot)) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/CreativeInventoryAction.kt b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/CreativeInventoryAction.kt new file mode 100644 index 000000000..d20503eaa --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/CreativeInventoryAction.kt @@ -0,0 +1,36 @@ +package com.lambda.client.activity.activities.inventory.core + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.types.TimeoutActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.event.events.PacketEvent +import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.threads.onMainThreadSafe +import com.lambda.client.util.threads.safeListener +import kotlinx.coroutines.launch +import net.minecraft.item.ItemStack +import net.minecraft.network.play.client.CPacketCreativeInventoryAction +import net.minecraft.network.play.server.SPacketSetSlot +import net.minecraft.util.EnumHand + +class CreativeInventoryAction( + private val stack: ItemStack, + override val timeout: Long = 600L +) : TimeoutActivity, Activity() { + override fun SafeClientEvent.onInitialize() { + player.setHeldItem(EnumHand.MAIN_HAND, stack) + connection.sendPacket(CPacketCreativeInventoryAction(36 + player.inventory.currentItem, stack)) + } + + init { + safeListener { + if (it.packet !is SPacketSetSlot) return@safeListener + + defaultScope.launch { + onMainThreadSafe { + success() + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/InventoryTransaction.kt b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/InventoryTransaction.kt new file mode 100644 index 000000000..a47f3993e --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/InventoryTransaction.kt @@ -0,0 +1,95 @@ +package com.lambda.client.activity.activities.inventory.core + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.types.AttemptActivity +import com.lambda.client.activity.types.TimeoutActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.event.events.PacketEvent +import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.threads.onMainThreadSafe +import com.lambda.client.util.threads.safeListener +import kotlinx.coroutines.launch +import net.minecraft.inventory.ClickType +import net.minecraft.inventory.Container +import net.minecraft.item.ItemStack +import net.minecraft.network.play.client.CPacketClickWindow +import net.minecraft.network.play.server.SPacketConfirmTransaction + +class InventoryTransaction( + val windowId: Int = 0, + val slot: Int, + private val mouseButton: Int = 0, + val type: ClickType, + override val timeout: Long = 500L, + override val maxAttempts: Int = 5, + override var usedAttempts: Int = 0 +) : TimeoutActivity, AttemptActivity, Activity() { + private var transactionId: Short = -1 + + override fun SafeClientEvent.onInitialize() { + getContainerOrNull(windowId)?.let { activeContainer -> + transactionId = activeContainer.getNextTransactionID(player.inventory) + + val itemStack = if (type == ClickType.PICKUP && slot != -999) { + activeContainer.inventorySlots?.getOrNull(slot)?.stack ?: ItemStack.EMPTY + } else { + ItemStack.EMPTY + } + + val packet = CPacketClickWindow( + windowId, + slot, + mouseButton, + type, + itemStack, + transactionId + ) + + connection.sendPacket(packet) + + playerController.updateController() + +// LambdaMod.LOG.info("Sent packet: ${packet.javaClass.simpleName}") + } ?: run { + failedWith(ContainerOutdatedException(windowId)) + } + } + + init { + safeListener { + val packet = it.packet + + if (packet !is SPacketConfirmTransaction + || packet.windowId != windowId + || packet.actionNumber != transactionId + ) return@safeListener + + defaultScope.launch { + onMainThreadSafe { + if (!packet.wasAccepted()) { + failedWith(DeniedException(packet.actionNumber)) + return@onMainThreadSafe + } + + getContainerOrNull(packet.windowId)?.let { container -> + container.slotClick(slot, mouseButton, type, player) + success() +// LambdaMod.LOG.info("Accepted packet: ${it.packet.javaClass.simpleName} $transactionId") + } ?: run { + failedWith(ContainerOutdatedException(packet.windowId)) + } + } + } + } + } + + private fun SafeClientEvent.getContainerOrNull(windowId: Int): Container? = + if (windowId == player.openContainer.windowId) { + player.openContainer + } else { + null + } + + class ContainerOutdatedException(windowId: Int) : Exception("WindowID $windowId of container outdated") + class DeniedException(transactionId: Short) : Exception("InventoryTransaction $transactionId was denied") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/QuickMoveSlot.kt b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/QuickMoveSlot.kt new file mode 100644 index 000000000..7a6c76b55 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/QuickMoveSlot.kt @@ -0,0 +1,37 @@ +package com.lambda.client.activity.activities.inventory.core + +import com.lambda.client.activity.Activity +import com.lambda.client.event.SafeClientEvent +import net.minecraft.inventory.ClickType +import net.minecraft.inventory.Slot + +class QuickMoveSlot( + private val slot: Slot +) : Activity() { + override fun SafeClientEvent.onInitialize() { + if (slot.stack.isEmpty) { + success() + } else { + addSubActivities(InventoryTransaction(player.openContainer.windowId, slot.slotNumber, 0, ClickType.QUICK_MOVE)) + } + } + + override fun SafeClientEvent.onChildSuccess(childActivity: Activity) { + if (childActivity !is InventoryTransaction) return + + if (slot.stack.isEmpty) { + success() + } else { + failedWith(ExceptionSlotNotEmpty()) + } + } + + override fun SafeClientEvent.onChildFailure(childActivities: ArrayDeque, childException: Exception): Boolean { + if (childException !is InventoryTransaction.DeniedException) return false + + failedWith(ExceptionSlotNotEmpty()) + return true + } + + class ExceptionSlotNotEmpty : Exception("Slot not empty") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/SwapHotbarSlots.kt b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/SwapHotbarSlots.kt new file mode 100644 index 000000000..ad91cc47c --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/SwapHotbarSlots.kt @@ -0,0 +1,15 @@ +package com.lambda.client.activity.activities.inventory.core + +import com.lambda.client.activity.Activity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.items.HotbarSlot +import net.minecraft.inventory.ClickType + +class SwapHotbarSlots( + private val slotFrom: HotbarSlot, + private val slotTo: HotbarSlot +) : Activity() { + override fun SafeClientEvent.onInitialize() { + addSubActivities(InventoryTransaction(player.openContainer.windowId, slotFrom.slotNumber, slotTo.hotbarSlot, ClickType.SWAP)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/SwapOrSwitchToSlot.kt b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/SwapOrSwitchToSlot.kt new file mode 100644 index 000000000..b8d9b1a8f --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/SwapOrSwitchToSlot.kt @@ -0,0 +1,25 @@ +package com.lambda.client.activity.activities.inventory.core + +import com.lambda.client.activity.Activity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.items.hotbarSlots +import com.lambda.client.util.items.toHotbarSlotOrNull +import net.minecraft.inventory.Slot + +class SwapOrSwitchToSlot( + private val slotFrom: Slot, + private val hotbarFilter: (Slot) -> Boolean = { true } +) : Activity() { + override fun SafeClientEvent.onInitialize() { + slotFrom.toHotbarSlotOrNull()?.let { hotbarSlot -> + addSubActivities(SwitchToHotbarSlot(hotbarSlot)) + } ?: run { + val slotTo = player.hotbarSlots.firstOrNull { hotbarFilter(it.slot) } ?: return + + addSubActivities( + SwapWithSlot(slotFrom, slotTo.hotbarSlot), + SwitchToHotbarSlot(slotTo) + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/SwapWithSlot.kt b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/SwapWithSlot.kt new file mode 100644 index 000000000..daec5636e --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/SwapWithSlot.kt @@ -0,0 +1,15 @@ +package com.lambda.client.activity.activities.inventory.core + +import com.lambda.client.activity.Activity +import com.lambda.client.event.SafeClientEvent +import net.minecraft.inventory.ClickType +import net.minecraft.inventory.Slot + +class SwapWithSlot( + private val slotFrom: Slot, + private val hotbarSlotTo: Int +) : Activity() { + override fun SafeClientEvent.onInitialize() { + addSubActivities(InventoryTransaction(player.openContainer.windowId, slotFrom.slotNumber, hotbarSlotTo, ClickType.SWAP)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/SwitchToHotbarSlot.kt b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/SwitchToHotbarSlot.kt new file mode 100644 index 000000000..a6dbc89ad --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/inventory/core/SwitchToHotbarSlot.kt @@ -0,0 +1,14 @@ +package com.lambda.client.activity.activities.inventory.core + +import com.lambda.client.activity.Activity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.items.HotbarSlot + +class SwitchToHotbarSlot(private val slot: HotbarSlot) : Activity() { + override fun SafeClientEvent.onInitialize() { + if (slot.hotbarSlot !in 0..8) return + player.inventory.currentItem = slot.hotbarSlot + playerController.updateController() + success() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/storage/BreakDownEnderChests.kt b/src/main/kotlin/com/lambda/client/activity/activities/storage/BreakDownEnderChests.kt new file mode 100644 index 000000000..0fbc3bc8b --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/storage/BreakDownEnderChests.kt @@ -0,0 +1,67 @@ +package com.lambda.client.activity.activities.storage + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.construction.core.BreakBlock +import com.lambda.client.activity.activities.inventory.AcquireItemInActiveHand +import com.lambda.client.activity.activities.storage.core.PlaceContainer +import com.lambda.client.activity.activities.storage.types.* +import com.lambda.client.activity.types.RepeatingActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.module.modules.client.BuildTools +import com.lambda.client.util.items.countItem +import com.lambda.client.util.items.inventorySlots +import com.lambda.client.util.items.item +import net.minecraft.enchantment.EnchantmentHelper +import net.minecraft.init.Blocks +import net.minecraft.init.Enchantments +import net.minecraft.init.Items +import net.minecraft.item.ItemStack + +class BreakDownEnderChests( + override val maximumRepeats: Int = 0, + override var repeated: Int = 0 +) : RepeatingActivity, Activity() { + override fun SafeClientEvent.onInitialize() { + val freeSlots = player.inventorySlots.filter { slot -> + BuildTools.ejectList.contains(slot.stack.item.registryName.toString()) + || slot.stack.isEmpty + } + + if (freeSlots.isEmpty()) { + if (player.inventorySlots.countItem(Blocks.OBSIDIAN.item) > 0) { + addSubActivities( + ShulkerTransaction(ContainerAction.PUSH, StackSelection().apply { + selection = isBlock(Blocks.OBSIDIAN) + }) + ) + return + } + + failedWith(NoSpaceLeftInInventoryException()) + return + } + + addSubActivities( + PlaceContainer(StackSelection().apply { + selection = isBlock(Blocks.ENDER_CHEST) + }) + ) + } + + override fun SafeClientEvent.onChildSuccess(childActivity: Activity) { + if (childActivity !is PlaceContainer) return + + addSubActivities( + AcquireItemInActiveHand(StackSelection().apply { + selection = isItem(Items.DIAMOND_PICKAXE) and hasEnchantment(Enchantments.SILK_TOUCH).not() + }), + BreakBlock( + childActivity.containerPos, + collectDrops = true, + minCollectAmount = 64 + ) + ) + } + + class NoSpaceLeftInInventoryException : Exception("No space left in inventory") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/storage/ShulkerTransaction.kt b/src/main/kotlin/com/lambda/client/activity/activities/storage/ShulkerTransaction.kt new file mode 100644 index 000000000..7f8e1cb4f --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/storage/ShulkerTransaction.kt @@ -0,0 +1,59 @@ +package com.lambda.client.activity.activities.storage + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.construction.core.BreakBlock +import com.lambda.client.activity.activities.storage.core.CloseContainer +import com.lambda.client.activity.activities.storage.core.ContainerWindowTransaction +import com.lambda.client.activity.activities.storage.core.PlaceContainer +import com.lambda.client.activity.activities.storage.types.ContainerAction +import com.lambda.client.activity.activities.storage.types.StackSelection +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.items.allSlots + +/** + * Push or pull item from a fitting shulker from inventory + */ +class ShulkerTransaction( + val action: ContainerAction, + val order: StackSelection +) : Activity() { + override fun SafeClientEvent.onInitialize() { + if (action == ContainerAction.PULL) { + player.allSlots.mapNotNull { + order.findShulkerToPull(it) + }.minByOrNull { it.second }?.first?.let { slot -> + addSubActivities( + PlaceContainer(StackSelection().apply { + selection = isItemStack(slot.stack) + }, open = true) + ) + return + } + } else { + player.allSlots.mapNotNull { + order.findShulkerToPush(it) + }.minByOrNull { it.second }?.first?.let { slot -> + addSubActivities( + PlaceContainer(StackSelection().apply { + selection = isItemStack(slot.stack) + }, open = true) + ) + return + } + } + } + + override fun SafeClientEvent.onChildSuccess(childActivity: Activity) { + if (childActivity !is PlaceContainer) return + + addSubActivities( + ContainerWindowTransaction(action, order), + CloseContainer(), + BreakBlock( + childActivity.containerPos, + collectDrops = true, + ignoreIgnored = true + ) + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/storage/StashTransaction.kt b/src/main/kotlin/com/lambda/client/activity/activities/storage/StashTransaction.kt new file mode 100644 index 000000000..6365444fd --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/storage/StashTransaction.kt @@ -0,0 +1,81 @@ +package com.lambda.client.activity.activities.storage + +import baritone.api.pathing.goals.GoalBlock +import baritone.api.pathing.goals.GoalGetToBlock +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.storage.core.CloseContainer +import com.lambda.client.activity.activities.storage.core.ContainerWindowTransaction +import com.lambda.client.activity.activities.storage.core.OpenContainer +import com.lambda.client.activity.activities.storage.types.ContainerAction +import com.lambda.client.activity.activities.storage.types.Stash +import com.lambda.client.activity.activities.storage.types.StackSelection +import com.lambda.client.activity.activities.travel.CustomGoal +import com.lambda.client.activity.types.LoopWhileActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.math.CoordinateConverter.asString +import com.lambda.client.util.math.VectorUtils.distanceTo +import com.lambda.client.util.text.MessageSendHelper +import net.minecraft.tileentity.TileEntity + +/** + * [StashTransaction] is an [Activity] that allows you to pull or push items from a [Stash]. + * @param orders The orders to pull or push items. + */ +class StashTransaction( + private val orders: Set> +) : LoopWhileActivity, Activity() { + override val loopWhile: SafeClientEvent.() -> Boolean = { orderQueue.isNotEmpty() } + override var currentLoops = 0 + + private val orderQueue = ArrayDeque(orders.sortedBy { it.second }) + // try different containers in case one is not matching the search + private val containerQueue = ArrayDeque() + + override fun SafeClientEvent.onInitialize() { + if (orderQueue.isEmpty()) { + success() + return + } + + // TODO: Use cached chests to instantly find the closest needed container (maybe even shared cache?) + + orderQueue.firstOrNull()?.first?.let { + val highestNonAirBlock = world.getTopSolidOrLiquidBlock(it.area.center) + + MessageSendHelper.sendWarningMessage("Player is not in the area ${it.area}! Moving to closest position (${highestNonAirBlock.asString()})...") + addSubActivities(CustomGoal(GoalBlock(highestNonAirBlock), + inGoal = { blockPos -> it.area.containedBlocks.contains(blockPos) }, timeout = 999999L) + ) + } + } + + override fun SafeClientEvent.onChildSuccess(childActivity: Activity) { + when (childActivity) { + is CustomGoal -> { + if (childActivity.goal !is GoalBlock) return + + orderQueue.removeFirstOrNull()?.let { order -> + val stashContainer = world.loadedTileEntityList.filter { + order.first.area.containedBlocks.contains(it.pos) + } + + if (stashContainer.isEmpty()) { + failedWith(NoContainerFoundInStashException()) + return + } + + stashContainer.minByOrNull { player.distanceTo(it.pos) }?.let { container -> + addSubActivities( + CustomGoal(GoalGetToBlock(container.pos)), + OpenContainer(container.pos), + ContainerWindowTransaction(order.second, order.third), + CloseContainer() + ) + } + } + } + } + } + + class NoContainerFoundInStashException : Exception("No chest found in area!") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/storage/StoreItemToEnderChest.kt b/src/main/kotlin/com/lambda/client/activity/activities/storage/StoreItemToEnderChest.kt new file mode 100644 index 000000000..f00b1d27c --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/storage/StoreItemToEnderChest.kt @@ -0,0 +1,10 @@ +package com.lambda.client.activity.activities.storage + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.storage.types.ItemInfo +import net.minecraft.item.ItemStack + +class StoreItemToEnderChest( + containerStack: ItemStack, + private val itemInfo: ItemInfo +) : Activity() \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/storage/core/CloseContainer.kt b/src/main/kotlin/com/lambda/client/activity/activities/storage/core/CloseContainer.kt new file mode 100644 index 000000000..fdd575505 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/storage/core/CloseContainer.kt @@ -0,0 +1,11 @@ +package com.lambda.client.activity.activities.storage.core + +import com.lambda.client.activity.Activity +import com.lambda.client.event.SafeClientEvent + +class CloseContainer : Activity() { + override fun SafeClientEvent.onInitialize() { + player.closeScreen() + success() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/storage/core/ContainerWindowTransaction.kt b/src/main/kotlin/com/lambda/client/activity/activities/storage/core/ContainerWindowTransaction.kt new file mode 100644 index 000000000..ba82958c2 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/storage/core/ContainerWindowTransaction.kt @@ -0,0 +1,89 @@ +package com.lambda.client.activity.activities.storage.core + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.inventory.core.QuickMoveSlot +import com.lambda.client.activity.activities.inventory.core.SwapWithSlot +import com.lambda.client.activity.activities.storage.types.* +import com.lambda.client.activity.seperatedSlots +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.module.modules.client.BuildTools +import com.lambda.client.util.items.countEmpty +import com.lambda.client.util.items.hotbarSlots +import com.lambda.client.util.text.MessageSendHelper +import net.minecraft.inventory.Container + +class ContainerWindowTransaction( + private val action: ContainerAction, + private val order: StackSelection +) : Activity() { + override fun SafeClientEvent.onInitialize() { + val seperatedSlots = player.openContainer.seperatedSlots + + val (fromSlots, toSlots) = if (action == ContainerAction.PULL) { + seperatedSlots.let { (first, second) -> Pair(first, second) } + } else { + seperatedSlots.let { (first, second) -> Pair(second, first) } + } + + val toMoveSlots = fromSlots.filter(order.filter) + + if (toMoveSlots.isEmpty()) { + if (order.count == 0) { + success() + return + } + + failedWith(NoItemFoundException()) + return + } + + if (toMoveSlots.size < order.count) { + failedWith(NotEnoughSlotsException()) + return + } + + val remainingSlots = if (order.count == 0) { + toMoveSlots + } else { + toMoveSlots.take(order.count) + } + + remainingSlots.forEach { fromSlot -> + if (toSlots.countEmpty() > 0) { + addSubActivities(QuickMoveSlot(fromSlot)) + return@forEach + } + + val ejectableSlots = toSlots.filter { slot -> + BuildTools.ejectList.contains(slot.stack.item.registryName.toString()) + } + + if (ejectableSlots.isEmpty()) { + failedWith(NoSpaceLeftInInventoryException()) + return@forEach + } + + ejectableSlots.firstOrNull()?.let { + val firstHotbarSlot = player.hotbarSlots.first() + + addSubActivities( + SwapWithSlot(it, firstHotbarSlot.hotbarSlot), + SwapWithSlot(fromSlot, firstHotbarSlot.hotbarSlot) + ) + } + } + } + + override fun SafeClientEvent.onChildFailure(childActivities: ArrayDeque, childException: Exception): Boolean { + if (childException !is QuickMoveSlot.ExceptionSlotNotEmpty) return false + + MessageSendHelper.sendWarningMessage("Quick move failed, container full") + success() + return true + } + + class NoSpaceLeftInInventoryException : Exception("No space left in inventory") + class NoItemFoundException : Exception("No item to move found") + class NotEnoughSlotsException : Exception("Not enough slots") + class ContainerNotKnownException(val container: Container) : Exception("Container ${container::class.simpleName} not known") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/storage/core/OpenContainer.kt b/src/main/kotlin/com/lambda/client/activity/activities/storage/core/OpenContainer.kt new file mode 100644 index 000000000..54b032e11 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/storage/core/OpenContainer.kt @@ -0,0 +1,65 @@ +package com.lambda.client.activity.activities.storage.core + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.types.AttemptActivity +import com.lambda.client.activity.types.RotatingActivity +import com.lambda.client.activity.types.TimeoutActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.event.events.PacketEvent +import com.lambda.client.util.math.RotationUtils.getRotationTo +import com.lambda.client.util.math.Vec2f +import com.lambda.client.util.math.VectorUtils.toVec3dCenter +import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.threads.onMainThreadSafe +import com.lambda.client.util.threads.safeListener +import com.lambda.client.util.world.getHitVec +import com.lambda.client.util.world.getHitVecOffset +import kotlinx.coroutines.launch +import net.minecraft.block.BlockContainer +import net.minecraft.network.play.client.CPacketPlayerTryUseItemOnBlock +import net.minecraft.network.play.server.SPacketWindowItems +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumHand +import net.minecraft.util.math.BlockPos + +class OpenContainer( + private val containerPos: BlockPos, + override var rotation: Vec2f? = null, + override val timeout: Long = 1000L, + override val maxAttempts: Int = 3, + override var usedAttempts: Int = 0, +) : RotatingActivity, TimeoutActivity, AttemptActivity, Activity() { + override fun SafeClientEvent.onInitialize() { + val currentState = world.getBlockState(containerPos) + + if (currentState.block !is BlockContainer) { + failedWith(BlockNotContainerException(currentState)) + return + } + + val diff = player.getPositionEyes(1f).subtract(containerPos.toVec3dCenter()) + val normalizedVec = diff.normalize() + + val side = EnumFacing.getFacingFromVector(normalizedVec.x.toFloat(), normalizedVec.y.toFloat(), normalizedVec.z.toFloat()) + val hitVecOffset = getHitVecOffset(side) + + rotation = getRotationTo(getHitVec(containerPos, side)) + + connection.sendPacket(CPacketPlayerTryUseItemOnBlock(containerPos, side, EnumHand.MAIN_HAND, hitVecOffset.x.toFloat(), hitVecOffset.y.toFloat(), hitVecOffset.z.toFloat())) + player.swingArm(EnumHand.MAIN_HAND) + } + + init { + safeListener { + if (it.packet !is SPacketWindowItems) return@safeListener + + defaultScope.launch { + onMainThreadSafe { + success() + } + } + } + } + + class BlockNotContainerException(blockState: Any) : Exception("Block $blockState is not a container") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/storage/core/PlaceContainer.kt b/src/main/kotlin/com/lambda/client/activity/activities/storage/core/PlaceContainer.kt new file mode 100644 index 000000000..fcc97c306 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/storage/core/PlaceContainer.kt @@ -0,0 +1,72 @@ +package com.lambda.client.activity.activities.storage.core + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.construction.core.PlaceBlock +import com.lambda.client.activity.activities.inventory.AcquireItemInActiveHand +import com.lambda.client.activity.activities.storage.types.ItemInfo +import com.lambda.client.activity.activities.storage.types.StackSelection +import com.lambda.client.activity.types.AttemptActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.EntityUtils.flooredPosition +import com.lambda.client.util.items.block +import com.lambda.client.util.math.VectorUtils +import com.lambda.client.util.math.VectorUtils.toVec3dCenter +import com.lambda.client.util.world.getVisibleSides +import com.lambda.client.util.world.isPlaceable +import com.lambda.client.util.world.isReplaceable +import net.minecraft.item.ItemStack +import net.minecraft.util.EnumFacing +import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.util.math.BlockPos + +class PlaceContainer( + private val selection: StackSelection, + private val open: Boolean = false, + override val maxAttempts: Int = 5, + override var usedAttempts: Int = 0 +) : AttemptActivity, Activity() { + var containerPos: BlockPos = BlockPos.ORIGIN + + override fun SafeClientEvent.onInitialize() { + containerPos = getContainerPos() ?: run { + failedWith(NoContainerPlacePositionFoundException()) + return + } + + addSubActivities( + AcquireItemInActiveHand(selection), + PlaceBlock(containerPos, selection.placementResult, ignoreProperties = true) + ) + + if (open) addSubActivities(OpenContainer(containerPos)) + } + + private fun SafeClientEvent.getContainerPos(): BlockPos? { + return VectorUtils.getBlockPosInSphere(player.positionVector, 4.25f).asSequence() + .filter { pos -> +// world.isPlaceable(pos, targetState.getSelectedBoundingBox(world, pos)) // TODO: Calculate correct resulting state of placed block to enable rotation checks + world.isPlaceable(pos, AxisAlignedBB(pos)) + && !world.getBlockState(pos.down()).isReplaceable + && world.isAirBlock(pos.up()) + && getVisibleSides(pos.down()).contains(EnumFacing.UP) + && pos.y >= player.flooredPosition.y + }.sortedWith( + compareByDescending { + secureScore(it) + }.thenBy { + player.positionVector.distanceTo(it.toVec3dCenter()) + } + ).firstOrNull() + } + + private fun SafeClientEvent.secureScore(pos: BlockPos): Int { + var safe = 0 + if (!world.getBlockState(pos.down().north()).isReplaceable) safe++ + if (!world.getBlockState(pos.down().east()).isReplaceable) safe++ + if (!world.getBlockState(pos.down().south()).isReplaceable) safe++ + if (!world.getBlockState(pos.down().west()).isReplaceable) safe++ + return safe + } + + class NoContainerPlacePositionFoundException : Exception("No position to place a container was found") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/storage/types/Area.kt b/src/main/kotlin/com/lambda/client/activity/activities/storage/types/Area.kt new file mode 100644 index 000000000..e38430b92 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/storage/types/Area.kt @@ -0,0 +1,49 @@ +package com.lambda.client.activity.activities.storage.types + +import com.lambda.client.util.math.CoordinateConverter.asString +import net.minecraft.client.entity.EntityPlayerSP +import net.minecraft.util.math.BlockPos + +/** + * [Area] is a data class that represents an area in the world. + * @param pos1 The first position of the area. + * @param pos2 The second position of the area. + */ +data class Area(val pos1: BlockPos, val pos2: BlockPos) { + val center: BlockPos + get() = BlockPos( + (pos1.x + pos2.x) / 2, + (pos1.y + pos2.y) / 2, + (pos1.z + pos2.z) / 2 + ) + + val containedBlocks: Set + get() = BlockPos.getAllInBox(pos1, pos2).toSet() + + val minWidth: Int + get() = minOf(maxX - minX + 1, maxZ - minZ + 1) + + fun closestBlockPos(player: EntityPlayerSP) = containedBlocks.minBy { + it.distanceSq(player.posX, player.posY, player.posZ) + } + + fun grow(amount: Int) = Area( + BlockPos(pos1.x - amount, pos1.y - amount, pos1.z - amount), + BlockPos(pos2.x + amount, pos2.y + amount, pos2.z + amount) + ) + + val minX: Int + get() = minOf(pos1.x, pos2.x) + val minY: Int + get() = minOf(pos1.y, pos2.y) + val minZ: Int + get() = minOf(pos1.z, pos2.z) + val maxX: Int + get() = maxOf(pos1.x, pos2.x) + val maxY: Int + get() = maxOf(pos1.y, pos2.y) + val maxZ: Int + get() = maxOf(pos1.z, pos2.z) + + override fun toString() = "Area(${center.asString()})" +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/storage/types/ContainerAction.kt b/src/main/kotlin/com/lambda/client/activity/activities/storage/types/ContainerAction.kt new file mode 100644 index 000000000..7c35a9b23 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/storage/types/ContainerAction.kt @@ -0,0 +1,5 @@ +package com.lambda.client.activity.activities.storage.types + +enum class ContainerAction { + PUSH, PULL +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/storage/types/ItemInfo.kt b/src/main/kotlin/com/lambda/client/activity/activities/storage/types/ItemInfo.kt new file mode 100644 index 000000000..21b7b57d3 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/storage/types/ItemInfo.kt @@ -0,0 +1,37 @@ +package com.lambda.client.activity.activities.storage.types + +import com.lambda.client.activity.getShulkerInventory +import net.minecraft.inventory.Slot +import net.minecraft.item.Item +import net.minecraft.item.ItemShulkerBox +import net.minecraft.item.ItemStack + +data class ItemInfo( + val item: Item, + val number: Int = 1, // 0 = all + val itemStack: ItemStack? = null, + val predicate: (ItemStack) -> Boolean = { true }, + val metadata: Int? = null, + var containedInShulker: Boolean = false +) { + val optimalStack: ItemStack + get() = ItemStack(item, number, metadata ?: 0) + + val slotFilter = { slot: Slot -> + if (containedInShulker) { + slot.stack.item is ItemShulkerBox && getShulkerInventory(slot.stack)?.any { + stackFilter(it) + } == true + } else stackFilter(slot.stack) + } + + val stackFilter = { stack: ItemStack -> + itemStack == null + && item == stack.item + && predicate(stack) + && (metadata == null || metadata == stack.metadata) + || (itemStack != null && ItemStack.areItemStacksEqual(itemStack, stack)) + } + + override fun toString() = "ItemInfo(item=$item, number=$number, itemStack=$itemStack, metadata=$metadata, containedInShulker=$containedInShulker)" +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/storage/types/StackSelection.kt b/src/main/kotlin/com/lambda/client/activity/activities/storage/types/StackSelection.kt new file mode 100644 index 000000000..f4d3ae343 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/storage/types/StackSelection.kt @@ -0,0 +1,176 @@ +package com.lambda.client.activity.activities.storage.types + +import com.lambda.client.activity.getShulkerInventory +import com.lambda.client.util.items.block +import com.lambda.client.util.items.item +import net.minecraft.block.Block +import net.minecraft.block.state.IBlockState +import net.minecraft.enchantment.Enchantment +import net.minecraft.enchantment.EnchantmentHelper +import net.minecraft.init.Blocks +import net.minecraft.inventory.Slot +import net.minecraft.item.Item +import net.minecraft.item.ItemStack + +/** + * [StackSelection] is a class that holds a predicate for matching [ItemStack]s. + * @param count The count of item stacks to be matched. + * @param selection The predicate for matching [ItemStack]s. + */ +class StackSelection( + var count: Int = DEFAULT_AMOUNT, + val inShulkerBox: Boolean = false +) { + lateinit var selection: (ItemStack) -> Boolean + + var item: Item? = null + private var metadata: Int? = null + private var damage: Int? = null + private var itemStack: ItemStack? = null + + val filter: (Slot) -> Boolean get() = { slot -> + if (inShulkerBox) { + getShulkerInventory(slot.stack)?.any { selection(it) } ?: false + } else selection(slot.stack) + } + + val optimalStack: ItemStack? + get() = item?.let { ItemStack(it, count, metadata ?: 0) } + + val placementResult: IBlockState + get() = itemStack?.item?.block?.defaultState + ?: optimalStack?.item?.block?.defaultState + ?: Blocks.AIR.defaultState + + /** + * returns a function that finds a shulker box to push matching items into. + */ + val findShulkerToPush = { slot: Slot -> + getShulkerInventory(slot.stack)?.let { inventory -> + if (inventory.all { selection(it) || it.isEmpty }) { + val storableItems = inventory.sumOf { +// if (it.isEmpty) item.itemStackLimit else it.maxStackSize - it.count + if (it.isEmpty) item?.itemStackLimit ?: 0 else 0 + } + + if (storableItems > 0) slot to storableItems else null + } else null + } + } + + /** + * returns a function that finds a shulker box to pull matching items from. + */ + val findShulkerToPull = { slot: Slot -> + getShulkerInventory(slot.stack)?.let { inventory -> + val usableItems = inventory.sumOf { if (selection(it)) it.count else 0 } + + if (usableItems > 0) slot to usableItems else null + } + } + + /** + * [isItem] returns a predicate that matches a specific [Item]. + * @param item The [Item] to be matched. + * @return A predicate that matches the [Item]. + */ + fun isItem(item: Item): (ItemStack) -> Boolean { + this.item = item + return { it.item == item } + } + + /** + * [isItem] returns a predicate that matches a specific [Item] instance. + * @param T The instance of [Item] to be matched. + * @return A predicate that matches the [Item]. + */ + inline fun isItem(): (ItemStack) -> Boolean = { it.item is T } + + /** + * [isBlock] returns a predicate that matches a specific [Block]. + * @param block The [Block] to be matched. + * @return A predicate that matches the [Block]. + */ + fun isBlock(block: Block): (ItemStack) -> Boolean { + item = block.item + return { it.item == block.item } + } + + /** + * [isItemStack] returns a predicate that matches a specific [ItemStack]. + * @param stack The [ItemStack] to be matched. + * @return A predicate that matches the [ItemStack]. + */ + fun isItemStack(stack: ItemStack): (ItemStack) -> Boolean { + this.itemStack = stack + return { ItemStack.areItemStacksEqual(it, stack) } + } + + /** + * [hasMetadata] returns a predicate that matches a specific `metadata`. + * @param metadata The `metadata` to be matched. + * @return A predicate that matches the `metadata`. + */ + fun hasMetadata(metadata: Int): (ItemStack) -> Boolean { + this.metadata = metadata + return { it.metadata == metadata } + } + + /** + * [hasDamage] returns a predicate that matches a specific damage value. + * @param damage The damage value to be matched. + * @return A predicate that matches the damage value. + */ + fun hasDamage(damage: Int): (ItemStack) -> Boolean { + this.damage = damage + return { it.itemDamage == damage } + } + + /** + * [hasEnchantment] returns a predicate that matches a specific [Enchantment] and level. + * @param enchantment The [Enchantment] to be matched. + * @param level The level to be matched (if -1 will look for any level above 0). + * @return A predicate that matches the [Enchantment] and `level`. + */ + fun hasEnchantment(enchantment: Enchantment, level: Int = -1): (ItemStack) -> Boolean = { + if (level < 0) EnchantmentHelper.getEnchantmentLevel(enchantment, it) > 0 + else EnchantmentHelper.getEnchantmentLevel(enchantment, it) == level + } + + /** + * Returns the negation of the original predicate. + * @return A new predicate that matches if the original predicate does not match. + */ + fun ((ItemStack) -> Boolean).not(): (ItemStack) -> Boolean { + return { !this(it) } + } + + /** + * Combines two predicates using the logical AND operator. + * @param otherPredicate The second predicate. + * @return A new predicate that matches if both input predicates match. + */ + infix fun ((ItemStack) -> Boolean).and(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean { + return { this(it) && otherPredicate(it) } + } + + /** + * Combines two predicates using the logical OR operator. + * @param otherPredicate The second predicate. + * @return A new predicate that matches if either input predicate matches. + */ + infix fun ((ItemStack) -> Boolean).or(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean { + return { this(it) || otherPredicate(it) } + } + + companion object { + const val DEFAULT_AMOUNT = 1 + val FULL_SHULKERS: (ItemStack) -> Boolean = { stack -> + getShulkerInventory(stack)?.none { it.isEmpty } == true + } + val EMPTY_SHULKERS: (ItemStack) -> Boolean = { stack -> + getShulkerInventory(stack)?.all { it.isEmpty } == true + } + val EVERYTHING: (ItemStack) -> Boolean = { true } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/storage/types/Stash.kt b/src/main/kotlin/com/lambda/client/activity/activities/storage/types/Stash.kt new file mode 100644 index 000000000..3fb48f89c --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/storage/types/Stash.kt @@ -0,0 +1,22 @@ +package com.lambda.client.activity.activities.storage.types + +import com.lambda.client.util.math.CoordinateConverter.asString +import net.minecraft.item.Item +import net.minecraft.util.math.BlockPos + +/** + * [Stash] is a data class that represents a stash of [Item]s. + * @param area The area that the stash is located in. + * @param items The items that are in the stash. + */ +data class Stash(val area: Area, val items: List) { + override fun toString() = "Stash(${ + items.joinToString { + it.registryName.toString().split(":").last() + } + })@(${area.center.asString()})" + + companion object { + val EMPTY = Stash(Area(BlockPos.ORIGIN, BlockPos.ORIGIN), emptyList()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/travel/CollectDrops.kt b/src/main/kotlin/com/lambda/client/activity/activities/travel/CollectDrops.kt new file mode 100644 index 000000000..202c9d446 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/travel/CollectDrops.kt @@ -0,0 +1,42 @@ +package com.lambda.client.activity.activities.travel + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.types.LoopWhileActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.math.VectorUtils.distanceTo +import net.minecraft.entity.item.EntityItem +import net.minecraft.item.Item +import net.minecraft.item.ItemStack + +class CollectDrops( + private val item: Item, + private val itemStack: ItemStack = ItemStack.EMPTY, + private val predicate: (ItemStack) -> Boolean = { true }, + private val maxRange: Float = 10.0f, + private val minAmount: Int = 1, + override var currentLoops: Int = 0, +) : LoopWhileActivity, Activity() { + private val SafeClientEvent.drops + get() = + world.loadedEntityList.filterIsInstance().filter { + player.distanceTo(it.positionVector) < maxRange + && it.item.item == item + && predicate(it.item) + && if (itemStack != ItemStack.EMPTY) ItemStack.areItemStacksEqual(it.item, itemStack) else true + } + + override val loopWhile: SafeClientEvent.() -> Boolean = { + drops.isNotEmpty() + } + + override fun SafeClientEvent.onInitialize() { + if (drops.isEmpty() || drops.sumOf { it.item.count } < minAmount) { + success() + return + } + + drops.minByOrNull { drop -> player.distanceTo(drop.positionVector) }?.let { drop -> + addSubActivities(CollectEntityItem(drop)) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/travel/CollectEntityItem.kt b/src/main/kotlin/com/lambda/client/activity/activities/travel/CollectEntityItem.kt new file mode 100644 index 000000000..e8a017bd8 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/travel/CollectEntityItem.kt @@ -0,0 +1,47 @@ +package com.lambda.client.activity.activities.travel + +import baritone.api.pathing.goals.GoalBlock +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.inventory.DumpSlot +import com.lambda.client.activity.types.TimeoutActivity +import com.lambda.client.module.modules.client.BuildTools +import com.lambda.client.util.BaritoneUtils +import com.lambda.client.util.items.countEmpty +import com.lambda.client.util.items.inventorySlots +import com.lambda.client.util.threads.safeListener +import net.minecraft.entity.item.EntityItem +import net.minecraftforge.fml.common.gameevent.TickEvent + +class CollectEntityItem( + private val entityItem: EntityItem, + override val timeout: Long = 20000L +) : TimeoutActivity, Activity() { + init { + safeListener { event -> + if (event.phase != TickEvent.Phase.START) return@safeListener + + if (!world.loadedEntityList.contains(entityItem)) { + success() + return@safeListener + } + + if (player.inventorySlots.countEmpty() > 0) { + BaritoneUtils.primary?.customGoalProcess?.setGoalAndPath(GoalBlock(entityItem.position)) + return@safeListener + } + + if (subActivities.filterIsInstance().isNotEmpty()) return@safeListener + + player.inventorySlots.firstOrNull { slot -> + BuildTools.ejectList.contains(slot.stack.item.registryName.toString()) + }?.let { slot -> + addSubActivities(DumpSlot(slot)) + return@safeListener + } + + onFailure(InventoryFullException()) + } + } + + class InventoryFullException : Exception("No empty slots or items to dump!") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/activities/travel/CustomGoal.kt b/src/main/kotlin/com/lambda/client/activity/activities/travel/CustomGoal.kt new file mode 100644 index 000000000..223a78a5d --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/activities/travel/CustomGoal.kt @@ -0,0 +1,33 @@ +package com.lambda.client.activity.activities.travel + +import baritone.api.pathing.goals.Goal +import com.lambda.client.activity.Activity +import com.lambda.client.activity.types.TimeoutActivity +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.BaritoneUtils +import com.lambda.client.util.EntityUtils.flooredPosition +import com.lambda.client.util.threads.safeListener +import net.minecraft.util.math.BlockPos +import net.minecraftforge.fml.common.gameevent.TickEvent + +class CustomGoal( + val goal: Goal, + val inGoal: (BlockPos) -> Boolean = { goal.isInGoal(it) }, + override val timeout: Long = 100000L +) : TimeoutActivity, Activity() { + override fun SafeClientEvent.onInitialize() { + if (!inGoal(player.flooredPosition)) return + + success() + } + + init { + safeListener { event -> + if (event.phase != TickEvent.Phase.START) return@safeListener + + BaritoneUtils.primary?.customGoalProcess?.setGoalAndPath(goal) + + if (inGoal(player.flooredPosition)) success() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/types/AttemptActivity.kt b/src/main/kotlin/com/lambda/client/activity/types/AttemptActivity.kt new file mode 100644 index 000000000..bfad7aaff --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/types/AttemptActivity.kt @@ -0,0 +1,31 @@ +package com.lambda.client.activity.types + +import com.lambda.client.LambdaMod +import com.lambda.client.activity.Activity +import com.lambda.client.event.SafeClientEvent + +interface AttemptActivity { + val maxAttempts: Int + var usedAttempts: Int + + companion object { + fun SafeClientEvent.checkAttempt(activity: Activity, causeException: Exception): Boolean { + if (activity !is AttemptActivity) return false + if (causeException is MaxAttemptsExceededException) return false + + with(activity) { + if (usedAttempts >= maxAttempts) { + failedWith(MaxAttemptsExceededException(usedAttempts, causeException)) + } else { + usedAttempts++ + LambdaMod.LOG.warn("$activityName caused ${causeException::class.simpleName}: ${causeException.message}. Attempt $usedAttempts of $maxAttempts restarting...") + initialize() + return true + } + } + return false + } + + class MaxAttemptsExceededException(usedAttempts: Int, causeException: Exception) : Exception("Exceeded $usedAttempts attempts caused by ${causeException::class.simpleName}: ${causeException.message}") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/types/BuildActivity.kt b/src/main/kotlin/com/lambda/client/activity/types/BuildActivity.kt new file mode 100644 index 000000000..1a4793632 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/types/BuildActivity.kt @@ -0,0 +1,39 @@ +package com.lambda.client.activity.types + +import com.lambda.client.util.color.ColorHolder + +interface BuildActivity { + var context: Context + var availability: Availability + var type: Type + var exposedSides: Int + var distance: Double + + enum class Context(val color: ColorHolder) { + IN_PROGRESS(ColorHolder(240, 222, 60)), + RESTOCK(ColorHolder(3, 252, 169)), + PICKUP(ColorHolder(252, 3, 207)), + NONE(ColorHolder(0, 0, 0, 0)), + PENDING(ColorHolder(11, 11, 175)) + } + + enum class Availability(val color: ColorHolder) { + VALID(ColorHolder(0, 255, 0, 0)), + WRONG_ITEM_SELECTED(ColorHolder(3, 252, 169)), + BLOCKED_BY_PLAYER(ColorHolder(252, 3, 207)), + NOT_IN_RANGE(ColorHolder(252, 3, 207)), + NOT_REPLACEABLE(ColorHolder(46, 0, 0, 30)), + NOT_EXPOSED(ColorHolder(46, 0, 0, 30)), + NEEDS_SUPPORT(ColorHolder(46, 0, 0, 30)), + NOT_VISIBLE(ColorHolder(46, 0, 0, 30)), + NEEDS_LIQUID_HANDLING(ColorHolder(50, 12, 112)), + NONE(ColorHolder(11, 11, 11)) + } + + enum class Type(val color: ColorHolder) { + LIQUID_FILL(ColorHolder(114, 27, 255)), + BREAK_BLOCK(ColorHolder(222, 0, 0)), + IS_SUPPORT(ColorHolder(0, 166, 0)), + PLACE_BLOCK(ColorHolder(35, 188, 254)), + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/types/DelayedActivity.kt b/src/main/kotlin/com/lambda/client/activity/types/DelayedActivity.kt new file mode 100644 index 000000000..3845a2f88 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/types/DelayedActivity.kt @@ -0,0 +1,20 @@ +package com.lambda.client.activity.types + +import com.lambda.client.activity.Activity +import com.lambda.client.event.SafeClientEvent + +interface DelayedActivity { + val delay: Long + + fun SafeClientEvent.onDelayedActivity() + + companion object { + fun SafeClientEvent.checkDelayed(activity: Activity) { + if (activity !is DelayedActivity) return + + with(activity) { + if (age > delay) onDelayedActivity() + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/types/LoopWhileActivity.kt b/src/main/kotlin/com/lambda/client/activity/types/LoopWhileActivity.kt new file mode 100644 index 000000000..21c03d4f1 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/types/LoopWhileActivity.kt @@ -0,0 +1,24 @@ +package com.lambda.client.activity.types + +import com.lambda.client.activity.Activity +import com.lambda.client.event.SafeClientEvent + +interface LoopWhileActivity { + val loopWhile: SafeClientEvent.() -> Boolean + var currentLoops: Int + + companion object { + fun SafeClientEvent.checkLoopingUntil(activity: Activity) { + if (activity !is LoopWhileActivity) return + + with(activity) { + if (!loopWhile()) return + + currentLoops++ + status = Activity.Status.UNINITIALIZED + parent?.subActivities?.add(activity) +// LambdaMod.LOG.info("Looping $name ($currentLoops)") + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/types/RenderAABBActivity.kt b/src/main/kotlin/com/lambda/client/activity/types/RenderAABBActivity.kt new file mode 100644 index 000000000..341ddc13e --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/types/RenderAABBActivity.kt @@ -0,0 +1,69 @@ +package com.lambda.client.activity.types + +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.manager.managers.ActivityManager +import com.lambda.client.util.EntityUtils +import com.lambda.client.util.color.ColorHolder +import com.lambda.client.util.graphics.LambdaTessellator +import net.minecraft.entity.Entity +import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.util.math.BlockPos + +interface RenderAABBActivity { + val aabbCompounds: MutableSet + + companion object { + val normalizedRender: MutableSet = mutableSetOf() + + fun SafeClientEvent.checkAABBRender() { + normalizedRender.clear() + + ActivityManager + .allSubActivities + .filterIsInstance() + .forEach { activity -> + activity.aabbCompounds.forEach { compound -> + when (compound) { + is RenderAABB -> normalizedRender.add(compound) + is RenderBlockPos -> { + with(compound) { + normalizedRender.add(toRenderAABB()) + } + } + + is RenderEntity -> { + with(compound) { + normalizedRender.add(toRenderAABB()) + } + } + } + } + } + } + + interface RenderAABBCompound + + data class RenderAABB( + var renderAABB: AxisAlignedBB, + var color: ColorHolder + ) : RenderAABBCompound + + data class RenderBlockPos( + var renderBlockPos: BlockPos, + var color: ColorHolder + ) : RenderAABBCompound { + fun SafeClientEvent.toRenderAABB() = + RenderAABB(world.getBlockState(renderBlockPos).getSelectedBoundingBox(world, renderBlockPos), color) + } + + data class RenderEntity( + var renderEntity: Entity, + var color: ColorHolder + ) : RenderAABBCompound { + fun toRenderAABB() = + RenderAABB(renderEntity.renderBoundingBox.offset( + EntityUtils.getInterpolatedAmount(renderEntity, LambdaTessellator.pTicks()) + ), color) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/types/RenderOverlayTextActivity.kt b/src/main/kotlin/com/lambda/client/activity/types/RenderOverlayTextActivity.kt new file mode 100644 index 000000000..7ef642f38 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/types/RenderOverlayTextActivity.kt @@ -0,0 +1,47 @@ +package com.lambda.client.activity.types + +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.manager.managers.ActivityManager +import com.lambda.client.module.modules.client.BuildTools.maxDebugAmount +import com.lambda.client.module.modules.client.BuildTools.maxDebugRange +import com.lambda.client.module.modules.client.BuildTools.showDebugRender +import com.lambda.client.util.color.ColorHolder +import com.lambda.client.util.math.VectorUtils.distanceTo +import net.minecraft.util.math.Vec3d + +interface RenderOverlayTextActivity { + val overlayTexts: MutableSet + + companion object { + val normalizedRender: MutableSet = mutableSetOf() + + fun SafeClientEvent.checkOverlayRender() { + if (!showDebugRender) return + + normalizedRender.clear() + + ActivityManager + .allSubActivities + .filterIsInstance() + .forEach { activity -> + activity.overlayTexts + .sortedBy { player.distanceTo(it.origin) } + .filter { player.distanceTo(it.origin) < maxDebugRange } + .take(maxDebugAmount) + .forEach { compound -> + normalizedRender.add(compound) + } + } + } + + interface RenderOverlayTextCompound + + data class RenderOverlayText( + var text: String, + var color: ColorHolder = ColorHolder(255, 255, 255), + var origin: Vec3d, + var index: Int = 0, + var scale: Float = 1f, + ) : RenderOverlayTextCompound + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/types/RepeatingActivity.kt b/src/main/kotlin/com/lambda/client/activity/types/RepeatingActivity.kt new file mode 100644 index 000000000..c2d46b0a3 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/types/RepeatingActivity.kt @@ -0,0 +1,26 @@ +package com.lambda.client.activity.types + +import com.lambda.client.activity.Activity +import com.lambda.client.event.ListenerManager + +interface RepeatingActivity { + val maximumRepeats: Int + var repeated: Int + + companion object { + fun checkRepeat(activity: Activity) { + if (activity !is RepeatingActivity) return + + with(activity) { + if (repeated++ >= maximumRepeats && maximumRepeats != 0) { + ListenerManager.unregister(activity) + return + } + + status = Activity.Status.UNINITIALIZED + parent?.subActivities?.add(activity) +// LambdaMod.LOG.info("Looping $name [$currentLoops/${if (maxLoops == 0) "∞" else maxLoops}] ") + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/types/RotatingActivity.kt b/src/main/kotlin/com/lambda/client/activity/types/RotatingActivity.kt new file mode 100644 index 000000000..1152b59bd --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/types/RotatingActivity.kt @@ -0,0 +1,23 @@ +package com.lambda.client.activity.types + +import com.lambda.client.activity.Activity +import com.lambda.client.manager.managers.PlayerPacketManager +import com.lambda.client.util.math.Vec2f + +interface RotatingActivity { + var rotation: Vec2f? + + companion object { + fun checkRotating(activity: Activity) { + if (activity !is RotatingActivity) return + + with(activity) { + rotation?.let { + PlayerPacketManager.sendPlayerPacket { + rotate(it) + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/types/TimedActivity.kt b/src/main/kotlin/com/lambda/client/activity/types/TimedActivity.kt new file mode 100644 index 000000000..d1fe6ae32 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/types/TimedActivity.kt @@ -0,0 +1,5 @@ +package com.lambda.client.activity.types + +interface TimedActivity { + var earliestFinish: Long +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/activity/types/TimeoutActivity.kt b/src/main/kotlin/com/lambda/client/activity/types/TimeoutActivity.kt new file mode 100644 index 000000000..c48ed9848 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/activity/types/TimeoutActivity.kt @@ -0,0 +1,22 @@ +package com.lambda.client.activity.types + +import com.lambda.client.activity.Activity +import com.lambda.client.event.SafeClientEvent + +interface TimeoutActivity { + val timeout: Long + + companion object { + fun SafeClientEvent.checkTimeout(activity: Activity) { + if (activity !is TimeoutActivity) return + + with(activity) { + if (age <= timeout) return + + failedWith(TimeoutException(age, timeout)) + } + } + + class TimeoutException(age: Long, timeout: Long) : Exception("Exceeded maximum age ${age}ms / ${timeout}ms") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/command/commands/BuildToolsCommand.kt b/src/main/kotlin/com/lambda/client/command/commands/BuildToolsCommand.kt new file mode 100644 index 000000000..45ad09756 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/command/commands/BuildToolsCommand.kt @@ -0,0 +1,133 @@ +package com.lambda.client.command.commands + +import com.lambda.client.command.ClientCommand +import com.lambda.client.module.modules.client.BuildTools +import com.lambda.client.util.text.MessageSendHelper +import net.minecraft.init.Items + +object BuildToolsCommand : ClientCommand( + name = "buildtools", + alias = arrayOf("bt", "build"), + description = "Customize settings of BuildTools" +) { + init { + literal("ignore") { + literal("add", "new", "+") { + block("block") { blockArg -> + execute("Adds a block to ignore list") { + if (BuildTools.ignoreBlocks.add(blockArg.value.registryName.toString())) { + MessageSendHelper.sendChatMessage("Added &7${blockArg.value.localizedName}&r to ignore list.") + } else { + MessageSendHelper.sendChatMessage("&7${blockArg.value.localizedName}&r is already ignored.") + } + } + } + } + + literal("remove", "rem", "-", "del") { + block("block") { blockArg -> + execute("Removes a block from ignore list") { + if (BuildTools.ignoreBlocks.remove(blockArg.value.registryName.toString())) { + MessageSendHelper.sendChatMessage("Removed &7${blockArg.value.localizedName}&r from ignore list.") + } else { + MessageSendHelper.sendChatMessage("&7${blockArg.value.localizedName}&r is not yet ignored.") + } + } + } + } + + literal("reset", "clear") { + execute("Resets the ignore list") { + BuildTools.ignoreBlocks.clear() + MessageSendHelper.sendChatMessage("Reset ignore list.") + } + } + + literal("default") { + execute("Adds all default blocks to ignore list") { + BuildTools.ignoreBlocks.addAll(BuildTools.defaultIgnoreBlocks) + MessageSendHelper.sendChatMessage("Added all default blocks to ignore list.") + } + } + + execute("Lists all ignored blocks") { + if (BuildTools.ignoreBlocks.isEmpty()) { + MessageSendHelper.sendChatMessage("No blocks are ignored.") + return@execute + } + + MessageSendHelper.sendChatMessage("Ignored blocks: ${BuildTools.ignoreBlocks.joinToString(", ")}") + } + } + + literal("eject") { + literal("add", "new", "+") { + block("block") { blockArg -> + execute("Adds a block to eject list") { + val added = BuildTools.ejectList.add(blockArg.value.registryName.toString()) + if (added) { + MessageSendHelper.sendChatMessage("Added &7${blockArg.value.localizedName}&r to eject list.") + } else { + MessageSendHelper.sendChatMessage("&7${blockArg.value.localizedName}&r is already in eject list.") + } + } + } + } + + literal("remove", "rem", "-", "del") { + block("block") { blockArg -> + execute("Removes a block from eject list") { + val removed = BuildTools.ejectList.remove(blockArg.value.registryName.toString()) + if (removed) { + MessageSendHelper.sendChatMessage("Removed &7${blockArg.value.localizedName}&r from eject list.") + } else { + MessageSendHelper.sendChatMessage("&7${blockArg.value.localizedName}&r is not yet in the eject list.") + } + } + } + } + + literal("reset", "clear") { + execute("Resets the eject list") { + BuildTools.ejectList.clear() + MessageSendHelper.sendChatMessage("Reset eject list.") + } + } + + literal("default") { + execute("Adds all default blocks to eject list") { + BuildTools.ejectList.addAll(BuildTools.defaultEjectList) + MessageSendHelper.sendChatMessage("Added all default blocks to eject list.") + } + } + + execute("Lists all blocks in eject list") { + if (BuildTools.ejectList.isEmpty()) { + MessageSendHelper.sendChatMessage("No blocks are in the eject list.") + return@execute + } + MessageSendHelper.sendChatMessage("Eject list: ${BuildTools.ejectList.joinToString(", ")}") + } + } + + literal("food") { + item("item") { itemArg -> + execute("Sets the food item") { + BuildTools.defaultFood = itemArg.value + MessageSendHelper.sendChatMessage("Set food item to &7${itemArg.value.registryName}&r.") + } + } + + literal("default") { + execute("Sets the food item to default") { + BuildTools.defaultFood = Items.GOLDEN_APPLE + MessageSendHelper.sendChatMessage("Set food item to default.") + } + } + + execute("Shows the food item") { + MessageSendHelper.sendChatMessage("Food item: &7${BuildTools.defaultFood.registryName}&r") + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/command/commands/HighwayToolsCommand.kt b/src/main/kotlin/com/lambda/client/command/commands/HighwayToolsCommand.kt new file mode 100644 index 000000000..572c698e0 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/command/commands/HighwayToolsCommand.kt @@ -0,0 +1,39 @@ +package com.lambda.client.command.commands + +import com.lambda.client.command.ClientCommand +import com.lambda.client.module.modules.misc.HighwayTools +import com.lambda.client.util.text.MessageSendHelper + +object HighwayToolsCommand : ClientCommand( + name = "highwaytools", + alias = arrayOf("ht", "hwt", "high"), + description = "Customize settings of HighwayTools." +) { + init { + literal("material", "mat") { + block("block") { blockArg -> + execute("Sets a block as main material") { + HighwayTools.material = blockArg.value + MessageSendHelper.sendChatMessage("Set your building material to &7${blockArg.value.localizedName}&r.") + } + } + } + + literal("distance") { + int("distance") { distanceArg -> + execute("Sets the distance between the player and the highway") { + HighwayTools.distance = distanceArg.value + MessageSendHelper.sendChatMessage("Set your distance to &7${distanceArg.value}&r.") + } + } + } + + literal("start") { + executeSafe("Starts the highway") { + with(HighwayTools) { + start() + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/command/commands/WorldEaterCommand.kt b/src/main/kotlin/com/lambda/client/command/commands/WorldEaterCommand.kt new file mode 100644 index 000000000..a2e2a1722 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/command/commands/WorldEaterCommand.kt @@ -0,0 +1,163 @@ +package com.lambda.client.command.commands + +import com.lambda.client.activity.activities.storage.types.Area +import com.lambda.client.activity.activities.storage.types.Stash +import com.lambda.client.command.ClientCommand +import com.lambda.client.command.CommandManager +import com.lambda.client.module.modules.misc.WorldEater +import com.lambda.client.module.modules.misc.WorldEater.info +import com.lambda.client.util.text.MessageSendHelper +import net.minecraft.item.Item + +object WorldEaterCommand : ClientCommand( + name = "worldeater", + alias = arrayOf("we"), + description = "World eater command" +) { + init { + literal("start") { + executeSafe("Start world eater") { + with(WorldEater) { + clearAllAreas() + } + } + } + + literal("stop") { + executeSafe("Stop world eater") { + WorldEater.ownedActivity?.let { + with(it) { + cancel() + } + } + } + } + + literal("pickup") { + literal("add", "new", "+") { + item("item") { itemArg -> + execute("Add item to pickup list") { + if (WorldEater.collectables.add(itemArg.value)) { + MessageSendHelper.sendChatMessage("Added &7${itemArg.value.registryName}&r to pickup list.") + } else { + MessageSendHelper.sendChatMessage("&7${itemArg.value.registryName}&r is already on the pickup list.") + } + } + } + } + + literal("remove") { + item("item") { itemArg -> + execute("Remove item from pickup list") { + if (WorldEater.collectables.remove(itemArg.value)) { + MessageSendHelper.sendChatMessage("Removed &7${itemArg.value.registryName}&r from pickup list.") + } else { + MessageSendHelper.sendChatMessage("&7${itemArg.value.registryName}&r is not on the pickup list.") + } + } + } + } + } + + literal("quarry") { + literal("add", "new", "+") { + blockPos("pos1") { pos1 -> + blockPos("pos2") { pos2 -> + execute("Adds excavating area") { + val area = Area(pos1.value, pos2.value) + + WorldEater.quarries.add(area) + MessageSendHelper.sendChatMessage("Added excavating area $area") + } + } + } + } + literal("remove", "rem", "-") { + int("id") { id -> + execute("Removes excavating area") { + val removed = WorldEater.quarries.value.removeAt(id.value) + MessageSendHelper.sendChatMessage("Removed excavating area $removed") + } + } + } + execute("Shows all quarries") { + MessageSendHelper.sendChatMessage("Quarries:") + WorldEater.quarries.forEachIndexed { index, volume -> + MessageSendHelper.sendChatMessage(" &7$index&r: $volume") + } + } + } + + literal("stash") { + literal("add", "new", "+") { + blockPos("pos1") { pos1 -> + blockPos("pos2") { pos2 -> + greedy("items") { + execute("Adds stash area") { + val safeArgs = CommandManager.tryParseArgument(args.joinToString(" ")) ?: return@execute + + val items = safeArgs.mapNotNull { Item.getByNameOrId(it) } + val stash = Stash(Area(pos1.value, pos2.value), items) + + WorldEater.stashes.value.add(stash) + MessageSendHelper.sendChatMessage("Added stash $stash") + } + } + } + } + } + literal("remove", "rem", "-") { + int("id") { id -> + execute("Removes stash area") { + val removed = WorldEater.stashes.value.removeAt(id.value) + MessageSendHelper.sendChatMessage("Removed stash area $removed") + } + } + } + execute("Shows all stashes") { + MessageSendHelper.sendChatMessage("Stashes:") + WorldEater.stashes.forEachIndexed { index, stash -> + MessageSendHelper.sendChatMessage(" &7$index&r: $stash") + } + } + } + + literal("dropOff") { + literal("add", "new", "+") { + blockPos("pos1") { pos1 -> + blockPos("pos2") { pos2 -> + greedy("items") { + execute("Adds drop off area") { + val safeArgs = CommandManager.tryParseArgument(args.joinToString(" ")) ?: return@execute + + val items = safeArgs.mapNotNull { Item.getByNameOrId(it) } + val dropOff = Stash(Area(pos1.value, pos2.value), items) + + WorldEater.dropOff.value.add(dropOff) + MessageSendHelper.sendChatMessage("Added drop-off area $dropOff") + } + } + } + } + } + literal("remove", "rem", "-") { + int("id") { id -> + execute("Removes excavating area") { + val removed = WorldEater.dropOff.value.removeAt(id.value) + MessageSendHelper.sendChatMessage("Removed drop-off area $removed") + } + } + } + execute("Shows all drop-off areas") { + MessageSendHelper.sendChatMessage("Stashes:") + WorldEater.dropOff.forEachIndexed { index, dropOff -> + MessageSendHelper.sendChatMessage(" &7$index&r: $dropOff") + } + } + } + + executeSafe("General setting info") { + MessageSendHelper.sendChatMessage(info()) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/event/ListenerManager.kt b/src/main/kotlin/com/lambda/client/event/ListenerManager.kt index 4ef7a8ba4..f78fbf2c4 100644 --- a/src/main/kotlin/com/lambda/client/event/ListenerManager.kt +++ b/src/main/kotlin/com/lambda/client/event/ListenerManager.kt @@ -10,9 +10,9 @@ import java.util.concurrent.CopyOnWriteArrayList */ object ListenerManager { - private val listenerMap = ConcurrentHashMap>>() + val listenerMap = ConcurrentHashMap>>() - private val asyncListenerMap = ConcurrentHashMap>>() + val asyncListenerMap = ConcurrentHashMap>>() /** * Register the [listener] to the [ListenerManager] diff --git a/src/main/kotlin/com/lambda/client/event/listener/ListenerImpl.kt b/src/main/kotlin/com/lambda/client/event/listener/ListenerImpl.kt index e0aede6da..861e0a646 100644 --- a/src/main/kotlin/com/lambda/client/event/listener/ListenerImpl.kt +++ b/src/main/kotlin/com/lambda/client/event/listener/ListenerImpl.kt @@ -22,7 +22,7 @@ inline fun Any.asyncListener(noinline function: suspend (T) -> /** * Create and register a new async listener for this object - * Must be used with Kotlinx Coroutine and a implementation of [IAsyncEventBus] + * Must be used with Kotlinx Coroutine and an implementation of [IAsyncEventBus] * * @param T type of the target event * @param clazz class of the target event @@ -57,7 +57,7 @@ fun Any.listener(priority: Int = DEFAULT_PRIORITY, clazz: Class, fu /** * Implementation of [AbstractListener] with suspend block - * Must be used with Kotlinx Coroutine and a implementation of [IAsyncEventBus] + * Must be used with Kotlinx Coroutine and an implementation of [IAsyncEventBus] */ class AsyncListener( owner: Any, diff --git a/src/main/kotlin/com/lambda/client/gui/hudgui/elements/client/ActivityManagerHud.kt b/src/main/kotlin/com/lambda/client/gui/hudgui/elements/client/ActivityManagerHud.kt new file mode 100644 index 000000000..e792026c9 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/gui/hudgui/elements/client/ActivityManagerHud.kt @@ -0,0 +1,65 @@ +package com.lambda.client.gui.hudgui.elements.client + +import com.lambda.client.activity.Activity +import com.lambda.client.event.ListenerManager +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.gui.hudgui.LabelHud +import com.lambda.client.manager.managers.ActivityManager +import org.apache.commons.lang3.time.DurationFormatUtils + +internal object ActivityManagerHud: LabelHud( + name = "ActivityManager", + category = Category.MISC, + description = "Display current activities." +) { + val maxEntries by setting("Max Entries", 15, 1..100, 1) + val anonymize by setting("Anonymize", false) + private val details by setting("Details", false) + private var startTime = 0L + + override fun SafeClientEvent.updateText() { + if (ActivityManager.hasNoSubActivities) { + startTime = 0L + return + } + + if (startTime == 0L) startTime = System.currentTimeMillis() + + val runTimeMS = System.currentTimeMillis() - startTime + val runTimeS = (runTimeMS + 1) / 1000.0 + + with(ActivityManager) { + displayText.add("Runtime", secondaryColor) + displayText.addLine(DurationFormatUtils.formatDuration(runTimeMS, "HH:mm:ss,SSS"), primaryColor) + displayText.add("Amount", secondaryColor) + displayText.add(ActivityManager.allSubActivities.size.toString(), primaryColor) + displayText.add("Current", secondaryColor) + displayText.addLine(getCurrentActivity().activityName, primaryColor) + displayText.add("Total Placed", secondaryColor) + displayText.add(totalBlocksPlaced.toString(), primaryColor) + displayText.add("Total Broken", secondaryColor) + displayText.add(totalBlocksBroken.toString(), primaryColor) + displayText.add("Place/s", secondaryColor) + displayText.add("%.2f".format(totalBlocksPlaced / runTimeS), primaryColor) + displayText.add("Break/s", secondaryColor) + displayText.addLine("%.2f".format(totalBlocksBroken / runTimeS), primaryColor) + + appendInfo(displayText, primaryColor, secondaryColor, details) + } + + val sync = ListenerManager.listenerMap.keys.filterIsInstance() + val async = ListenerManager.asyncListenerMap.keys.filterIsInstance() + + if (sync.isNotEmpty() || async.isNotEmpty()) { + displayText.addLine("") + displayText.addLine("Subscribers:") + if (sync.isNotEmpty()) displayText.addLine("SYNC ${sync.take(maxEntries).map { it::class.simpleName }}${if (sync.size > maxEntries) "..." else ""}") + if (async.isNotEmpty()) displayText.addLine("ASYNC ${async.take(maxEntries).map { it::class.simpleName }}${if (async.size > maxEntries) "..." else ""}") + } + } + + // STATS? + + var totalBlocksPlaced = 0 + var totalBlocksBroken = 0 +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/gui/hudgui/elements/misc/WorldEaterHud.kt b/src/main/kotlin/com/lambda/client/gui/hudgui/elements/misc/WorldEaterHud.kt new file mode 100644 index 000000000..5543db972 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/gui/hudgui/elements/misc/WorldEaterHud.kt @@ -0,0 +1,27 @@ +package com.lambda.client.gui.hudgui.elements.misc + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.construction.core.BuildStructure +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.gui.hudgui.LabelHud +import com.lambda.client.module.modules.misc.WorldEater + +internal object WorldEaterHud : LabelHud( + name = "WorldEater", + category = Category.MISC, + description = "Statistics about the world eater." +) { + override fun SafeClientEvent.updateText() { + if (WorldEater.ownedActivity == null) { + displayText.addLine("Not running", primaryColor) + return + } + + with(WorldEater) { + displayText.add("Progress", secondaryColor) + displayText.addLine("${"%.3f".format(quarries.value.sumOf { it.containedBlocks.count { pos -> world.isAirBlock(pos) } }.toFloat() / quarries.value.sumOf { it.containedBlocks.size } * 100)}%", primaryColor) + displayText.add("Layers left", secondaryColor) + displayText.addLine("${ownedActivity?.subActivities?.filterIsInstance()?.count { it.status == Activity.Status.UNINITIALIZED }}", primaryColor) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/gui/rgui/WindowComponent.kt b/src/main/kotlin/com/lambda/client/gui/rgui/WindowComponent.kt index f2d10d173..9c32031a4 100644 --- a/src/main/kotlin/com/lambda/client/gui/rgui/WindowComponent.kt +++ b/src/main/kotlin/com/lambda/client/gui/rgui/WindowComponent.kt @@ -132,8 +132,8 @@ open class WindowComponent( onResize() } else if (draggableHeight == height || relativeClickPos.y <= draggableHeight) { - posX = roundOnGrid(preDragPos.x + draggedDist.x).coerceIn(.0f, mc.displayWidth - width) - posY = roundOnGrid(preDragPos.y + draggedDist.y).coerceIn(.0f, mc.displayHeight - height) + posX = roundOnGrid(preDragPos.x + draggedDist.x).coerceIn(.0f, max(mc.displayWidth - width, 0.01f)) + posY = roundOnGrid(preDragPos.y + draggedDist.y).coerceIn(.0f, max(mc.displayHeight - height, 0.01f)) onReposition() } diff --git a/src/main/kotlin/com/lambda/client/manager/managers/ActivityManager.kt b/src/main/kotlin/com/lambda/client/manager/managers/ActivityManager.kt new file mode 100644 index 000000000..68bd062c2 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/manager/managers/ActivityManager.kt @@ -0,0 +1,242 @@ +package com.lambda.client.manager.managers + +import com.lambda.client.activity.Activity +import com.lambda.client.activity.activities.storage.* +import com.lambda.client.activity.activities.storage.types.* +import com.lambda.client.activity.getShulkerInventory +import com.lambda.client.activity.types.RenderAABBActivity +import com.lambda.client.activity.types.RenderAABBActivity.Companion.checkAABBRender +import com.lambda.client.activity.types.RenderOverlayTextActivity +import com.lambda.client.activity.types.RenderOverlayTextActivity.Companion.checkOverlayRender +import com.lambda.client.activity.types.TimedActivity +import com.lambda.client.event.LambdaEventBus +import com.lambda.client.event.ListenerManager +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.event.events.RenderOverlayEvent +import com.lambda.client.event.events.RenderWorldEvent +import com.lambda.client.event.listener.listener +import com.lambda.client.manager.Manager +import com.lambda.client.module.modules.client.BuildTools +import com.lambda.client.module.modules.client.BuildTools.executionCountPerTick +import com.lambda.client.module.modules.client.BuildTools.textScale +import com.lambda.client.module.modules.client.BuildTools.tickDelay +import com.lambda.client.module.modules.combat.KillAura +import com.lambda.client.module.modules.misc.WorldEater +import com.lambda.client.module.modules.player.AutoEat +import com.lambda.client.util.BaritoneUtils +import com.lambda.client.util.TickTimer +import com.lambda.client.util.TimeUnit +import com.lambda.client.util.graphics.ESPRenderer +import com.lambda.client.util.graphics.GlStateUtils +import com.lambda.client.util.graphics.ProjectionUtils +import com.lambda.client.util.graphics.font.FontRenderAdapter +import com.lambda.client.util.items.* +import com.lambda.client.util.math.CoordinateConverter.asString +import com.lambda.client.util.math.VectorUtils.distanceTo +import com.lambda.client.util.text.MessageSendHelper +import com.lambda.client.util.threads.safeListener +import net.minecraft.init.Blocks +import net.minecraft.init.Items +import net.minecraft.item.Item +import net.minecraftforge.fml.common.gameevent.TickEvent +import org.lwjgl.opengl.GL11 +import scala.tools.nsc.backend.icode.analysis.TypeFlowAnalysis.MethodTFA.Gen +import java.util.* + +object ActivityManager : Manager, Activity() { + private val renderer = ESPRenderer() + const val MAX_DEPTH = 25 + private val tickTimer = TickTimer(TimeUnit.TICKS) + + init { + safeListener { event -> + if (hasNoSubActivities + || event.phase != TickEvent.Phase.START + ) return@safeListener + + /* life support systems */ + if (AutoEat.eating) return@safeListener + if (KillAura.isActive()) return@safeListener + if (!player.onGround) return@safeListener + + if (BuildTools.storageManagement) maintainInventory() + + allSubActivities + .filter { it.status == Status.RUNNING && it.subActivities.isEmpty() } + .forEach { + with(it) { + updateTypesOnTick(it) + } + } + + if (!tickTimer.tick(tickDelay * 1L)) return@safeListener + + var lastActivity: Activity? = null + + BaritoneUtils.settings?.allowInventory?.value = false + + repeat(executionCountPerTick) { + val current = getCurrentActivity() + + with(current) { + // ToDo: Find a working way to guarantee specific age of activity + (lastActivity as? TimedActivity)?.let { + if (age < it.earliestFinish) return@repeat + } + + updateActivity() + checkOverlayRender() + checkAABBRender() + } + + lastActivity = current + } + } + + listener { + if (hasNoSubActivities) return@listener + + renderer.aFilled = BuildTools.aFilled + renderer.aOutline = BuildTools.aOutline + renderer.thickness = BuildTools.thickness + + RenderAABBActivity.normalizedRender + .forEach { renderAABB -> + renderer.add(renderAABB.renderAABB, renderAABB.color) + } + + renderer.render(true) + } + + listener { + if (hasNoSubActivities) return@listener + + GlStateUtils.rescaleActual() + + RenderOverlayTextActivity.normalizedRender.forEach { renderText -> + GL11.glPushMatrix() + val screenPos = ProjectionUtils.toScreenPos(renderText.origin) + GL11.glTranslated(screenPos.x, screenPos.y, 0.0) + GL11.glScalef(textScale * 2.0f, textScale * 2.0f, 1.0f) + + val halfWidth = FontRenderAdapter.getStringWidth(renderText.text) / -2.0f + val lineHeight = FontRenderAdapter.getFontHeight() + 2.0f + val yLift = lineHeight * 3 / 2 + + FontRenderAdapter.drawString( + renderText.text, + halfWidth, + lineHeight * renderText.index - yLift, + color = renderText.color + ) + + GL11.glPopMatrix() + } + GlStateUtils.rescaleMc() + } + } + + override fun getCurrentActivity() = + subActivities.maxByOrNull { it.owner?.modulePriority ?: 0 }?.getCurrentActivity() ?: this + + fun reset() { + ListenerManager.listenerMap.keys.filterIsInstance().forEach { + it.parent?.let { _ -> + LambdaEventBus.unsubscribe(it) + ListenerManager.unregister(it) + } + } + ListenerManager.asyncListenerMap.keys.filterIsInstance().forEach { + it.parent?.let { _ -> + LambdaEventBus.unsubscribe(it) + ListenerManager.unregister(it) + } + } + BaritoneUtils.primary?.pathingBehavior?.cancelEverything() + subActivities.clear() + } + + private fun SafeClientEvent.maintainInventory() { + if (allSubActivities.filterIsInstance().isNotEmpty() + || allSubActivities.filterIsInstance().isNotEmpty() + ) return + + val stashOrders = mutableSetOf>() + + if (player.allSlots.none { StackSelection.EMPTY_SHULKERS(it.stack) }) { + // ToDo: Request correct amount of shulkers + WorldEater.stashes.minByOrNull { + player.distanceTo(it.area.center) + }?.let { stash -> + stashOrders.add(Triple(stash, ContainerAction.PULL, StackSelection(5).apply { + selection = StackSelection.EMPTY_SHULKERS + })) + } + + if (player.allSlots.any { StackSelection.FULL_SHULKERS(it.stack) }) { + WorldEater.dropOff.minByOrNull { + player.distanceTo(it.area.center) + }?.let { stash -> + stashOrders.add(Triple(stash, ContainerAction.PUSH, StackSelection(0).apply { + selection = StackSelection.FULL_SHULKERS + })) + } + } + + if (stashOrders.isNotEmpty()) { + MessageSendHelper.sendChatMessage("No empty shulker boxes found, getting new ones and dump full ones.") + addSubActivities(StashTransaction(stashOrders)) + return + } + } + + if (player.inventorySlots.countEmpty() <= BuildTools.keepFreeSlots) { + val itemsToStore = WorldEater.collectables.filter { + player.inventorySlots.countItem(it) > 0 + } + + if (itemsToStore.isNotEmpty()) { + MessageSendHelper.sendChatMessage("Compressing ${ + itemsToStore.joinToString { "${it.registryName?.path}" } + } to shulker boxes.") + + addSubActivities(itemsToStore.map { item -> + ShulkerTransaction(ContainerAction.PUSH, StackSelection().apply { + selection = isItem(item) + count = 0 + }) + }) + return + } + } + + if (BuildTools.usePickaxe) checkItem(Items.DIAMOND_PICKAXE, stashOrders) + if (BuildTools.useShovel) checkItem(Items.DIAMOND_SHOVEL, stashOrders) + if (BuildTools.useAxe) checkItem(Items.DIAMOND_AXE, stashOrders) + if (BuildTools.useSword) checkItem(Items.DIAMOND_SWORD, stashOrders) + if (BuildTools.useShears) checkItem(Items.SHEARS, stashOrders) + + checkItem(Items.GOLDEN_APPLE, stashOrders) + + if (stashOrders.isNotEmpty()) addSubActivities(StashTransaction(stashOrders)) + } + + private fun SafeClientEvent.checkItem(item: Item, orders: MutableSet>) { + if (player.allSlots.countItem(item) + player.allSlots.sumOf { slot -> + getShulkerInventory(slot.stack)?.filter { it.item == item }?.sumOf { it.count } ?: 0 + // ToDo: Add ender chest support also make this use some kind of utils for storage management + } >= BuildTools.minToolAmount) return + + val optimalStash = WorldEater.stashes + .filter { it.items.contains(item) } + .minByOrNull { player.distanceTo(it.area.center) } ?: return + + MessageSendHelper.sendChatMessage("Missing ${ + item.registryName + }. Fetching shulker from stash (${optimalStash.area.center.asString()}).") + + orders.add(Triple(optimalStash, ContainerAction.PULL, StackSelection(inShulkerBox = true).apply { + selection = isItem(item) + })) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/manager/managers/KamiMojiManager.kt b/src/main/kotlin/com/lambda/client/manager/managers/LambdaMojiManager.kt similarity index 100% rename from src/main/kotlin/com/lambda/client/manager/managers/KamiMojiManager.kt rename to src/main/kotlin/com/lambda/client/manager/managers/LambdaMojiManager.kt diff --git a/src/main/kotlin/com/lambda/client/manager/managers/PlayerPacketManager.kt b/src/main/kotlin/com/lambda/client/manager/managers/PlayerPacketManager.kt index 7a27dcda5..e06883d1d 100644 --- a/src/main/kotlin/com/lambda/client/manager/managers/PlayerPacketManager.kt +++ b/src/main/kotlin/com/lambda/client/manager/managers/PlayerPacketManager.kt @@ -15,11 +15,12 @@ import net.minecraft.network.play.client.CPacketPlayer import net.minecraft.util.math.Vec3d import net.minecraftforge.fml.common.gameevent.TickEvent import java.util.* +import java.util.concurrent.ConcurrentLinkedDeque object PlayerPacketManager : Manager { /** TreeMap for all packets to be sent, sorted by their callers' priority */ - private val packetMap = TreeMap(compareByDescending { it.modulePriority }) + private val packetMap = ConcurrentLinkedDeque() var serverSidePosition: Vec3d = Vec3d.ZERO; private set var prevServerSidePosition: Vec3d = Vec3d.ZERO; private set @@ -33,7 +34,7 @@ object PlayerPacketManager : Manager { listener(Int.MIN_VALUE) { if (it.phase != Phase.PERI || packetMap.isEmpty()) return@listener - it.apply(packetMap.values.first()) + it.apply(packetMap.first()) packetMap.clear() } @@ -80,14 +81,14 @@ object PlayerPacketManager : Manager { } } - inline fun AbstractModule.sendPlayerPacket(block: Packet.Builder.() -> Unit) { + inline fun sendPlayerPacket(block: Packet.Builder.() -> Unit) { Packet.Builder().apply(block).build()?.let { sendPlayerPacket(it) } } - fun AbstractModule.sendPlayerPacket(packet: Packet) { - packetMap[this] = packet + fun sendPlayerPacket(packet: Packet) { + packetMap.push(packet) } class Packet private constructor( diff --git a/src/main/kotlin/com/lambda/client/module/AbstractModule.kt b/src/main/kotlin/com/lambda/client/module/AbstractModule.kt index d49db0637..b4e42a7b6 100644 --- a/src/main/kotlin/com/lambda/client/module/AbstractModule.kt +++ b/src/main/kotlin/com/lambda/client/module/AbstractModule.kt @@ -1,5 +1,6 @@ package com.lambda.client.module +import com.lambda.client.activity.Activity import com.lambda.client.commons.interfaces.Alias import com.lambda.client.commons.interfaces.Nameable import com.lambda.client.event.LambdaEventBus diff --git a/src/main/kotlin/com/lambda/client/module/modules/client/BuildTools.kt b/src/main/kotlin/com/lambda/client/module/modules/client/BuildTools.kt new file mode 100644 index 000000000..1eb679145 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/module/modules/client/BuildTools.kt @@ -0,0 +1,179 @@ +package com.lambda.client.module.modules.client + +import com.lambda.client.manager.managers.ActivityManager +import com.lambda.client.module.Category +import com.lambda.client.module.Module +import com.lambda.client.setting.settings.impl.collection.CollectionSetting +import com.lambda.client.util.items.shulkerList +import com.lambda.client.util.threads.safeListener +import net.minecraft.block.Block +import net.minecraft.init.Blocks +import net.minecraft.init.Items +import net.minecraft.item.Item +import net.minecraftforge.fml.common.gameevent.TickEvent + +object BuildTools : Module( + name = "BuildTools", + description = "Settings for internal build engine", + category = Category.CLIENT, + showOnArray = false, + alwaysEnabled = true +) { + private val page by setting("Page", Page.BUILDING, description = "Switch between setting pages") + + /* behavior */ + val maxReach by setting("Max Reach", 4.9f, 1.0f..7.0f, 0.1f, { page == Page.BUILDING }, description = "Sets the range of the blueprint. Decrease when tasks fail!", unit = " blocks") + val autoPathing by setting("Auto Pathing", true, { page == Page.BUILDING }, description = "Automatically pathfind to the next block") + val pathingRecomputeTimeout by setting("Pathing Recompute Timeout", 200, 0..1000, 20, { page == Page.BUILDING }, description = "Timeout for recomputing the path", unit = " ms") + val collectRange by setting("Collect Range", 16, 1..50, 1, { page == Page.BUILDING }, description = "Sets the range of the pickup", unit = " blocks") + val minimumStackSize by setting("Minimum Amount Collect", 32, 1..64, 1, { page == Page.BUILDING }, description = "Minimum amount of items to pick up", unit = " items") +// val moveSpeed by setting("Packet Move Speed", 0.2, 0.0..1.0, 0.01, { page == Page.BEHAVIOR }, description = "Maximum player velocity per tick", unit = "m/t") +// val taskTimeout by setting("Task Timeout", 8, 0..20, 1, { page == Page.BEHAVIOR }, description = "Timeout for waiting for the server to try again", unit = " ticks") +// val maxRetries by setting("Max Task Retries", 3, 0..10, 1, { page == Page.BEHAVIOR }, description = "Maximum amount of timeouts for a task") +// val rubberbandTimeout by setting("Rubberband Timeout", 50, 5..100, 5, { page == Page.BEHAVIOR }, description = "Timeout for pausing after a lag", unit = " ticks") +// private val clearQueue by setting("Clear build queue", false, { page == Page.BEHAVIOR }, consumer = { _, it -> +// if (it) BuildToolsManager.resetAll() +// false +// }) + val tickDelay by setting("Tick Delay", 0, 0..200, 1, { page == Page.BUILDING }) + + /* mining */ + val breakDelay by setting("Break Delay", 1, 0..20, 1, { page == Page.BUILDING }, description = "Sets the delay ticks between break tasks", unit = " ticks") +// val maxPending by setting("Max Pending", 1, 1..100, 1, { page == Page.BUILDING }, description = "Sets the maximum amount of pending break tasks") + val miningSpeedFactor by setting("Mining Speed Factor", 1.0f, 0.0f..2.0f, 0.01f, { page == Page.BUILDING }, description = "Factor to manipulate calculated mining speed") +// val interactionLimit by setting("Interaction Limit", 20, 1..100, 1, { page == Page.MINING }, description = "Set the interaction limit per second", unit = " interactions/s") +// val multiBreak by setting("Multi Break", true, { page == Page.MINING }, description = "Breaks multiple instant breaking blocks intersecting with view vector on the same tick") +// val packetFlood by setting("Packet Flood", false, { page == Page.MINING }, description = "Exploit for faster packet breaks. Sends START and STOP packet on same tick") + + /* placing */ + val placeDelay by setting("Place Delay", 1, 0..20, 1, { page == Page.BUILDING }, description = "Sets the delay ticks between placement tasks", unit = " ticks") + val breakDownCycles by setting("Break Down", 64, 1..200, 1, { page == Page.BUILDING }, description = "", unit = " ender chests") + val pickBlock by setting("Pick Block Creative", true, { page == Page.BUILDING }, description = "Use pick block to place blocks when in creative mode") + val placeStrictness by setting("Placement Strictness", PlacementStrictness.DIRECTION, { page == Page.BUILDING }, description = "ANY: Allow all exposed surfaces. DIRECTION: Only allow surfaces in the direction of the player. VISIBLE: Only allow surfaces that are visible to the player.") +// val illegalPlacements by setting("Illegal Placements", false, { page == Page.BUILDING }, description = "Do not use on 2b2t. Tries to interact with invisible surfaces") +// val doPending by setting("Do Pending", true, { page == Page.BUILDING }, description = "Do not wait for server to confirm action") + val executionCountPerTick by setting("Executions Per Tick", ActivityManager.MAX_DEPTH, 0..ActivityManager.MAX_DEPTH * 2, 1, { page == Page.BUILDING }, description = "How many tasks to execute per tick") +// val scaffold by setting("Scaffold", true, { page == Page.PLACING }, description = "Tries to bridge / scaffold when stuck placing") + val placementSearch by setting("Place Deep Search", 1, 1..4, 1, { page == Page.BUILDING }, description = "EXPERIMENTAL: Attempts to find a support block for placing against", unit = " blocks") + val directionForce by setting("Block Direction Exploit", true, { page == Page.BUILDING }, description = "EXPLOIT: Forces the direction of the block to be placed") + + /* storage management */ + val storageManagement by setting("Manage Storage", true, { page == Page.STORAGE_MANAGEMENT }, description = "Choose to interact with container using only packets") +// val searchEChest by setting("Search Ender Chest", false, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Allow access to your ender chest") +// val leaveEmptyShulkers by setting("Leave Empty Shulkers", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Does not break empty shulkers") +// val grindObsidian by setting("Grind Obsidian", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Destroy Ender Chests to obtain Obsidian") +// val pickupRadius by setting("Pickup radius", 8, 1..50, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Sets the radius for pickup", unit = " blocks") +// val fastFill by setting("Fast Fill", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Moves as many item stacks to inventory as possible") + val keepFreeSlots by setting("Free Slots", 1, 0..30, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many inventory slots are untouched on refill", unit = " slots") +// val lockSlotHotkey by setting("Lock Slot Hotkey", Bind(), { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Sets the hotkey for locking a slot") +// val manageFood by setting("Manage Food", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Choose to manage food") +// val manageTools by setting("Manage Tools", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Choose to manage food") +// val leastTools by setting("Least Tools", 1, 0..36, 1, { page == Page.STORAGE_MANAGEMENT && manageTools && storageManagement }, description = "How many tools are saved") +// val leastEnder by setting("Least Ender Chests", 1, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many ender chests are saved") +// val leastFood by setting("Least Food", 1, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && manageFood && storageManagement }, description = "How many food items are saved") +// val preferEnderChests by setting("Prefer Ender Chests", false, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Prevent using raw material shulkers") + + /* tools */ + val usePickaxe by setting("Force Pickaxe", true, { page == Page.TOOLS }, description = "Use pickaxe to mine blocks") + val useAxe by setting("Force Axe", true, { page == Page.TOOLS }, description = "Use axe to mine blocks") + val useShovel by setting("Force Shovel", true, { page == Page.TOOLS }, description = "Use shovel to mine blocks") + val useSword by setting("Force Sword", true, { page == Page.TOOLS }, description = "Use sword to mine blocks") + val useShears by setting("Force Shears", true, { page == Page.TOOLS }, description = "Use shears to mine blocks") + val minToolAmount by setting("Min Tool Amount", 1, 0..10, 1, { page == Page.TOOLS }, description = "How many tools are saved") + + /* render */ +// val info by setting("Show Info", true, { page == Page.RENDER }, description = "Prints session stats in chat") +// val goalRender by setting("Baritone Goal", false, { page == Page.RENDER }, description = "Renders the baritone goal") +// val showCurrentPos by setting("Current Pos", false, { page == Page.RENDER }, description = "Renders the current position") + val filled by setting("Filled", true, { page == Page.RENDER }, description = "Renders colored task surfaces") + val outline by setting("Outline", true, { page == Page.RENDER }, description = "Renders colored task outlines") + val maxDistance by setting("Max Distance", 24, 0..256, 1, { page == Page.RENDER }, description = "Max distance to render tasks", unit = " blocks") +// val popUp by setting("Pop up", true, { page == Page.RENDER }, description = "Funny render effect") +// val popUpSpeed by setting("Pop up speed", 150, 0..500, 1, { page == Page.RENDER && popUp }, description = "Sets speed of the pop up effect", unit = "ms") + val showDebugRender by setting("Debug Render", false, { page == Page.RENDER }, description = "Render debug info on tasks") + val maxDebugRange by setting("Max Debug Range", 8, 0..256, 1, { page == Page.RENDER && showDebugRender }, description = "Max distance to render debug info", unit = " blocks") + val maxDebugAmount by setting("Max Debug Amount", 20, 0..256, 1, { page == Page.RENDER && showDebugRender }, description = "Max amount of debug info to render") + val textScale by setting("Text Scale", 1.0f, 0.0f..4.0f, 0.25f, { page == Page.RENDER && showDebugRender }, description = "Scale of debug text") +// val distScaleFactor by setting("Distance Scale Factor", 0.05f, 0.0f..1.0f, 0.05f, { page == Page.RENDER && showDebugRender }) +// val minDistScale by setting("Min Distance Scale", 0.35f, 0.0f..1.0f, 0.05f, { page == Page.RENDER && showDebugRender }) + val aFilled by setting("Filled Alpha", 26, 0..255, 1, { filled && page == Page.RENDER }, description = "Sets the opacity") + val aOutline by setting("Outline Alpha", 91, 0..255, 1, { outline && page == Page.RENDER }, description = "Sets the opacity") + val thickness by setting("Thickness", 2.0f, 0.25f..4.0f, 0.25f, { outline && page == Page.RENDER }, description = "Sets thickness of outline") + + /* misc */ +// val disableWarnings by setting("Disable Warnings", false, { page == Page.MISC }, description = "DANGEROUS: Disable warnings on enable") +// val debugLevel by setting("Debug Level", DebugLevel.IMPORTANT, { page == Page.MISC }, description = "Sets the debug log depth level") +// val fakeSounds by setting("Fake Sounds", true, { page == Page.MISC }, description = "Adds artificial sounds to the actions") +// val anonymizeLog by setting("Anonymize", false, { page == Page.MISC }, description = "Censors all coordinates in HUD and Chat") +// val disableMode by setting("Disable Mode", DisableMode.NONE, { page == Page.MISC }, description = "Choose action when bot is out of materials or tools") +// val usingProxy by setting("Proxy", false, { page == Page.MISC && disableMode == DisableMode.LOGOUT }, description = "Enable this if you are using a proxy to call the given command") +// val proxyCommand by setting("Proxy Command", "/dc", { page == Page.MISC && disableMode == DisableMode.LOGOUT && usingProxy }, description = "Command to be sent to log out") + + private enum class Page { + BUILDING, STORAGE_MANAGEMENT, TOOLS, RENDER + } + + enum class DebugLevel { + OFF, IMPORTANT, VERBOSE + } + + enum class DisableMode { + NONE, ANTI_AFK, LOGOUT + } + + enum class PlacementStrictness { + ANY, DIRECTION, VISIBLE + } + + var defaultFillerMat: Block + get() = Block.getBlockFromName(fillerMatSaved.value) ?: Blocks.NETHERRACK + set(value) { + fillerMatSaved.value = value.registryName.toString() + } + + var defaultTool: Item + get() = Item.getByNameOrId(tool.value) ?: Items.GOLDEN_APPLE + set(value) { + tool.value = value.registryName.toString() + } + + var defaultFood: Item + get() = Item.getByNameOrId(food.value) ?: Items.GOLDEN_APPLE + set(value) { + food.value = value.registryName.toString() + } + + val defaultIgnoreBlocks = linkedSetOf( + "minecraft:standing_sign", + "minecraft:wall_sign", + "minecraft:standing_banner", + "minecraft:wall_banner" + ).also { defaultIgnoreBlocks -> defaultIgnoreBlocks.addAll(shulkerList.map { it.registryName.toString() }) } + + val defaultEjectList = linkedSetOf( + "minecraft:grass", + "minecraft:dirt", + "minecraft:netherrack", + "minecraft:stone", + "minecraft:cobblestone" + ) + + val ignoreBlocks = setting(CollectionSetting("IgnoreList", defaultIgnoreBlocks, { false })) + var ignoredBlocks: List = ignoreBlocks.mapNotNull { Block.getBlockFromName(it) } + init { + ignoreBlocks.editListeners.add { ignoredBlocks = ignoreBlocks.mapNotNull { Block.getBlockFromName(it) } } + + safeListener { + if (it.phase != TickEvent.Phase.START) return@safeListener + if (ignoreBlocks.size != ignoredBlocks.size) { + ignoreBlocks.filter { Block.getBlockFromName(it) == null }.forEach { ignoreBlocks.remove(it) } + ignoredBlocks = ignoreBlocks.mapNotNull { Block.getBlockFromName(it) } + } + } + } + + val ejectList = setting(CollectionSetting("Eject List", defaultEjectList, { false })) + private val fillerMatSaved = setting("FillerMat", "minecraft:netherrack", { false }) + private val food = setting("FoodItem", "minecraft:golden_apple", { false }) + private val tool = setting("ToolItem", "minecraft:diamond_pickaxe", { false }) +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/module/modules/combat/AutoTrap.kt b/src/main/kotlin/com/lambda/client/module/modules/combat/AutoTrap.kt index 2cd3c0ffd..ff5fab290 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/combat/AutoTrap.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/combat/AutoTrap.kt @@ -24,6 +24,7 @@ import com.lambda.client.util.world.isPlaceable import kotlinx.coroutines.Job import kotlinx.coroutines.launch import net.minecraft.init.Blocks +import net.minecraft.util.math.AxisAlignedBB import net.minecraft.util.math.BlockPos import net.minecraftforge.fml.common.gameevent.InputEvent import net.minecraftforge.fml.common.gameevent.TickEvent @@ -79,7 +80,9 @@ object AutoTrap : Module( private fun SafeClientEvent.canRun(): Boolean { (if (selfTrap.value) player else CombatManager.target)?.positionVector?.toBlockPos()?.let { for (offset in trapMode.offset) { - if (!world.isPlaceable(it.add(offset))) continue + val targetPos = it.add(offset) + + if (!world.isPlaceable(targetPos, AxisAlignedBB(targetPos))) continue return true } } diff --git a/src/main/kotlin/com/lambda/client/module/modules/combat/CrystalBasePlace.kt b/src/main/kotlin/com/lambda/client/module/modules/combat/CrystalBasePlace.kt index fff57a8a1..3c66df1f1 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/combat/CrystalBasePlace.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/combat/CrystalBasePlace.kt @@ -27,6 +27,7 @@ import com.lambda.client.util.world.getNeighbour import com.lambda.client.util.world.hasNeighbour import com.lambda.client.util.world.isPlaceable import net.minecraft.entity.EntityLivingBase +import net.minecraft.entity.item.EntityEnderCrystal import net.minecraft.init.Blocks import net.minecraft.item.ItemStack import net.minecraft.network.play.client.CPacketPlayerTryUseItemOnBlock @@ -149,7 +150,7 @@ object CrystalBasePlace : Module( for (pos in posList) { // Placeable check - if (!world.isPlaceable(pos)) continue + if (!world.isPlaceable(pos, entity.entityBoundingBox)) continue // Neighbour blocks check if (!hasNeighbour(pos)) continue diff --git a/src/main/kotlin/com/lambda/client/module/modules/combat/Surround.kt b/src/main/kotlin/com/lambda/client/module/modules/combat/Surround.kt index 827cd1b4c..9f70e8095 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/combat/Surround.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/combat/Surround.kt @@ -162,7 +162,7 @@ object Surround : Module( private fun SafeClientEvent.canRun(): Boolean { val playerPos = player.positionVector.toBlockPos() return SurroundUtils.surroundOffset.any { - world.isPlaceable(playerPos.add(it), true) + world.isPlaceable(playerPos.add(it), Blocks.OBSIDIAN.defaultState.getSelectedBoundingBox(world, playerPos.add(it)), true) } } diff --git a/src/main/kotlin/com/lambda/client/module/modules/misc/AutoObsidian.kt b/src/main/kotlin/com/lambda/client/module/modules/misc/AutoObsidian.kt index 54c5bc25f..3b2a36815 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/misc/AutoObsidian.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/misc/AutoObsidian.kt @@ -272,7 +272,7 @@ object AutoObsidian : Module( private fun SafeClientEvent.isPositionValid(pos: BlockPos, blockState: IBlockState, eyePos: Vec3d) = !world.getBlockState(pos.down()).isReplaceable && (blockState.block.let { it == Blocks.ENDER_CHEST || it is BlockShulkerBox } - || world.isPlaceable(pos)) + || world.isPlaceable(pos, blockState.getSelectedBoundingBox(world, pos))) && world.isAirBlock(pos.up()) && world.rayTraceBlocks(eyePos, pos.toVec3dCenter())?.let { it.typeOfHit == RayTraceResult.Type.MISS } ?: true diff --git a/src/main/kotlin/com/lambda/client/module/modules/misc/BlockData.kt b/src/main/kotlin/com/lambda/client/module/modules/misc/BlockData.kt index b375a1acb..ba7ef56bc 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/misc/BlockData.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/misc/BlockData.kt @@ -34,6 +34,15 @@ object BlockData : Module( val tag = NBTTagCompound().apply { tileEntity.writeToNBT(this) } MessageSendHelper.sendChatMessage("""$chatName &6Block Tags:$tag""".trimIndent()) } + + if (blockState.properties.isNotEmpty()) { + val properties = blockState.properties.entries.joinToString(", ") { "${it.key}=${it.value}" } + MessageSendHelper.sendChatMessage("""$chatName &6Block Properties: $properties""".trimIndent()) + } + + blockState.block.getValidRotations(world, blockPos)?.let { rotations -> + MessageSendHelper.sendChatMessage("""$chatName &6Block Valid Rotations: ${rotations.joinToString()}""".trimIndent()) + } } } } diff --git a/src/main/kotlin/com/lambda/client/module/modules/misc/Graffiti.kt b/src/main/kotlin/com/lambda/client/module/modules/misc/Graffiti.kt new file mode 100644 index 000000000..6294fdbdb --- /dev/null +++ b/src/main/kotlin/com/lambda/client/module/modules/misc/Graffiti.kt @@ -0,0 +1,35 @@ +package com.lambda.client.module.modules.misc + +import com.lambda.client.activity.activities.construction.Graffiti +import com.lambda.client.manager.managers.ActivityManager.addSubActivities +import com.lambda.client.module.Category +import com.lambda.client.module.Module +import com.lambda.client.util.threads.runSafe + +object Graffiti : Module( + name = "Graffiti", + description = "Spams item frames and maps", + category = Category.MISC +) { + private val mapID = setting("Map ID", 0, 0..65535, 1) + private var ownedActivity: Graffiti? = null + + init { + onEnable { + Graffiti(mapID.value).let { + ownedActivity = it + addSubActivities(it) + } + } + + onDisable { + runSafe { + ownedActivity?.let { + with(it) { + cancel() + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/module/modules/misc/HighwayTools.kt b/src/main/kotlin/com/lambda/client/module/modules/misc/HighwayTools.kt new file mode 100644 index 000000000..4c032775c --- /dev/null +++ b/src/main/kotlin/com/lambda/client/module/modules/misc/HighwayTools.kt @@ -0,0 +1,241 @@ +package com.lambda.client.module.modules.misc + +import com.lambda.client.activity.activities.construction.core.BuildStructure +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.gui.hudgui.elements.client.ActivityManagerHud +import com.lambda.client.manager.managers.ActivityManager.addSubActivities +import com.lambda.client.module.Category +import com.lambda.client.module.Module +import com.lambda.client.module.modules.client.BuildTools +import com.lambda.client.util.EntityUtils.flooredPosition +import com.lambda.client.util.math.Direction +import com.lambda.client.util.math.VectorUtils.multiply +import com.lambda.client.util.threads.runSafe +import net.minecraft.block.Block +import net.minecraft.block.BlockColored +import net.minecraft.block.state.IBlockState +import net.minecraft.init.Blocks +import net.minecraft.item.EnumDyeColor +import net.minecraft.util.math.BlockPos + +object HighwayTools : Module( + name = "HighwayTools", + description = "Be the grief a step a head.", + category = Category.MISC, + alias = arrayOf("HT", "HWT") +) { + private val structure by setting("Structure", Structure.HIGHWAY, description = "Choose the structure") + private val width by setting("Width", 6, 1..50, 1, description = "Sets the width of blueprint", unit = " blocks") + private val height by setting("Height", 4, 2..10, 1, description = "Sets height of blueprint", unit = " blocks") + private val offset by setting("Offset", 3, -10..10, 1, description = "Sets the offset of the structure", unit = " blocks") + private val rainbowMode by setting("Rainbow Mode", false, description = "Rainbow highway uwu") + + private val backfill by setting("Backfill", false, { structure == Structure.TUNNEL }, description = "Fills the tunnel behind you") + private val fillFloor by setting("Fill Floor", false, { structure == Structure.TUNNEL && !backfill }, description = "Cleans up the tunnels floor") + private val fillRightWall by setting("Fill Right Wall", false, { structure == Structure.TUNNEL && !backfill }, description = "Cleans up the right wall") + private val fillLeftWall by setting("Fill Left Wall", false, { structure == Structure.TUNNEL && !backfill }, description = "Cleans up the left wall") + private val fillRoof by setting("Fill Roof", false, { structure == Structure.TUNNEL && !backfill }, description = "Cleans up the tunnels roof") + private val fillCorner by setting("Fill Corner", false, { structure == Structure.TUNNEL && !cornerBlock && !backfill && width > 2 }, description = "Cleans up the tunnels corner") + private val cornerBlock by setting("Corner Block", false, { structure == Structure.HIGHWAY || (structure == Structure.TUNNEL && !backfill && width > 2) }, description = "If activated will break the corner in tunnel or place a corner while paving") + private val railingHeight by setting("Railing Height", 1, 0..4, 1, { structure == Structure.HIGHWAY }, description = "Sets height of railing", unit = " blocks") + + enum class Structure { + HIGHWAY, TUNNEL + } + + private enum class Pages { + GENERAL, CLEAN_UP, MATERIALS + } + + private var originDirection = Direction.NORTH + private var originOrthogonalDirection = Direction.NORTH + private var originPosition = BlockPos.ORIGIN + var distance = 0 // 0 means infinite + + private var ownedBuildStructure: BuildStructure? = null + + var material: Block + get() = Block.getBlockFromName(materialSaved.value) ?: Blocks.OBSIDIAN + set(value) { + materialSaved.value = value.registryName.toString() + } + + init { + onEnable { + runSafe { + start() + } + } + + onDisable { + runSafe { + ownedBuildStructure?.let { + with(it) { + cancel() + } + } + } + } + } + + fun SafeClientEvent.start() { + originPosition = player.flooredPosition.down() + originDirection = Direction.fromEntity(player) + originOrthogonalDirection = originDirection.clockwise(if (originDirection.isDiagonal) 1 else 2) + + printEnable() + + ActivityManagerHud.totalBlocksBroken = 0 + ActivityManagerHud.totalBlocksPlaced = 0 + + BuildStructure( + generateHighway(), + direction = originDirection, + offsetMove = BlockPos(originDirection.directionVec.multiply(offset)), + maximumRepeats = distance, + doPadding = true + ).let { + ownedBuildStructure = it + addSubActivities(it) + } + } + + private fun generateHighway(): HashMap { + val blueprint = hashMapOf() + + for (x in -5..5) { + val thisPos = originPosition.add(originDirection.directionVec.multiply(x)) + generateClear(blueprint, thisPos) + + if (structure == Structure.TUNNEL) { + if (fillFloor) generateFloor(blueprint, thisPos) + if (fillRightWall || fillLeftWall) generateWalls(blueprint, thisPos) + if (fillRoof) generateRoof(blueprint, thisPos) + if (fillCorner && !cornerBlock && width > 2) generateCorner(blueprint, thisPos) + } else { + generateBase(blueprint, thisPos) + } + } + + if (structure == Structure.TUNNEL && (!fillFloor || backfill)) { + if (originDirection.isDiagonal) { + for (x in 0..width) { + val pos = originPosition.add(originDirection.directionVec.multiply(x)) + blueprint[pos] = fillerState() + blueprint[pos.add(originDirection.clockwise(7).directionVec)] = fillerState() + } + } else { + for (x in 0..width) { + blueprint[originPosition.add(originDirection.directionVec.multiply(x))] = fillerState() + } + } + } + + return blueprint + } + + private fun generateClear(blueprint: HashMap, basePos: BlockPos) { + for (w in 0 until width) { + for (h in 0 until height) { + val x = w - width / 2 + val pos = basePos.add(originOrthogonalDirection.directionVec.multiply(x)).up(h) + + if (structure == Structure.HIGHWAY && h == 0 && isRail(w)) { + continue + } + + if (structure == Structure.HIGHWAY) { + blueprint[pos] = Blocks.AIR.defaultState + } else { + if (!(isRail(w) && h == 0 && !cornerBlock && width > 2)) blueprint[pos.up()] = Blocks.AIR.defaultState + } + } + } + } + + private fun generateBase(blueprint: HashMap, basePos: BlockPos) { + for (w in 0 until width) { + val x = w - width / 2 + val pos = basePos.add(originOrthogonalDirection.directionVec.multiply(x)) + + if (structure == Structure.HIGHWAY && isRail(w)) { + if (!cornerBlock && width > 2 && originDirection.isDiagonal) blueprint[pos] = fillerState() // support block + val startHeight = if (cornerBlock && width > 2) 0 else 1 + for (y in startHeight..railingHeight) { + blueprint[pos.up(y)] = if (rainbowMode) getRainbowBlockState(pos.up(y)) else material.defaultState + } + } else { + blueprint[pos] = if (rainbowMode) getRainbowBlockState(pos) else material.defaultState + } + } + } + + private fun generateFloor(blueprint: HashMap, basePos: BlockPos) { + val wid = if (cornerBlock && width > 2) { + width + } else { + width - 2 + } + for (w in 0 until wid) { + val x = w - wid / 2 + val pos = basePos.add(originOrthogonalDirection.directionVec.multiply(x)) + blueprint[pos] = fillerState() + } + } + + private fun generateWalls(blueprint: HashMap, basePos: BlockPos) { + val cb = if (!cornerBlock && width > 2) { + 1 + } else { + 0 + } + for (h in cb until height) { + if (fillRightWall) blueprint[basePos.add(originOrthogonalDirection.directionVec.multiply(width - width / 2)).up(h + 1)] = fillerState() + if (fillLeftWall) blueprint[basePos.add(originOrthogonalDirection.directionVec.multiply(-1 - width / 2)).up(h + 1)] = fillerState() + } + } + + private fun generateRoof(blueprint: HashMap, basePos: BlockPos) { + for (w in 0 until width) { + val x = w - width / 2 + val pos = basePos.add(originOrthogonalDirection.directionVec.multiply(x)) + blueprint[pos.up(height + 1)] = fillerState() + } + } + + private fun generateCorner(blueprint: HashMap, basePos: BlockPos) { + blueprint[basePos.add(originOrthogonalDirection.directionVec.multiply(-1 - width / 2 + 1)).up()] = fillerState() + blueprint[basePos.add(originOrthogonalDirection.directionVec.multiply(width - width / 2 - 1)).up()] = fillerState() + } + + private fun isRail(w: Int) = railingHeight > 0 && w !in 1 until width - 1 + + private fun SafeClientEvent.printEnable() { + + } + + fun printSettings() { + + } + + private fun getRainbowBlockState(pos: BlockPos): IBlockState { + val rainbowColors = listOf( + EnumDyeColor.PURPLE, + EnumDyeColor.BLUE, + EnumDyeColor.CYAN, + EnumDyeColor.LIME, + EnumDyeColor.YELLOW, + EnumDyeColor.ORANGE, + EnumDyeColor.RED + ) + + return Blocks.CONCRETE.defaultState.withProperty( + BlockColored.COLOR, + rainbowColors[(originPosition.subtract(pos).z + width / 2).mod(rainbowColors.size)] + ) + } + + private fun fillerState() = Blocks.NETHERRACK.defaultState + + private val materialSaved = setting("Material", "minecraft:obsidian", { false }) +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/module/modules/misc/SchematicBuilder.kt b/src/main/kotlin/com/lambda/client/module/modules/misc/SchematicBuilder.kt new file mode 100644 index 000000000..a55085989 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/module/modules/misc/SchematicBuilder.kt @@ -0,0 +1,62 @@ +package com.lambda.client.module.modules.misc + +import com.lambda.client.activity.activities.construction.BuildSchematic +import com.lambda.client.manager.managers.ActivityManager +import com.lambda.client.manager.managers.ActivityManager.addSubActivities +import com.lambda.client.module.Category +import com.lambda.client.module.Module +import com.lambda.client.util.math.Direction +import com.lambda.client.util.math.VectorUtils.multiply +import com.lambda.client.util.schematic.LambdaSchematicaHelper +import com.lambda.client.util.schematic.LambdaSchematicaHelper.isSchematicaPresent +import com.lambda.client.util.text.MessageSendHelper +import com.lambda.client.util.threads.runSafe +import net.minecraft.util.math.BlockPos + +object SchematicBuilder : Module( + name = "SchematicBuilder", + description = "Build schematics", + category = Category.MISC, + alias = arrayOf("sb") +) { + private val offset by setting("Offset", 0, -10..10, 1) + private val inLayers by setting("In Layers", true) + + private var ownedBuildStructure: BuildSchematic? = null + + init { + onEnable { + runSafe { + if (!isSchematicaPresent) { + MessageSendHelper.sendErrorMessage("$chatName Schematica is not loaded / installed!") + disable() + return@runSafe + } + + LambdaSchematicaHelper.loadedSchematic?.let { schematic -> + val direction = Direction.fromEntity(player) + + BuildSchematic( + schematic, + inLayers, + direction, + BlockPos(direction.directionVec.multiply(offset)) + ).let { + ownedBuildStructure = it + addSubActivities(it) + } + } + } + } + + onDisable { + runSafe { + ownedBuildStructure?.let { + with(it) { + cancel() + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/module/modules/misc/TestActivityManager.kt b/src/main/kotlin/com/lambda/client/module/modules/misc/TestActivityManager.kt new file mode 100644 index 000000000..578fd0bc5 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/module/modules/misc/TestActivityManager.kt @@ -0,0 +1,185 @@ +package com.lambda.client.module.modules.misc + +import com.lambda.client.activity.activities.construction.Graffiti +import com.lambda.client.activity.activities.construction.SurroundWithObsidian +import com.lambda.client.activity.activities.construction.core.PlaceBlock +import com.lambda.client.activity.activities.interaction.crafting.ReachXPLevel +import com.lambda.client.activity.activities.inventory.AcquireItemInActiveHand +import com.lambda.client.activity.activities.storage.* +import com.lambda.client.activity.activities.storage.types.* +import com.lambda.client.activity.activities.travel.CollectDrops +import com.lambda.client.activity.types.RenderAABBActivity.Companion.checkAABBRender +import com.lambda.client.manager.managers.ActivityManager +import com.lambda.client.manager.managers.ActivityManager.addSubActivities +import com.lambda.client.module.Category +import com.lambda.client.module.Module +import com.lambda.client.util.EntityUtils.flooredPosition +import com.lambda.client.util.MovementUtils.centerPlayer +import com.lambda.client.util.items.item +import com.lambda.client.util.math.VectorUtils.multiply +import com.lambda.client.util.threads.runSafe +import net.minecraft.block.BlockDirectional +import net.minecraft.block.BlockHorizontal +import net.minecraft.init.Blocks +import net.minecraft.init.Items +import net.minecraft.item.Item +import net.minecraft.util.EnumFacing + +object TestActivityManager : Module( + name = "TestActivityManager", + description = "a", + category = Category.MISC +) { + private val ctiectie by setting("Auto Obby", false, consumer = { _, _-> + addSubActivities( + BreakDownEnderChests() + ) + false + }) + + private val tie by setting("Store one Obby", false, consumer = { _, _-> + addSubActivities( + ShulkerTransaction(ContainerAction.PUSH, StackSelection().apply { + selection = isBlock(Blocks.OBSIDIAN) + }) + ) + false + }) + + private val eictie by setting("Stash request", false, consumer = { _, _-> + WorldEater.stashes.firstOrNull()?.let { + addSubActivities( + StashTransaction(setOf(Triple(it, ContainerAction.PULL, StackSelection().apply { + selection = isBlock(Blocks.OBSIDIAN) + }))) + ) + } + false + }) + + private val ectitie by setting("Stash request multi", false, consumer = { _, _-> + WorldEater.stashes.firstOrNull()?.let { + addSubActivities( + StashTransaction(setOf(Triple(it, ContainerAction.PULL, StackSelection(0).apply { + selection = isBlock(Blocks.OBSIDIAN) + }))) + ) + } + false + }) + + private val ectiectictietie by setting("Stash request multi more", false, consumer = { _, _-> + WorldEater.stashes.firstOrNull()?.let { + addSubActivities( + StashTransaction(setOf(Triple(it, ContainerAction.PULL, StackSelection().apply { + selection = isBlock(Blocks.OBSIDIAN) + }))), + StashTransaction(setOf(Triple(it, ContainerAction.PUSH, StackSelection().apply { + selection = isBlock(Blocks.OBSIDIAN) + }))) + ) + } + false + }) + + private val etit by setting("Acquire Obby", false, consumer = { _, _-> + addSubActivities( + AcquireItemInActiveHand(StackSelection().apply { + selection = isBlock(Blocks.OBSIDIAN) + }) + ) + false + }) + + private val ectiectietit by setting("Graffiti", false, consumer = { _, _-> + addSubActivities( + Graffiti(100) + ) + false + }) + + private val eticiettie by setting("Direction shenanigans", false, consumer = { _, _-> + runSafe { + var currentDirection = player.horizontalFacing + var position = player.flooredPosition.add(currentDirection.directionVec.multiply(2)) + + repeat(4) { + val targetState = Blocks.MAGENTA_GLAZED_TERRACOTTA.defaultState.withProperty(BlockHorizontal.FACING, currentDirection) + + addSubActivities( + PlaceBlock(position, targetState) + ) + + currentDirection = currentDirection.rotateY() + position = position.add(currentDirection.directionVec) + } + } + + false + }) + + private val ctiectictiectie by setting("Button", false, consumer = { _, _-> + runSafe { + val currentDirection = player.horizontalFacing + +// val targetState = Blocks.QUARTZ_BLOCK.defaultState +// .withProperty(BlockQuartz.VARIANT, BlockQuartz.EnumType.LINES_X) + + val targetState = Blocks.WOODEN_BUTTON.defaultState + .withProperty(BlockDirectional.FACING, EnumFacing.NORTH) + + addSubActivities(PlaceBlock( + player.flooredPosition.add(currentDirection.directionVec.multiply(2)), + targetState + )) + } + + false + }) + + private val po by setting("Pickup Obby", false, consumer = { _, _-> + addSubActivities(CollectDrops(Blocks.OBSIDIAN.item)) + false + }) + + private val ctiectiectiectieciec by setting("Pickup Dropped", false, consumer = { _, _-> + runSafe { + val stack = player.heldItemMainhand.copy() + + addSubActivities(CollectDrops(stack.item, stack)) + } + + false + }) + + private val tiectie by setting("Surround", false, consumer = { _, _-> + runSafe { + player.centerPlayer() + addSubActivities( + SurroundWithObsidian(player.flooredPosition) + ) + } + false + }) + + val raiseXPLevel by setting("Reach level 30", false, consumer = { _, _-> + addSubActivities(ReachXPLevel(30)) + false + }) + + private val reset by setting("Reset", false, consumer = { _, _-> + ActivityManager.reset() + false + }) + + init { + onToggle { + runSafe { + with(ActivityManager.getCurrentActivity()) { + updateActivity() + checkAABBRender() + } + } + } + } +} diff --git a/src/main/kotlin/com/lambda/client/module/modules/misc/WorldEater.kt b/src/main/kotlin/com/lambda/client/module/modules/misc/WorldEater.kt new file mode 100644 index 000000000..1493ac653 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/module/modules/misc/WorldEater.kt @@ -0,0 +1,116 @@ +package com.lambda.client.module.modules.misc + +import com.lambda.client.activity.activities.construction.ClearArea +import com.lambda.client.activity.activities.construction.core.BuildStructure +import com.lambda.client.activity.activities.storage.types.Area +import com.lambda.client.activity.activities.storage.types.Stash +import com.lambda.client.command.CommandManager.prefix +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.manager.managers.ActivityManager +import com.lambda.client.manager.managers.ActivityManager.addSubActivities +import com.lambda.client.module.Category +import com.lambda.client.module.Module +import com.lambda.client.setting.settings.impl.collection.CollectionSetting +import com.lambda.client.util.items.item +import com.lambda.client.util.math.VectorUtils.distanceTo +import com.lambda.client.util.text.MessageSendHelper +import com.lambda.client.util.threads.runSafe +import net.minecraft.init.Blocks +import net.minecraft.init.Items +import net.minecraft.item.Item +import net.minecraft.util.EnumFacing + +object WorldEater : Module( + name = "WorldEater", + description = "Full auto excavation", + category = Category.MISC, + alias = arrayOf("we") +) { + private val layerSize by setting("Layers size", 1, 1..6, 1) + private val sliceSize by setting("Slice size", 1, 1..6, 1) + private val sliceDirection by setting("Slice direction", EnumFacing.NORTH) + private val collectAll by setting("Collect all", false) + + private val defaultPickupItems = linkedSetOf( + Blocks.DIRT.item, + Blocks.GRASS.item, + Blocks.STONE.item, + Blocks.COBBLESTONE.item, + Blocks.GRAVEL.item, + Blocks.SAND.item, + Blocks.SANDSTONE.item, + Blocks.RED_SANDSTONE.item, + Blocks.CLAY.item, + Blocks.IRON_ORE.item, + Blocks.GOLD_ORE.item, + Items.COAL, + ) + + val tools = setting(CollectionSetting("Tools", mutableListOf(), entryType = Item::class.java)) + + val collectables = setting(CollectionSetting("Pick up items", defaultPickupItems, entryType = Item::class.java)) + val quarries = setting(CollectionSetting("Quarries", mutableListOf(), entryType = Area::class.java)) + val stashes = setting(CollectionSetting("Stashes", mutableListOf(), entryType = Stash::class.java)) + val dropOff = setting(CollectionSetting("Drop offs", mutableListOf(), entryType = Stash::class.java)) + + var ownedActivity: ClearArea? = null + + init { + onEnable { + if (quarries.value.isEmpty()) { + MessageSendHelper.sendChatMessage("No quarries set yet. Use &7${prefix}we quarry add&r to add one.") + disable() + return@onEnable + } + + runSafe { + clearAllAreas() + } + } + + onDisable { + runSafe { + ActivityManager.reset() // ToDo: Should also cancel the maintain inventory activity + ownedActivity?.let { + with(it) { + cancel() + } + } + ownedActivity = null + } + } + } + + fun SafeClientEvent.clearAllAreas() { + quarries.value.minByOrNull { player.distanceTo(it.pos1) }?.let { area -> + MessageSendHelper.sendChatMessage("Start excavating closest area: $area") + ClearArea( + area, + layerSize, + sliceSize, + sliceDirection, + collectAll = collectAll + ).also { + ownedActivity = it + addSubActivities(it) + } + } + } + + fun SafeClientEvent.info() = "WorldEater:\n&7Progress&r:${ + "%.3f".format(quarries.value.sumOf { it.containedBlocks.count { pos -> world.isAirBlock(pos) } }.toFloat() / quarries.value.sumOf { it.containedBlocks.size } * 100) + }% with ${ + ownedActivity?.subActivities?.filterIsInstance()?.size + } layers left.\n&7Pickup&r:${collectables.value.joinToString { + "${it.registryName?.path}" + }}\n&7Quarries&r: ${ + if (quarries.value.isEmpty()) "None" + else quarries.value.joinToString { "${quarries.indexOf(it) + 1}: $it" } + }\n&7Stashes&r: ${ + if (stashes.value.isEmpty()) "None" + else stashes.value.joinToString { "${stashes.indexOf(it) + 1}: $it" } + }\n&7Drop-off&r: ${ + if (dropOff.value.isEmpty()) "None" + else dropOff.value.joinToString { "${dropOff.indexOf(it) + 1}: $it" } + }" +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/module/modules/movement/Parkour.kt b/src/main/kotlin/com/lambda/client/module/modules/movement/Parkour.kt index 07ea8bb58..915d2cfdf 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/movement/Parkour.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/movement/Parkour.kt @@ -5,7 +5,6 @@ import com.lambda.client.event.listener.listener import com.lambda.client.module.Category import com.lambda.client.module.Module import com.lambda.client.module.modules.client.GuiColors -import com.lambda.client.module.modules.render.Search.setting import com.lambda.client.util.Wrapper import com.lambda.client.util.graphics.ESPRenderer import com.lambda.client.util.graphics.GeometryMasks diff --git a/src/main/kotlin/com/lambda/client/module/modules/player/InventoryManagerTwo.kt b/src/main/kotlin/com/lambda/client/module/modules/player/InventoryManagerTwo.kt new file mode 100644 index 000000000..0c8f195de --- /dev/null +++ b/src/main/kotlin/com/lambda/client/module/modules/player/InventoryManagerTwo.kt @@ -0,0 +1,109 @@ +package com.lambda.client.module.modules.player + +import com.lambda.client.activity.activities.construction.core.BreakBlock +import com.lambda.client.activity.activities.storage.core.PlaceContainer +import com.lambda.client.activity.activities.storage.types.StackSelection +import com.lambda.client.event.events.GuiEvent +import com.lambda.client.event.events.WindowClickEvent +import com.lambda.client.manager.managers.ActivityManager +import com.lambda.client.manager.managers.ActivityManager.addSubActivities +import com.lambda.client.module.Category +import com.lambda.client.module.Module +import com.lambda.client.util.items.item +import com.lambda.client.util.threads.safeListener +import net.minecraft.block.BlockEnderChest +import net.minecraft.block.BlockShulkerBox +import net.minecraft.client.gui.inventory.GuiChest +import net.minecraft.client.gui.inventory.GuiShulkerBox +import net.minecraft.init.Blocks +import net.minecraft.inventory.ClickType +import net.minecraft.item.ItemShulkerBox +import net.minecraft.util.math.BlockPos +import net.minecraftforge.fml.common.gameevent.TickEvent + +object InventoryManagerTwo : Module( + name = "InventoryManagerTwo", + description = "Manages your inventory automatically", + category = Category.PLAYER +) { + private val placedContainer = ArrayDeque(mutableListOf()) + private val currentlyOpen: PlaceContainer? = null + + init { + safeListener { + if (it.type != ClickType.PICKUP || it.mouseButton != 1) return@safeListener + + player.openContainer.inventorySlots.getOrNull(it.slotId)?.let { slot -> + if (!(slot.stack.item is ItemShulkerBox || slot.stack.item == Blocks.ENDER_CHEST.item)) return@safeListener + + addSubActivities(PlaceContainer(StackSelection().apply { + selection = isItemStack(slot.stack) + }, open = true)) + + it.cancel() + +// val cloned = ArrayDeque(placedShulkerBoxes) +// +// cloned.forEachIndexed { index, openShulker -> +// if (index == 0) return@forEachIndexed +// placedShulkerBoxes.remove(openShulker) +// +// val currentBlock = world.getBlockState(openShulker.containerPos).block +// +// if (!(currentBlock is BlockShulkerBox || currentBlock is BlockEnderChest)) return@forEachIndexed +// +// ActivityManager.addSubActivities( +// BreakBlock(openShulker.containerPos, collectDrops = true) +// ) +// } + +// if (placedShulkerBoxes.size > 1) { +// val previous = placedShulkerBoxes.removeFirst() +// +// val currentBlock = world.getBlockState(previous.containerPos).block +// +// if (!(currentBlock is BlockShulkerBox || currentBlock is BlockEnderChest)) return@safeListener +// +// ActivityManager.addSubActivities( +// BreakBlock(previous.containerPos, collectDrops = true) +// ) +// } + } + } + + safeListener { + if (placedContainer.isEmpty() || it.phase != TickEvent.Phase.START) return@safeListener + + val cloned = ArrayDeque(placedContainer) + + cloned.forEachIndexed { index, placeContainer -> + if (index == 0 || placeContainer.containerPos == BlockPos.ORIGIN) return@forEachIndexed + placedContainer.remove(placeContainer) + + val currentBlock = world.getBlockState(placeContainer.containerPos).block + + if (!(currentBlock is BlockShulkerBox || currentBlock is BlockEnderChest)) return@forEachIndexed + + addSubActivities( + BreakBlock(placeContainer.containerPos, collectDrops = true) + ) + } + } + + safeListener { + if (!(it.screen is GuiShulkerBox || it.screen is GuiChest)) return@safeListener + + placedContainer.firstOrNull()?.let { placeContainer -> + placedContainer.remove(placeContainer) + + val currentBlock = world.getBlockState(placeContainer.containerPos).block + + if (!(currentBlock is BlockShulkerBox || currentBlock is BlockEnderChest)) return@safeListener + + addSubActivities( + BreakBlock(placeContainer.containerPos, collectDrops = true) + ) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/module/modules/player/NoGhostItems.kt b/src/main/kotlin/com/lambda/client/module/modules/player/NoGhostItems.kt index 1fabbde5b..355925cbf 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/player/NoGhostItems.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/player/NoGhostItems.kt @@ -5,6 +5,7 @@ import com.lambda.client.manager.managers.PlayerInventoryManager import com.lambda.client.manager.managers.PlayerInventoryManager.addInventoryTask import com.lambda.client.module.Category import com.lambda.client.module.Module +import com.lambda.client.util.text.MessageSendHelper import com.lambda.client.util.threads.safeListener import com.lambda.mixin.player.MixinPlayerControllerMP @@ -30,6 +31,7 @@ object NoGhostItems : Module( init { safeListener { + MessageSendHelper.sendChatMessage("${it.windowId} ${it.slotId} ${it.mouseButton} ${it.type}") if (syncMode == SyncMode.MODULES) return@safeListener addInventoryTask(PlayerInventoryManager.ClickInfo(it.windowId, it.slotId, it.mouseButton, it.type)) diff --git a/src/main/kotlin/com/lambda/client/module/modules/player/PacketLogger.kt b/src/main/kotlin/com/lambda/client/module/modules/player/PacketLogger.kt index e4fb6b800..294dcd9f8 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/player/PacketLogger.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/player/PacketLogger.kt @@ -948,7 +948,7 @@ object PacketLogger : Module( if (!sPacketBlockChange) return logServer(packet) { "blockPosition" to packet.blockPosition - "block" to packet.blockState.block.localizedName + "blockState" to packet.blockState } } is SPacketCamera -> { diff --git a/src/main/kotlin/com/lambda/client/module/modules/render/NoRender.kt b/src/main/kotlin/com/lambda/client/module/modules/render/NoRender.kt index dc4cddbb1..f5bb04fe2 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/render/NoRender.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/render/NoRender.kt @@ -58,8 +58,9 @@ object NoRender : Module( private val items = setting("Items", false, { page == Page.ENTITIES }) private val crystal = setting("Crystals", false, { page == Page.ENTITIES }) private val firework = setting("Firework", false, { page == Page.ENTITIES }) + private val tileEntity = setting("TileEntities", false, { page == Page.ENTITIES }) - //Armor + // Armor private val armorPlayer by setting("Players", false, { page == Page.ARMOR }) private val armorStands by setting("Armour Stands", false, { page == Page.ARMOR }) private val armorMobs by setting("Mobs", false, { page == Page.ARMOR }) @@ -68,7 +69,7 @@ object NoRender : Module( private val leggings by setting("Leggings", true, { page == Page.ARMOR }) private val boots by setting("Boots", true, { page == Page.ARMOR }) - //Overlay + // Overlay val hurtCamera by setting("Hurt Camera", false, { page == Page.OVERLAY }) private val fire by setting("Fire", false, { page == Page.OVERLAY }) private val water by setting("Water", false, { page == Page.OVERLAY }) diff --git a/src/main/kotlin/com/lambda/client/setting/serializables/BlockPosSerializer.kt b/src/main/kotlin/com/lambda/client/setting/serializables/BlockPosSerializer.kt new file mode 100644 index 000000000..963f88fff --- /dev/null +++ b/src/main/kotlin/com/lambda/client/setting/serializables/BlockPosSerializer.kt @@ -0,0 +1,18 @@ +package com.lambda.client.setting.serializables + +import com.google.gson.JsonDeserializer +import com.google.gson.JsonSerializer +import com.google.gson.* +import java.lang.reflect.Type +import net.minecraft.util.math.BlockPos + +object BlockPosSerializer : JsonSerializer, JsonDeserializer { + override fun serialize(src: BlockPos, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(src.toLong()) + } + + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): BlockPos { + val longValue = json.asJsonPrimitive.asLong + return BlockPos.fromLong(longValue) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/setting/serializables/BlockSerializer.kt b/src/main/kotlin/com/lambda/client/setting/serializables/BlockSerializer.kt new file mode 100644 index 000000000..186bda634 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/setting/serializables/BlockSerializer.kt @@ -0,0 +1,16 @@ +package com.lambda.client.setting.serializables + +import com.google.gson.* +import java.lang.reflect.Type +import net.minecraft.block.Block + +object BlockSerializer : JsonSerializer, JsonDeserializer { + override fun serialize(src: Block, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(src.registryName.toString()) + } + + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Block? { + val stringValue = json.asJsonPrimitive.asString + return Block.getBlockFromName(stringValue) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/setting/serializables/ItemTypeAdapterFactory.kt b/src/main/kotlin/com/lambda/client/setting/serializables/ItemTypeAdapterFactory.kt new file mode 100644 index 000000000..ff895cd8d --- /dev/null +++ b/src/main/kotlin/com/lambda/client/setting/serializables/ItemTypeAdapterFactory.kt @@ -0,0 +1,36 @@ +package com.lambda.client.setting.serializables + +import com.google.gson.Gson +import com.google.gson.JsonObject +import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import net.minecraft.item.Item + +object ItemTypeAdapterFactory : TypeAdapterFactory { + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + @Suppress("UNNECESSARY_SAFE_CALL") + if (!Item::class.java.isAssignableFrom(type?.rawType)) return null + val jsonObjectAdapter = gson.getAdapter(JsonObject::class.java) + + @Suppress("UNCHECKED_CAST") + return object : TypeAdapter() { + override fun write(out: com.google.gson.stream.JsonWriter, value: Item?) { + if (value == null) { + out.nullValue() + return + } + val jsonObject = JsonObject() + jsonObject.addProperty("name", value.registryName.toString()) + jsonObjectAdapter.write(out, jsonObject) + } + + override fun read(reader: JsonReader): Item? { + val jsonObject = jsonObjectAdapter.read(reader) + val name = jsonObject.get("name").asString + return Item.getByNameOrId(name) + } + } as TypeAdapter + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/setting/settings/AbstractSetting.kt b/src/main/kotlin/com/lambda/client/setting/settings/AbstractSetting.kt index 2867e07ab..81d00ec5e 100644 --- a/src/main/kotlin/com/lambda/client/setting/settings/AbstractSetting.kt +++ b/src/main/kotlin/com/lambda/client/setting/settings/AbstractSetting.kt @@ -5,6 +5,11 @@ import com.google.gson.GsonBuilder import com.google.gson.JsonElement import com.google.gson.JsonParser import com.lambda.client.commons.interfaces.Nameable +import com.lambda.client.setting.serializables.BlockPosSerializer +import com.lambda.client.setting.serializables.BlockSerializer +import com.lambda.client.setting.serializables.ItemTypeAdapterFactory +import net.minecraft.block.Block +import net.minecraft.util.math.BlockPos import kotlin.reflect.KProperty abstract class AbstractSetting : Nameable { @@ -47,7 +52,12 @@ abstract class AbstractSetting : Nameable { value.hashCode() protected companion object { - val gson: Gson = GsonBuilder().setPrettyPrinting().create() + val gson: Gson = GsonBuilder() + .registerTypeAdapter(BlockPos::class.java, BlockPosSerializer) + .registerTypeAdapter(Block::class.java, BlockSerializer) + .registerTypeAdapterFactory(ItemTypeAdapterFactory) + .setPrettyPrinting() + .create() val parser = JsonParser() } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/setting/settings/ImmutableSetting.kt b/src/main/kotlin/com/lambda/client/setting/settings/ImmutableSetting.kt index 9ad33535b..2239182e5 100644 --- a/src/main/kotlin/com/lambda/client/setting/settings/ImmutableSetting.kt +++ b/src/main/kotlin/com/lambda/client/setting/settings/ImmutableSetting.kt @@ -15,7 +15,7 @@ abstract class ImmutableSetting( override val visibility: () -> Boolean, val consumer: (prev: T, input: T) -> T, override val description: String, - override val unit: String + override val unit: String = "" ) : AbstractSetting() { override val value: T = valueIn override val valueClass: Class = valueIn.javaClass diff --git a/src/main/kotlin/com/lambda/client/setting/settings/MutableSetting.kt b/src/main/kotlin/com/lambda/client/setting/settings/MutableSetting.kt index 05ee5b3d8..a8cf950af 100644 --- a/src/main/kotlin/com/lambda/client/setting/settings/MutableSetting.kt +++ b/src/main/kotlin/com/lambda/client/setting/settings/MutableSetting.kt @@ -18,7 +18,7 @@ open class MutableSetting( override val visibility: () -> Boolean, consumer: (prev: T, input: T) -> T, override val description: String, - override val unit: String + override val unit: String = "" ) : AbstractSetting() { override val defaultValue = valueIn diff --git a/src/main/kotlin/com/lambda/client/setting/settings/SettingRegister.kt b/src/main/kotlin/com/lambda/client/setting/settings/SettingRegister.kt index fa781204c..061f96720 100644 --- a/src/main/kotlin/com/lambda/client/setting/settings/SettingRegister.kt +++ b/src/main/kotlin/com/lambda/client/setting/settings/SettingRegister.kt @@ -10,6 +10,9 @@ import com.lambda.client.setting.settings.impl.primitive.EnumSetting import com.lambda.client.setting.settings.impl.primitive.StringSetting import com.lambda.client.util.Bind import com.lambda.client.util.color.ColorHolder +import net.minecraft.block.Block +import net.minecraft.item.Item +import net.minecraft.util.math.BlockPos import java.util.function.BooleanSupplier /** @@ -75,6 +78,30 @@ interface SettingRegister { description: String = "" ) = setting(ColorSetting(name, value, hasAlpha, visibility, description)) + /** BlockPos Setting */ + fun T.setting( + name: String, + value: BlockPos, + visibility: () -> Boolean = { true }, + description: String = "" + ) = setting(MutableSetting(name, value, visibility, { _, input -> input }, description, unit = "")) + + /** Item Setting */ + fun T.setting( + name: String, + value : Item, + visibility: () -> Boolean = { true }, + description: String = "" + ) = setting(MutableSetting(name, value, visibility, { _, input -> input }, description, unit = "")) + + /** Block Setting */ + fun T.setting( + name: String, + value : Block, + visibility: () -> Boolean = { true }, + description: String = "" + ) = setting(MutableSetting(name, value, visibility, { _, input -> input }, description, unit = "")) + /** Boolean Setting */ fun T.setting( name: String, 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 ce1afaf80..c09ea6cdc 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 @@ -2,6 +2,7 @@ package com.lambda.client.setting.settings.impl.collection import com.google.gson.JsonElement import com.google.gson.reflect.TypeToken +import com.lambda.client.LambdaMod import com.lambda.client.setting.settings.ImmutableSetting class CollectionSetting>( @@ -51,7 +52,10 @@ class CollectionSetting>( override fun read(jsonElement: JsonElement?) { jsonElement?.asJsonArray?.let { - val cacheArray = gson.fromJson>(it, TypeToken.getArray(entryType ?: value.first().javaClass).type) + LambdaMod.LOG.info("Reading collection setting $name $it") + val cacheArray = gson.fromJson>( + it, TypeToken.getArray(entryType ?: value.first().javaClass).type + ) synchronized(lockObject) { value.clear() value.addAll(cacheArray) diff --git a/src/main/kotlin/com/lambda/client/setting/settings/impl/other/BindSetting.kt b/src/main/kotlin/com/lambda/client/setting/settings/impl/other/BindSetting.kt index 23e224f27..1d82b415a 100644 --- a/src/main/kotlin/com/lambda/client/setting/settings/impl/other/BindSetting.kt +++ b/src/main/kotlin/com/lambda/client/setting/settings/impl/other/BindSetting.kt @@ -12,7 +12,7 @@ class BindSetting( value: Bind, visibility: () -> Boolean = { true }, description: String = "" -) : ImmutableSetting(name, value, visibility, { _, input -> input }, description, unit = "") { +) : ImmutableSetting(name, value, visibility, { _, input -> input }, description) { override val defaultValue: Bind = Bind(TreeSet(value.modifierKeys), value.key, null) diff --git a/src/main/kotlin/com/lambda/client/setting/settings/impl/other/ColorSetting.kt b/src/main/kotlin/com/lambda/client/setting/settings/impl/other/ColorSetting.kt index 3feb506e4..f424b2a33 100644 --- a/src/main/kotlin/com/lambda/client/setting/settings/impl/other/ColorSetting.kt +++ b/src/main/kotlin/com/lambda/client/setting/settings/impl/other/ColorSetting.kt @@ -9,4 +9,4 @@ class ColorSetting( val hasAlpha: Boolean = true, visibility: () -> Boolean = { true }, description: String = "" -) : MutableSetting(name, value, visibility, { _, input -> if (!hasAlpha) input.apply { a = 255 } else input }, description, unit = "") \ No newline at end of file +) : MutableSetting(name, value, visibility, { _, input -> if (!hasAlpha) input.apply { a = 255 } else input }, description) \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/util/graphics/ESPRenderer.kt b/src/main/kotlin/com/lambda/client/util/graphics/ESPRenderer.kt index 78a9e767a..c4545ce79 100644 --- a/src/main/kotlin/com/lambda/client/util/graphics/ESPRenderer.kt +++ b/src/main/kotlin/com/lambda/client/util/graphics/ESPRenderer.kt @@ -4,6 +4,8 @@ import com.lambda.client.util.EntityUtils import com.lambda.client.util.EntityUtils.getInterpolatedAmount import com.lambda.client.util.Wrapper import com.lambda.client.util.color.ColorHolder +import net.minecraft.block.state.IBlockState +import net.minecraft.client.multiplayer.WorldClient import net.minecraft.client.renderer.GlStateManager import net.minecraft.client.renderer.culling.Frustum import net.minecraft.client.renderer.culling.ICamera @@ -50,6 +52,10 @@ class ESPRenderer { add(AxisAlignedBB(pos), color, sides) } + fun add(pos: BlockPos, state: IBlockState, world: WorldClient, color: ColorHolder, sides: Int) { + add(state.getSelectedBoundingBox(world, pos), color, sides) + } + fun add(box: AxisAlignedBB, color: ColorHolder) { add(box, color, GeometryMasks.Quad.ALL) } diff --git a/src/main/kotlin/com/lambda/client/util/items/Slot.kt b/src/main/kotlin/com/lambda/client/util/items/Slot.kt index c1e433c97..39f5da297 100644 --- a/src/main/kotlin/com/lambda/client/util/items/Slot.kt +++ b/src/main/kotlin/com/lambda/client/util/items/Slot.kt @@ -131,7 +131,7 @@ fun Slot.toHotbarSlotOrNull() = if (this.slotNumber in 36..44 && this.inventory == Wrapper.player?.inventory) HotbarSlot(this) else null -class HotbarSlot(slot: Slot) : Slot(slot.inventory, slot.slotIndex, slot.xPos, slot.yPos) { +class HotbarSlot(val slot: Slot) : Slot(slot.inventory, slot.slotIndex, slot.xPos, slot.yPos) { init { slotNumber = slot.slotNumber } diff --git a/src/main/kotlin/com/lambda/client/util/schematic/LambdaSchematicaHelper.kt b/src/main/kotlin/com/lambda/client/util/schematic/LambdaSchematicaHelper.kt new file mode 100644 index 000000000..0fce63ca9 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/util/schematic/LambdaSchematicaHelper.kt @@ -0,0 +1,23 @@ +package com.lambda.client.util.schematic + +import com.github.lunatrius.schematica.Schematica +import com.github.lunatrius.schematica.proxy.ClientProxy + +object LambdaSchematicaHelper { + val isSchematicaPresent: Boolean + get() = try { + Class.forName(Schematica::class.java.name) + true + } catch (ex: ClassNotFoundException) { + false + } catch (ex: NoClassDefFoundError) { + false + } + + val loadedSchematic: Schematic? + get() { + return ClientProxy.schematic?.let { + SchematicAdapter(it) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/util/schematic/Schematic.kt b/src/main/kotlin/com/lambda/client/util/schematic/Schematic.kt new file mode 100644 index 000000000..5d1d56774 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/util/schematic/Schematic.kt @@ -0,0 +1,20 @@ +package com.lambda.client.util.schematic + +import net.minecraft.block.state.IBlockState +import net.minecraft.util.math.BlockPos + +interface Schematic { + fun desiredState(x: Int, y: Int, z: Int): IBlockState { + return desiredState(BlockPos(x, y, z)) + } + + fun desiredState(pos: BlockPos): IBlockState + + fun inSchematic(pos: BlockPos): Boolean + fun inSchematic(x: Int, y: Int, z: Int): Boolean + + fun widthX(): Int + fun heightY(): Int + fun lengthZ(): Int + fun getOrigin(): BlockPos +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/util/schematic/SchematicAdapter.kt b/src/main/kotlin/com/lambda/client/util/schematic/SchematicAdapter.kt new file mode 100644 index 000000000..527d2e8b5 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/util/schematic/SchematicAdapter.kt @@ -0,0 +1,35 @@ +package com.lambda.client.util.schematic + +import com.github.lunatrius.schematica.client.world.SchematicWorld +import net.minecraft.block.state.IBlockState +import net.minecraft.util.math.BlockPos + +class SchematicAdapter(private val schematic: SchematicWorld) : Schematic { + override fun desiredState(pos: BlockPos): IBlockState { + return schematic.schematic.getBlockState(BlockPos(pos.x - getOrigin().x, pos.y - getOrigin().y, pos.z - getOrigin().z)) + } + + override fun widthX(): Int { + return schematic.schematic.width + } + + override fun heightY(): Int { + return schematic.schematic.height + } + + override fun lengthZ(): Int { + return schematic.schematic.length + } + + override fun getOrigin(): BlockPos { + return schematic.position + } + + override fun inSchematic(pos: BlockPos): Boolean { + return inSchematic(pos.x, pos.y, pos.z) + } + + override fun inSchematic(x: Int, y: Int, z: Int): Boolean { + return x >= schematic.position.x && x < schematic.position.x + widthX() && y >= schematic.position.y && y < schematic.position.y + heightY() && z >= schematic.position.z && z < schematic.position.z + lengthZ() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/util/world/Check.kt b/src/main/kotlin/com/lambda/client/util/world/Check.kt index 9e25de3d2..4c9f54e90 100644 --- a/src/main/kotlin/com/lambda/client/util/world/Check.kt +++ b/src/main/kotlin/com/lambda/client/util/world/Check.kt @@ -105,8 +105,8 @@ fun SafeClientEvent.hasNeighbour(pos: BlockPos): Boolean { * * @return true playing is not colliding with [pos] and there is block below it */ -fun World.isPlaceable(pos: BlockPos, ignoreSelfCollide: Boolean = false) = +fun World.isPlaceable(pos: BlockPos, targetBoundingBox: AxisAlignedBB = AxisAlignedBB(pos), ignoreSelfCollide: Boolean = false) = this.getBlockState(pos).isReplaceable - && checkNoEntityCollision(AxisAlignedBB(pos), if (ignoreSelfCollide) Wrapper.player else null) + && checkNoEntityCollision(targetBoundingBox, if (ignoreSelfCollide) Wrapper.player else null) && worldBorder.contains(pos) && !isOutsideBuildHeight(pos) \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/util/world/Interact.kt b/src/main/kotlin/com/lambda/client/util/world/Interact.kt index 1b09e15ed..6d5e43107 100644 --- a/src/main/kotlin/com/lambda/client/util/world/Interact.kt +++ b/src/main/kotlin/com/lambda/client/util/world/Interact.kt @@ -13,6 +13,7 @@ import net.minecraft.network.play.client.CPacketPlayerTryUseItemOnBlock import net.minecraft.util.EnumFacing import net.minecraft.util.EnumHand import net.minecraft.util.SoundCategory +import net.minecraft.util.math.AxisAlignedBB import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Vec3d import java.util.* @@ -83,7 +84,7 @@ private fun SafeClientEvent.getNeighbour( sides: Array, toIgnore: HashSet> ): PlaceInfo? { - if (!world.isPlaceable(pos)) return null + if (!world.isPlaceable(pos, AxisAlignedBB(pos))) return null sides.forEach { side -> checkNeighbour(eyePos, pos, side, range, visibleSideCheck, true, toIgnore)?.let { @@ -95,7 +96,7 @@ private fun SafeClientEvent.getNeighbour( sides.forEach { posSide -> val newPos = pos.offset(posSide) - if (!world.isPlaceable(newPos)) return@forEach + if (!world.isPlaceable(newPos, AxisAlignedBB(newPos))) return@forEach if (eyePos.distanceTo(newPos.toVec3dCenter()) > range + 1) return@forEach getNeighbour(eyePos, newPos, attempts - 1, range, visibleSideCheck, sides, toIgnore)?.let { @@ -126,7 +127,7 @@ private fun SafeClientEvent.checkNeighbour( if (dist > range) return null if (visibleSideCheck && !getVisibleSides(offsetPos, true).contains(oppositeSide)) return null if (checkReplaceable && world.getBlockState(offsetPos).isReplaceable) return null - if (!world.isPlaceable(pos)) return null + if (!world.isPlaceable(pos, AxisAlignedBB(pos))) return null val hitVecOffset = getHitVecOffset(oppositeSide) return PlaceInfo(offsetPos, oppositeSide, dist, hitVecOffset, hitVec, pos) @@ -140,6 +141,15 @@ fun SafeClientEvent.getMiningSide(pos: BlockPos): EnumFacing? { .minByOrNull { eyePos.squareDistanceTo(getHitVec(pos, it)) } } +fun SafeClientEvent.getMiningSide(pos: BlockPos, range: Float): EnumFacing? { + val eyePos = player.getPositionEyes(1.0f) + + return getVisibleSides(pos) + .filter { !world.getBlockState(pos.offset(it)).isFullBox } + .filter { eyePos.distanceTo(getHitVec(pos, it)) < range } + .minByOrNull { eyePos.squareDistanceTo(getHitVec(pos, it)) } +} + fun SafeClientEvent.getClosestVisibleSide(pos: BlockPos): EnumFacing? { val eyePos = player.getPositionEyes(1.0f) @@ -241,7 +251,7 @@ private fun SafeClientEvent.getStructurePlaceInfo( for (offset in structureOffset) { val pos = center.add(offset) if (toIgnore.contains(pos)) continue - if (!world.isPlaceable(pos)) continue + if (!world.isPlaceable(pos, AxisAlignedBB(pos))) continue return getNeighbour(pos, attempts, range, visibleSideCheck) ?: continue } @@ -277,7 +287,7 @@ fun SafeClientEvent.placeBlock( placeInfo: PlaceInfo, hand: EnumHand = EnumHand.MAIN_HAND ) { - if (!world.isPlaceable(placeInfo.placedPos)) return + if (!world.isPlaceable(placeInfo.placedPos, AxisAlignedBB(placeInfo.placedPos))) return connection.sendPacket(placeInfo.toPlacePacket(hand)) player.swingArm(hand) diff --git a/src/main/kotlin/com/lambda/client/util/world/PlaceInfo.kt b/src/main/kotlin/com/lambda/client/util/world/PlaceInfo.kt index 410543712..d57ae9212 100644 --- a/src/main/kotlin/com/lambda/client/util/world/PlaceInfo.kt +++ b/src/main/kotlin/com/lambda/client/util/world/PlaceInfo.kt @@ -9,6 +9,6 @@ class PlaceInfo( val side: EnumFacing, val dist: Double, val hitVecOffset: Vec3d, - val hitVec: Vec3d, + var hitVec: Vec3d, val placedPos: BlockPos ) \ No newline at end of file diff --git a/src/schematica_api/java/com/github/lunatrius/core/util/math/MBlockPos.java b/src/schematica_api/java/com/github/lunatrius/core/util/math/MBlockPos.java new file mode 100644 index 000000000..5fe69bcf0 --- /dev/null +++ b/src/schematica_api/java/com/github/lunatrius/core/util/math/MBlockPos.java @@ -0,0 +1,24 @@ +package com.github.lunatrius.core.util.math; + +import net.minecraft.util.math.BlockPos; + +public class MBlockPos extends BlockPos { + MBlockPos() { + super(0, 0, 0); + } + + @Override + public int getX() { + return 0; + } + + @Override + public int getY() { + return 0; + } + + @Override + public int getZ() { + return 0; + } +} \ No newline at end of file diff --git a/src/schematica_api/java/com/github/lunatrius/schematica/Schematica.java b/src/schematica_api/java/com/github/lunatrius/schematica/Schematica.java new file mode 100644 index 000000000..eec4da7be --- /dev/null +++ b/src/schematica_api/java/com/github/lunatrius/schematica/Schematica.java @@ -0,0 +1,7 @@ +package com.github.lunatrius.schematica; + +import com.github.lunatrius.schematica.proxy.CommonProxy; + +public class Schematica { + public static CommonProxy proxy; +} diff --git a/src/schematica_api/java/com/github/lunatrius/schematica/api/ISchematic.java b/src/schematica_api/java/com/github/lunatrius/schematica/api/ISchematic.java new file mode 100644 index 000000000..071f752a2 --- /dev/null +++ b/src/schematica_api/java/com/github/lunatrius/schematica/api/ISchematic.java @@ -0,0 +1,11 @@ +package com.github.lunatrius.schematica.api; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.util.math.BlockPos; + +public interface ISchematic { + IBlockState getBlockState(BlockPos blockPos); + int getWidth(); + int getHeight(); + int getLength(); +} diff --git a/src/schematica_api/java/com/github/lunatrius/schematica/client/world/SchematicWorld.java b/src/schematica_api/java/com/github/lunatrius/schematica/client/world/SchematicWorld.java new file mode 100644 index 000000000..860bd2ad6 --- /dev/null +++ b/src/schematica_api/java/com/github/lunatrius/schematica/client/world/SchematicWorld.java @@ -0,0 +1,12 @@ +package com.github.lunatrius.schematica.client.world; + +import com.github.lunatrius.core.util.math.MBlockPos; +import com.github.lunatrius.schematica.api.ISchematic; + +public class SchematicWorld { + public final MBlockPos position = null; + + public ISchematic getSchematic() { + return null; + } +} diff --git a/src/schematica_api/java/com/github/lunatrius/schematica/proxy/ClientProxy.java b/src/schematica_api/java/com/github/lunatrius/schematica/proxy/ClientProxy.java new file mode 100644 index 000000000..79a55132e --- /dev/null +++ b/src/schematica_api/java/com/github/lunatrius/schematica/proxy/ClientProxy.java @@ -0,0 +1,7 @@ +package com.github.lunatrius.schematica.proxy; + +import com.github.lunatrius.schematica.client.world.SchematicWorld; + +public class ClientProxy extends CommonProxy { + public static SchematicWorld schematic; +} diff --git a/src/schematica_api/java/com/github/lunatrius/schematica/proxy/CommonProxy.java b/src/schematica_api/java/com/github/lunatrius/schematica/proxy/CommonProxy.java new file mode 100644 index 000000000..e400c815a --- /dev/null +++ b/src/schematica_api/java/com/github/lunatrius/schematica/proxy/CommonProxy.java @@ -0,0 +1,5 @@ +package com.github.lunatrius.schematica.proxy; + +public abstract class CommonProxy { + +}