mirror of https://github.com/kami-blue/client
[refactor] Fix issues with and improve AutoObsidian (#1804)
Co-authored-by: natan <66911017+natan515@users.noreply.github.com> Co-authored-by: Xiaro <62033805+Xiaro@users.noreply.github.com> Co-authored-by: theredstoner <huddy987@yahoo.com> Co-authored-by: theredstoner <73187300+theredstoner@users.noreply.github.com> Co-authored-by: zincodrone <59712311+zincodrone@users.noreply.github.com> Co-authored-by: natan <2222natan@gmail.com> Co-authored-by: Dominika <sokolov.dominika@gmail.com>
This commit is contained in:
parent
e430b82ccb
commit
e83516bb7d
|
@ -1,180 +1,245 @@
|
|||
package me.zeroeightsix.kami.module.modules.misc
|
||||
|
||||
import baritone.api.pathing.goals.Goal
|
||||
import baritone.api.pathing.goals.GoalNear
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import me.zeroeightsix.kami.event.Phase
|
||||
import me.zeroeightsix.kami.event.SafeClientEvent
|
||||
import me.zeroeightsix.kami.mixin.extension.rightClickDelayTimer
|
||||
import me.zeroeightsix.kami.event.events.OnUpdateWalkingPlayerEvent
|
||||
import me.zeroeightsix.kami.event.events.RenderWorldEvent
|
||||
import me.zeroeightsix.kami.manager.managers.PlayerPacketManager
|
||||
import me.zeroeightsix.kami.module.Module
|
||||
import me.zeroeightsix.kami.process.AutoObsidianProcess
|
||||
import me.zeroeightsix.kami.setting.ModuleConfig.setting
|
||||
import me.zeroeightsix.kami.util.BaritoneUtils
|
||||
import me.zeroeightsix.kami.util.EntityUtils
|
||||
import me.zeroeightsix.kami.util.EntityUtils.flooredPosition
|
||||
import me.zeroeightsix.kami.util.*
|
||||
import me.zeroeightsix.kami.util.EntityUtils.getDroppedItem
|
||||
import me.zeroeightsix.kami.util.InventoryUtils
|
||||
import me.zeroeightsix.kami.util.WorldUtils.isPlaceableForChest
|
||||
import me.zeroeightsix.kami.util.math.RotationUtils
|
||||
import me.zeroeightsix.kami.util.text.MessageSendHelper.sendChatMessage
|
||||
import me.zeroeightsix.kami.util.WorldUtils.placeBlock
|
||||
import me.zeroeightsix.kami.util.color.ColorHolder
|
||||
import me.zeroeightsix.kami.util.graphics.ESPRenderer
|
||||
import me.zeroeightsix.kami.util.math.RotationUtils.getRotationTo
|
||||
import me.zeroeightsix.kami.util.math.VectorUtils
|
||||
import me.zeroeightsix.kami.util.math.VectorUtils.toVec3dCenter
|
||||
import me.zeroeightsix.kami.util.text.MessageSendHelper
|
||||
import me.zeroeightsix.kami.util.threads.defaultScope
|
||||
import me.zeroeightsix.kami.util.threads.onMainThreadSafe
|
||||
import me.zeroeightsix.kami.util.threads.safeListener
|
||||
import net.minecraft.block.BlockShulkerBox
|
||||
import net.minecraft.block.state.IBlockState
|
||||
import net.minecraft.client.audio.PositionedSoundRecord
|
||||
import net.minecraft.client.gui.inventory.GuiShulkerBox
|
||||
import net.minecraft.enchantment.EnchantmentHelper
|
||||
import net.minecraft.init.Blocks
|
||||
import net.minecraft.init.Enchantments
|
||||
import net.minecraft.init.Items
|
||||
import net.minecraft.init.SoundEvents
|
||||
import net.minecraft.inventory.ClickType
|
||||
import net.minecraft.network.play.client.CPacketEntityAction
|
||||
import net.minecraft.network.play.client.CPacketPlayerDigging
|
||||
import net.minecraft.network.play.client.CPacketPlayerTryUseItemOnBlock
|
||||
import net.minecraft.util.EnumFacing
|
||||
import net.minecraft.util.EnumHand
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.RayTraceResult
|
||||
import net.minecraft.util.math.Vec3d
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent
|
||||
import org.kamiblue.commons.extension.ceilToInt
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.kamiblue.commons.interfaces.DisplayEnum
|
||||
import org.kamiblue.event.listener.listener
|
||||
|
||||
@Module.Info(
|
||||
name = "AutoObsidian",
|
||||
category = Module.Category.MISC,
|
||||
description = "Mines ender chest automatically to fill inventory with obsidian"
|
||||
description = "Breaks down Ender Chests to restock obsidian"
|
||||
)
|
||||
object AutoObsidian : Module() {
|
||||
private val searchShulker = setting("SearchShulker", false)
|
||||
private val autoRefill = setting("AutoRefill", false)
|
||||
private val threshold = setting("RefillThreshold", 8, 1..56, 1, { autoRefill.value })
|
||||
private val targetStacks = setting("TargetStacks", 1, 1..20, 1)
|
||||
private val delayTicks = setting("DelayTicks", 5, 0..10, 1)
|
||||
private val fillMode by setting("FillMode", FillMode.TARGET_STACKS)
|
||||
private val searchShulker by setting("SearchShulker", false)
|
||||
private val leaveEmptyShulkers by setting("LeaveEmptyShulkers", true, { searchShulker })
|
||||
private val autoRefill by setting("AutoRefill", false, { fillMode != FillMode.INFINITE })
|
||||
private val threshold by setting("RefillThreshold", 8, 1..56, 1, { autoRefill && fillMode != FillMode.INFINITE })
|
||||
private val targetStacks by setting("TargetStacks", 1, 1..20, 1, { fillMode == FillMode.TARGET_STACKS })
|
||||
private val delayTicks by setting("DelayTicks", 5, 0..10, 1)
|
||||
private val rotationMode by setting("RotationMode", RotationMode.SPOOF)
|
||||
private val maxReach by setting("MaxReach", 4.5f, 2.0f..6.0f, 0.1f)
|
||||
|
||||
enum class State {
|
||||
SEARCHING, PLACING, PRE_MINING, MINING, COLLECTING, DONE
|
||||
private enum class FillMode(override val displayName: String, val message: String) : DisplayEnum {
|
||||
TARGET_STACKS("Target Stacks", "Target stacks reached"),
|
||||
FILL_INVENTORY("Fill Inventory", "Inventory filled"),
|
||||
INFINITE("Infinite", "")
|
||||
}
|
||||
|
||||
private enum class SearchingState {
|
||||
PLACING, OPENING, PRE_MINING, MINING, COLLECTING, DONE
|
||||
enum class State(override val displayName: String) : DisplayEnum {
|
||||
SEARCHING("Searching"),
|
||||
PLACING("Placing"),
|
||||
PRE_MINING("Pre Mining"),
|
||||
MINING("Mining"),
|
||||
COLLECTING("Collecting"),
|
||||
DONE("Done")
|
||||
}
|
||||
|
||||
var pathing = false
|
||||
var goal: BlockPos? = null
|
||||
var state = State.SEARCHING
|
||||
private enum class SearchingState(override val displayName: String) : DisplayEnum {
|
||||
PLACING("Placing"),
|
||||
OPENING("Opening"),
|
||||
PRE_MINING("Pre Mining"),
|
||||
MINING("Mining"),
|
||||
COLLECTING("Collecting"),
|
||||
DONE("Done")
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
private enum class RotationMode(override val displayName: String) : DisplayEnum {
|
||||
OFF("Off"),
|
||||
SPOOF("Spoof"),
|
||||
VIEW_LOCK("View Lock")
|
||||
}
|
||||
|
||||
var goal: Goal? = null; private set
|
||||
var state = State.SEARCHING; private set
|
||||
private var searchingState = SearchingState.PLACING
|
||||
|
||||
private var active = false
|
||||
private var searchingState = SearchingState.PLACING
|
||||
private var playerPos = BlockPos(0, -1, 0)
|
||||
private var placingPos = BlockPos(0, -1, 0)
|
||||
private var shulkerBoxId = 0
|
||||
private var tickCount = 0
|
||||
private var openTime = 0L
|
||||
private var lastHitVec: Vec3d? = null
|
||||
|
||||
private val tickTimer = TickTimer(TimeUnit.TICKS)
|
||||
private val rotateTimer = TickTimer(TimeUnit.TICKS)
|
||||
private val shulkerOpenTimer = TickTimer(TimeUnit.TICKS)
|
||||
private val renderer = ESPRenderer().apply { aFilled = 33; aOutline = 233 }
|
||||
|
||||
override fun isActive(): Boolean {
|
||||
return isEnabled && active
|
||||
}
|
||||
|
||||
override fun onEnable() {
|
||||
if (mc.player == null) return
|
||||
state = State.SEARCHING
|
||||
}
|
||||
|
||||
override fun onDisable() {
|
||||
reset()
|
||||
}
|
||||
|
||||
init {
|
||||
safeListener<TickEvent.ClientTickEvent> {
|
||||
if (it.phase != TickEvent.Phase.END) return@safeListener
|
||||
|
||||
/* Just a delay */
|
||||
if (tickCount < delayTicks.value) {
|
||||
tickCount++
|
||||
return@safeListener
|
||||
} else tickCount = 0
|
||||
if (it.phase != TickEvent.Phase.END || !tickTimer.tick(delayTicks.toLong())) return@safeListener
|
||||
|
||||
updateState()
|
||||
when (state) {
|
||||
|
||||
/* Searching states */
|
||||
State.SEARCHING -> {
|
||||
if (searchShulker.value) {
|
||||
when (searchingState) {
|
||||
SearchingState.PLACING -> placeShulker(placingPos)
|
||||
SearchingState.OPENING -> openShulker(placingPos)
|
||||
SearchingState.PRE_MINING -> mineBlock(placingPos, true)
|
||||
SearchingState.MINING -> mineBlock(placingPos, false)
|
||||
SearchingState.COLLECTING -> collectDroppedItem(shulkerBoxId)
|
||||
SearchingState.DONE -> {
|
||||
/* Positions need to be updated after moving while collecting dropped shulker box */
|
||||
val currentPos = player.flooredPosition
|
||||
playerPos = currentPos
|
||||
setPlacingPos()
|
||||
}
|
||||
}
|
||||
} else searchingState = SearchingState.DONE
|
||||
searchingState()
|
||||
}
|
||||
State.PLACING -> {
|
||||
placeEnderChest(placingPos)
|
||||
}
|
||||
State.PRE_MINING -> {
|
||||
mineBlock(placingPos, true)
|
||||
}
|
||||
State.MINING -> {
|
||||
mineBlock(placingPos, false)
|
||||
}
|
||||
State.COLLECTING -> {
|
||||
collectDroppedItem(Blocks.OBSIDIAN.id)
|
||||
}
|
||||
|
||||
/* Main states */
|
||||
State.PLACING -> placeEnderChest(placingPos)
|
||||
State.PRE_MINING -> mineBlock(placingPos, true)
|
||||
State.MINING -> mineBlock(placingPos, false)
|
||||
State.COLLECTING -> collectDroppedItem(49)
|
||||
State.DONE -> {
|
||||
if (!autoRefill.value) {
|
||||
sendChatMessage("$chatName Reached target stacks, disabling.")
|
||||
AutoObsidian.disable()
|
||||
if (!autoRefill) {
|
||||
MessageSendHelper.sendChatMessage("$chatName ${fillMode.message}, disabling.")
|
||||
disable()
|
||||
} else {
|
||||
if (active) sendChatMessage("$chatName Reached target stacks, stopping.")
|
||||
if (active) MessageSendHelper.sendChatMessage("$chatName ${fillMode.message}, stopping.")
|
||||
reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisable() {
|
||||
BaritoneUtils.primary?.pathingControlManager?.mostRecentInControl()?.let {
|
||||
if (it.isPresent && it.get() == AutoObsidianProcess) {
|
||||
it.get().onLostControl()
|
||||
listener<RenderWorldEvent> {
|
||||
if (state != State.DONE) renderer.render(clear = false, cull = true)
|
||||
}
|
||||
|
||||
safeListener<OnUpdateWalkingPlayerEvent> { event ->
|
||||
if (event.phase != Phase.PRE || rotateTimer.tick(20L, false)) return@safeListener
|
||||
val rotation = lastHitVec?.let { getRotationTo(it) } ?: return@safeListener
|
||||
|
||||
when (rotationMode) {
|
||||
RotationMode.SPOOF -> {
|
||||
val packet = PlayerPacketManager.PlayerPacket(rotating = true, rotation = rotation)
|
||||
PlayerPacketManager.addPacket(this@AutoObsidian, packet)
|
||||
}
|
||||
RotationMode.VIEW_LOCK -> {
|
||||
player.rotationYaw = rotation.x
|
||||
player.rotationPitch = rotation.y
|
||||
}
|
||||
else -> {
|
||||
// Rotation off
|
||||
}
|
||||
}
|
||||
}
|
||||
reset()
|
||||
}
|
||||
|
||||
private fun SafeClientEvent.updateState() {
|
||||
val currentPos = player.flooredPosition
|
||||
if (state != State.DONE && placingPos.y == -1) {
|
||||
playerPos = currentPos
|
||||
setPlacingPos()
|
||||
}
|
||||
if (state != State.DONE) {
|
||||
updatePlacingPos()
|
||||
|
||||
if (!active && state != State.DONE) {
|
||||
active = true
|
||||
BaritoneUtils.primary?.pathingControlManager?.registerProcess(AutoObsidianProcess)
|
||||
}
|
||||
if (!active) {
|
||||
active = true
|
||||
BaritoneUtils.primary?.pathingControlManager?.registerProcess(AutoObsidianProcess)
|
||||
}
|
||||
|
||||
/* Tell baritone to get you back to position */
|
||||
if (state != State.DONE && state != State.COLLECTING && searchingState != SearchingState.COLLECTING) {
|
||||
if (currentPos.x != playerPos.x || currentPos.z != playerPos.z) {
|
||||
pathing = true
|
||||
goal = playerPos
|
||||
return
|
||||
} else {
|
||||
pathing = false
|
||||
if (state != State.COLLECTING && searchingState != SearchingState.COLLECTING) {
|
||||
goal = if (player.getDistanceSqToCenter(placingPos) > 2.0) {
|
||||
GoalNear(placingPos, 2)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Updates main state */
|
||||
updateMainState()
|
||||
updateSearchingState()
|
||||
}
|
||||
|
||||
/* Updates searching state */
|
||||
if (state == State.SEARCHING && searchingState != SearchingState.DONE) {
|
||||
updateSearchingState()
|
||||
} else if (state != State.SEARCHING) {
|
||||
searchingState = SearchingState.PLACING
|
||||
private fun SafeClientEvent.updatePlacingPos() {
|
||||
val eyePos = player.getPositionEyes(1f)
|
||||
if (isPositionValid(placingPos, world.getBlockState(placingPos), eyePos)) return
|
||||
|
||||
val posList = VectorUtils.getBlockPosInSphere(eyePos, maxReach)
|
||||
.sortedBy { it.distanceSqToCenter(eyePos.x, eyePos.y, eyePos.z) }
|
||||
.map { it to world.getBlockState(it) }
|
||||
.toList()
|
||||
|
||||
val pair = posList.find { it.second.block == Blocks.ENDER_CHEST || it.second.block is BlockShulkerBox }
|
||||
?: posList.find { isPositionValid(it.first, it.second, eyePos) }
|
||||
|
||||
if (pair != null) {
|
||||
placingPos = pair.first
|
||||
renderer.clear()
|
||||
renderer.add(pair.first, ColorHolder(64, 255, 64))
|
||||
} else {
|
||||
MessageSendHelper.sendChatMessage("$chatName No valid position for placing shulker box / ender chest nearby, disabling.")
|
||||
mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f))
|
||||
disable()
|
||||
}
|
||||
}
|
||||
|
||||
private fun SafeClientEvent.isPositionValid(pos: BlockPos, blockState: IBlockState, eyePos: Vec3d) =
|
||||
!world.getBlockState(pos.down()).material.isReplaceable
|
||||
&& (blockState.block.let { it == Blocks.ENDER_CHEST || it is BlockShulkerBox }
|
||||
|| WorldUtils.isPlaceable(pos))
|
||||
&& world.isAirBlock(pos.up())
|
||||
&& world.rayTraceBlocks(eyePos, pos.toVec3dCenter())?.let { it.typeOfHit == RayTraceResult.Type.MISS } ?: true
|
||||
|
||||
private fun SafeClientEvent.updateMainState() {
|
||||
val obbyCount = countObby()
|
||||
val passCountCheck = checkObbyCount()
|
||||
|
||||
state = when {
|
||||
state == State.DONE && autoRefill.value && InventoryUtils.countItemAll(49) <= threshold.value -> {
|
||||
state == State.DONE && autoRefill && InventoryUtils.countItemAll(Blocks.OBSIDIAN.id) < threshold -> {
|
||||
State.SEARCHING
|
||||
}
|
||||
state == State.COLLECTING && getDroppedItem(49, 8.0f) == null -> {
|
||||
state == State.COLLECTING && (!canPickUpObby() || getDroppedItem(Blocks.OBSIDIAN.id, 8.0f) == null) -> {
|
||||
State.DONE
|
||||
}
|
||||
state != State.DONE && world.isAirBlock(placingPos) && obbyCount >= targetStacks.value -> {
|
||||
state != State.DONE && world.isAirBlock(placingPos) && !passCountCheck -> {
|
||||
State.COLLECTING
|
||||
}
|
||||
state == State.MINING && world.isAirBlock(placingPos) -> {
|
||||
|
@ -183,199 +248,321 @@ object AutoObsidian : Module() {
|
|||
state == State.PLACING && !world.isAirBlock(placingPos) -> {
|
||||
State.PRE_MINING
|
||||
}
|
||||
state == State.SEARCHING && searchingState == SearchingState.DONE && obbyCount < targetStacks.value -> {
|
||||
state == State.SEARCHING && searchingState == SearchingState.DONE && passCountCheck -> {
|
||||
State.PLACING
|
||||
}
|
||||
else -> state
|
||||
else -> {
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can pick up more obsidian:
|
||||
* There must be at least one slot which is either empty, or contains a stack of obsidian less than 64
|
||||
*/
|
||||
private fun SafeClientEvent.canPickUpObby(): Boolean {
|
||||
return fillMode == FillMode.INFINITE || player.inventory?.mainInventory?.any {
|
||||
it.isEmpty || it.item.id == Blocks.OBSIDIAN.id && it.count < 64
|
||||
} ?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* @return `true` if can still place more ender chest
|
||||
*/
|
||||
private fun SafeClientEvent.checkObbyCount(): Boolean {
|
||||
val inventory = InventoryUtils.countItemAll(Blocks.OBSIDIAN.id)
|
||||
val dropped = EntityUtils.getDroppedItems(Blocks.OBSIDIAN.id, 8.0f).sumBy { it.item.count }
|
||||
|
||||
return when (fillMode) {
|
||||
FillMode.TARGET_STACKS -> {
|
||||
((inventory + dropped) / 8.0f).ceilToInt() / 8 <= targetStacks
|
||||
}
|
||||
FillMode.FILL_INVENTORY -> {
|
||||
countEmptySlots() - dropped >= 8
|
||||
}
|
||||
FillMode.INFINITE -> {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun SafeClientEvent.countEmptySlots(): Int {
|
||||
return player.inventory?.mainInventory?.sumBy {
|
||||
when {
|
||||
it.isEmpty -> 64
|
||||
it.item.id == Blocks.OBSIDIAN.id -> 64 - it.count
|
||||
else -> 0
|
||||
}
|
||||
} ?: 0
|
||||
}
|
||||
|
||||
private fun SafeClientEvent.updateSearchingState() {
|
||||
searchingState = when {
|
||||
searchingState == SearchingState.PLACING && InventoryUtils.countItemAll(130) > 0 -> {
|
||||
SearchingState.DONE
|
||||
}
|
||||
searchingState == SearchingState.COLLECTING && getDroppedItem(shulkerBoxId, 8.0f) == null -> {
|
||||
SearchingState.DONE
|
||||
}
|
||||
searchingState == SearchingState.MINING && world.isAirBlock(placingPos) -> {
|
||||
if (InventoryUtils.countItemAll(130) > 0) {
|
||||
SearchingState.COLLECTING
|
||||
} else { /* In case if the shulker wasn't placed due to server lag */
|
||||
SearchingState.PLACING
|
||||
if (state == State.SEARCHING) {
|
||||
if (searchingState != SearchingState.DONE) {
|
||||
searchingState = when {
|
||||
searchingState == SearchingState.PLACING && InventoryUtils.countItemAll(Blocks.ENDER_CHEST.id) > 0 -> {
|
||||
SearchingState.DONE
|
||||
}
|
||||
searchingState == SearchingState.COLLECTING && getDroppedItem(shulkerBoxId, 8.0f) == null -> {
|
||||
SearchingState.DONE
|
||||
}
|
||||
searchingState == SearchingState.MINING && world.isAirBlock(placingPos) -> {
|
||||
if (InventoryUtils.countItemAll(Blocks.ENDER_CHEST.id) > 0) {
|
||||
SearchingState.COLLECTING
|
||||
} else {
|
||||
// In case if the shulker wasn't placed due to server lag
|
||||
SearchingState.PLACING
|
||||
}
|
||||
}
|
||||
searchingState == SearchingState.OPENING && (InventoryUtils.countItemAll(Blocks.ENDER_CHEST.id) > 0
|
||||
|| InventoryUtils.getSlots(0, 35, 0) == null) -> {
|
||||
SearchingState.PRE_MINING
|
||||
}
|
||||
searchingState == SearchingState.PLACING && !world.isAirBlock(placingPos) -> {
|
||||
if (world.getBlockState(placingPos).block is BlockShulkerBox) {
|
||||
SearchingState.OPENING
|
||||
} else {
|
||||
// In case if the shulker wasn't placed due to server lag
|
||||
SearchingState.PRE_MINING
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
searchingState
|
||||
}
|
||||
}
|
||||
}
|
||||
searchingState == SearchingState.OPENING && (InventoryUtils.countItemAll(130) >= 64 || InventoryUtils.getSlots(0, 35, 0) == null) -> {
|
||||
SearchingState.PRE_MINING
|
||||
}
|
||||
searchingState == SearchingState.PLACING && !world.isAirBlock(placingPos) -> {
|
||||
if (world.getBlockState(placingPos).block is BlockShulkerBox) {
|
||||
SearchingState.OPENING
|
||||
} else { /* In case if the shulker wasn't placed due to server lag */
|
||||
SearchingState.PRE_MINING
|
||||
}
|
||||
}
|
||||
else -> searchingState
|
||||
}
|
||||
}
|
||||
|
||||
private fun countObby(): Int {
|
||||
val inventory = InventoryUtils.countItemAll(49)
|
||||
val dropped = EntityUtils.getDroppedItems(49, 8.0f).sumBy { it.item.count }
|
||||
return ((inventory + dropped) / 8.0f).ceilToInt() / 8
|
||||
}
|
||||
|
||||
private fun setPlacingPos() {
|
||||
if (getPlacingPos().y != -1) {
|
||||
placingPos = getPlacingPos()
|
||||
} else {
|
||||
sendChatMessage("$chatName No valid position for placing shulker box / ender chest nearby, disabling.")
|
||||
mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f))
|
||||
this.disable()
|
||||
return
|
||||
searchingState = SearchingState.PLACING
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPlacingPos(): BlockPos {
|
||||
val pos = playerPos
|
||||
var facing = EnumFacing.NORTH
|
||||
for (i in 1..4) {
|
||||
val posOffset = pos.offset(facing)
|
||||
val posOffsetDiagonal = posOffset.offset(facing.rotateY())
|
||||
when {
|
||||
isPlaceableForChest(posOffset) -> return posOffset
|
||||
isPlaceableForChest(posOffset.up()) -> return posOffset.up()
|
||||
isPlaceableForChest(posOffsetDiagonal) -> return posOffsetDiagonal
|
||||
isPlaceableForChest(posOffsetDiagonal.up()) -> return posOffsetDiagonal.up()
|
||||
else -> facing = facing.rotateY()
|
||||
}
|
||||
}
|
||||
return BlockPos(0, -1, 0)
|
||||
}
|
||||
|
||||
private fun SafeClientEvent.lookAtBlock(pos: BlockPos) {
|
||||
val vec3d = Vec3d(pos).add(0.5, 0.0, 0.5)
|
||||
val lookAt = RotationUtils.getRotationTo(vec3d)
|
||||
player.rotationYaw = lookAt.x
|
||||
player.rotationPitch = lookAt.y
|
||||
}
|
||||
|
||||
/* Tasks */
|
||||
private fun SafeClientEvent.placeShulker(pos: BlockPos) {
|
||||
for (i in 219..234) {
|
||||
if (InventoryUtils.getSlotsHotbar(i) == null) {
|
||||
if (i != 234) continue else {
|
||||
sendChatMessage("$chatName No shulker box was found in hotbar, disabling.")
|
||||
mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f))
|
||||
disable()
|
||||
return
|
||||
private fun SafeClientEvent.searchingState() {
|
||||
if (searchShulker) {
|
||||
when (searchingState) {
|
||||
SearchingState.PLACING -> {
|
||||
placeShulker(placingPos)
|
||||
}
|
||||
SearchingState.OPENING -> {
|
||||
openShulker(placingPos)
|
||||
}
|
||||
SearchingState.PRE_MINING -> {
|
||||
mineBlock(placingPos, true)
|
||||
}
|
||||
SearchingState.MINING -> {
|
||||
mineBlock(placingPos, false)
|
||||
}
|
||||
SearchingState.COLLECTING -> {
|
||||
collectDroppedItem(shulkerBoxId)
|
||||
}
|
||||
SearchingState.DONE -> {
|
||||
updatePlacingPos()
|
||||
}
|
||||
}
|
||||
shulkerBoxId = i
|
||||
InventoryUtils.swapSlotToItem(i)
|
||||
break
|
||||
} else {
|
||||
searchingState = SearchingState.DONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun SafeClientEvent.placeShulker(pos: BlockPos) {
|
||||
if (InventoryUtils.getSlotsHotbar(shulkerBoxId) == null && InventoryUtils.getSlotsNoHotbar(shulkerBoxId) != null) {
|
||||
InventoryUtils.moveToHotbar(shulkerBoxId, Items.DIAMOND_PICKAXE.id)
|
||||
} else {
|
||||
for (i in 219..234) {
|
||||
if (InventoryUtils.getSlotsHotbar(i) == null) {
|
||||
if (i == 234) {
|
||||
MessageSendHelper.sendChatMessage("$chatName No shulker box was found in hotbar, disabling.")
|
||||
mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f))
|
||||
disable()
|
||||
}
|
||||
continue
|
||||
}
|
||||
shulkerBoxId = i
|
||||
InventoryUtils.swapSlotToItem(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (world.getBlockState(pos).block !is BlockShulkerBox) {
|
||||
lookAtBlock(pos)
|
||||
player.isSneaking = true
|
||||
playerController.processRightClickBlock(player, world, pos.down(), EnumFacing.UP, mc.objectMouseOver.hitVec, EnumHand.MAIN_HAND)
|
||||
player.swingArm(EnumHand.MAIN_HAND)
|
||||
mc.rightClickDelayTimer = 4
|
||||
placeBlock(pos)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SafeClientEvent.placeEnderChest(pos: BlockPos) {
|
||||
if (InventoryUtils.getSlotsHotbar(130) == null && InventoryUtils.getSlotsNoHotbar(130) != null) {
|
||||
InventoryUtils.moveToHotbar(130, 278)
|
||||
/* Case where we need to move ender chests into the hotbar */
|
||||
if (InventoryUtils.getSlotsHotbar(Blocks.ENDER_CHEST.id) == null && InventoryUtils.getSlotsNoHotbar(Blocks.ENDER_CHEST.id) != null) {
|
||||
InventoryUtils.moveToHotbar(Blocks.ENDER_CHEST.id, Items.DIAMOND_PICKAXE.id)
|
||||
return
|
||||
} else if (InventoryUtils.getSlots(0, 35, 130) == null) {
|
||||
if (searchShulker.value) {
|
||||
} else if (InventoryUtils.getSlots(0, 35, Blocks.ENDER_CHEST.id) == null) {
|
||||
/* Case where we are out of ender chests */
|
||||
if (searchShulker) {
|
||||
state = State.SEARCHING
|
||||
} else {
|
||||
sendChatMessage("$chatName No ender chest was found in inventory, disabling.")
|
||||
MessageSendHelper.sendChatMessage("$chatName No ender chest was found in inventory, disabling.")
|
||||
mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f))
|
||||
disable()
|
||||
return
|
||||
}
|
||||
}
|
||||
InventoryUtils.swapSlotToItem(130)
|
||||
|
||||
lookAtBlock(pos)
|
||||
player.isSneaking = true
|
||||
playerController.processRightClickBlock(player, world, pos.down(), mc.objectMouseOver.sideHit, mc.objectMouseOver.hitVec, EnumHand.MAIN_HAND)
|
||||
player.swingArm(EnumHand.MAIN_HAND)
|
||||
mc.rightClickDelayTimer = 4
|
||||
/* Else, we already have ender chests in the hotbar */
|
||||
InventoryUtils.swapSlotToItem(Blocks.ENDER_CHEST.id)
|
||||
|
||||
placeBlock(pos)
|
||||
}
|
||||
|
||||
|
||||
private fun SafeClientEvent.openShulker(pos: BlockPos) {
|
||||
lookAtBlock(pos)
|
||||
if (mc.currentScreen !is GuiShulkerBox) {
|
||||
/* Added a delay here so it doesn't spam right click and get you kicked */
|
||||
if (System.currentTimeMillis() >= openTime + 2000L) {
|
||||
openTime = System.currentTimeMillis()
|
||||
playerController.processRightClickBlock(player, world, pos, mc.objectMouseOver.sideHit, mc.objectMouseOver.hitVec, EnumHand.MAIN_HAND)
|
||||
}
|
||||
} else {
|
||||
/* Extra delay here to wait for the item list to be loaded */
|
||||
Executors.newSingleThreadScheduledExecutor().schedule({
|
||||
val currentContainer = player.openContainer
|
||||
var enderChestSlot = -1
|
||||
for (i in 0..26) {
|
||||
if (currentContainer.inventory[i].item == Blocks.ENDER_CHEST) {
|
||||
enderChestSlot = i
|
||||
}
|
||||
}
|
||||
if (enderChestSlot != -1) {
|
||||
playerController.windowClick(currentContainer.windowId, enderChestSlot, 0, ClickType.QUICK_MOVE, player)
|
||||
if (mc.currentScreen is GuiShulkerBox) {
|
||||
val container = player.openContainer
|
||||
val slot = container.inventory.subList(0, 27).indexOfFirst { it.item.id == Blocks.ENDER_CHEST.id }
|
||||
|
||||
if (slot != -1) {
|
||||
InventoryUtils.inventoryClick(container.windowId, slot, 0, ClickType.QUICK_MOVE)
|
||||
player.closeScreen()
|
||||
} else if (shulkerOpenTimer.tick(100, false)) { // Wait for maximum of 5 seconds
|
||||
if (leaveEmptyShulkers && container.inventory.subList(0, 27).indexOfFirst { it.item.id != Items.AIR.id } == -1) {
|
||||
searchingState = SearchingState.PRE_MINING
|
||||
player.closeScreen()
|
||||
} else {
|
||||
sendChatMessage("$chatName No ender chest was found in shulker, disabling.")
|
||||
MessageSendHelper.sendChatMessage("$chatName No ender chest was found in shulker, disabling.")
|
||||
mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f))
|
||||
disable()
|
||||
}
|
||||
}, delayTicks.value * 50L, TimeUnit.MILLISECONDS)
|
||||
}
|
||||
} else {
|
||||
val side = EnumFacing.getDirectionFromEntityLiving(pos, player)
|
||||
val hitVecOffset = WorldUtils.getHitVecOffset(side)
|
||||
|
||||
lastHitVec = WorldUtils.getHitVec(pos, side)
|
||||
rotateTimer.reset()
|
||||
|
||||
if (shulkerOpenTimer.tick(50)) {
|
||||
defaultScope.launch {
|
||||
delay(10L)
|
||||
onMainThreadSafe {
|
||||
connection.sendPacket(CPacketPlayerTryUseItemOnBlock(pos, side, EnumHand.MAIN_HAND, hitVecOffset.x.toFloat(), hitVecOffset.y.toFloat(), hitVecOffset.z.toFloat()))
|
||||
player.swingArm(EnumHand.MAIN_HAND)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun SafeClientEvent.mineBlock(pos: BlockPos, pre: Boolean) {
|
||||
if (InventoryUtils.getSlotsHotbar(278) == null && InventoryUtils.getSlotsNoHotbar(278) != null) {
|
||||
InventoryUtils.moveToHotbar(278, 130)
|
||||
return
|
||||
} else if (InventoryUtils.getSlots(0, 35, 278) == null) {
|
||||
sendChatMessage("$chatName No pickaxe was found in inventory, disabling.")
|
||||
private fun SafeClientEvent.placeBlock(pos: BlockPos) {
|
||||
val pair = WorldUtils.getNeighbour(pos, 1, 6.5f)
|
||||
?: run {
|
||||
MessageSendHelper.sendChatMessage("$chatName Can't find neighbour block")
|
||||
return
|
||||
}
|
||||
|
||||
lastHitVec = WorldUtils.getHitVec(pair.second, pair.first)
|
||||
rotateTimer.reset()
|
||||
|
||||
connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.START_SNEAKING))
|
||||
|
||||
defaultScope.launch {
|
||||
delay(10L)
|
||||
onMainThreadSafe {
|
||||
placeBlock(pair.second, pair.first)
|
||||
}
|
||||
|
||||
delay(10L)
|
||||
onMainThreadSafe {
|
||||
connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.STOP_SNEAKING))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first hotbar slot of a diamond pickaxe that does not have the silk touch enchantment.
|
||||
* @return The position of the pickaxe. -1 if there is no match.
|
||||
*/
|
||||
private fun SafeClientEvent.getHotbarNonSilkTouchPick(): Int {
|
||||
val slotsWithPickaxes = InventoryUtils.getSlotsHotbar(Items.DIAMOND_PICKAXE.id)
|
||||
?: return -1
|
||||
|
||||
for (slot in slotsWithPickaxes) {
|
||||
if (EnchantmentHelper.getEnchantmentLevel(Enchantments.SILK_TOUCH, player.inventory.getStackInSlot(slot)) == 0) {
|
||||
return slot
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first non-hotbar slot of a diamond pickaxe that does not have the silk touch enchantment.
|
||||
* @return The position of the pickaxe. -1 if there is no match.
|
||||
*/
|
||||
private fun SafeClientEvent.getInventoryNonSilkTouchPick(): Int {
|
||||
val slotsWithPickaxes = InventoryUtils.getSlotsNoHotbar(Items.DIAMOND_PICKAXE.id)
|
||||
?: return -1
|
||||
|
||||
for (slot in slotsWithPickaxes) {
|
||||
if (EnchantmentHelper.getEnchantmentLevel(Enchantments.SILK_TOUCH, player.inventory.getStackInSlot(slot)) == 0) {
|
||||
return slot
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps the active hotbar slot to one which has a valid pickaxe (i.e. non-silk touch). If there is no valid pickaxe,
|
||||
* disable the module.
|
||||
*/
|
||||
private fun SafeClientEvent.swapToValidPickaxe() {
|
||||
var hotbarPickaxeSlot = getHotbarNonSilkTouchPick()
|
||||
val inventoryPickaxeSlot = getInventoryNonSilkTouchPick()
|
||||
|
||||
if ((hotbarPickaxeSlot == -1) && (inventoryPickaxeSlot != -1)) {
|
||||
/* Note that slot 36 in windowId 0 is the same as slot 0 in the hotbar */
|
||||
InventoryUtils.moveToSlot(0, inventoryPickaxeSlot, 36)
|
||||
hotbarPickaxeSlot = 0
|
||||
} else if (hotbarPickaxeSlot == -1) {
|
||||
MessageSendHelper.sendChatMessage("No valid pickaxe was found in inventory.")
|
||||
mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f))
|
||||
disable()
|
||||
return
|
||||
}
|
||||
InventoryUtils.swapSlotToItem(278)
|
||||
lookAtBlock(pos)
|
||||
|
||||
/* Packet mining lol */
|
||||
if (pre) {
|
||||
connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.START_DESTROY_BLOCK, pos, mc.objectMouseOver.sideHit))
|
||||
if (state != State.SEARCHING) state = State.MINING else searchingState = SearchingState.MINING
|
||||
} else {
|
||||
connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.STOP_DESTROY_BLOCK, pos, mc.objectMouseOver.sideHit))
|
||||
InventoryUtils.swapSlot(hotbarPickaxeSlot)
|
||||
}
|
||||
|
||||
private fun SafeClientEvent.mineBlock(pos: BlockPos, pre: Boolean) {
|
||||
if (pre) swapToValidPickaxe()
|
||||
|
||||
val side = EnumFacing.getDirectionFromEntityLiving(pos, player)
|
||||
lastHitVec = WorldUtils.getHitVec(pos, side)
|
||||
rotateTimer.reset()
|
||||
|
||||
defaultScope.launch {
|
||||
delay(5L)
|
||||
onMainThreadSafe {
|
||||
if (pre) {
|
||||
connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.START_DESTROY_BLOCK, pos, side))
|
||||
if (state != State.SEARCHING) state = State.MINING else searchingState = SearchingState.MINING
|
||||
} else {
|
||||
connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.STOP_DESTROY_BLOCK, pos, side))
|
||||
}
|
||||
player.swingArm(EnumHand.MAIN_HAND)
|
||||
}
|
||||
}
|
||||
|
||||
player.swingArm(EnumHand.MAIN_HAND)
|
||||
}
|
||||
|
||||
private fun collectDroppedItem(itemId: Int) {
|
||||
pathing = if (getDroppedItem(itemId, 16.0f) != null) {
|
||||
goal = getDroppedItem(itemId, 16.0f)
|
||||
true
|
||||
} else false
|
||||
goal = if (getDroppedItem(itemId, 8.0f) != null) {
|
||||
GoalNear(getDroppedItem(itemId, 8.0f), 0)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun reset() {
|
||||
active = false
|
||||
pathing = false
|
||||
goal = null
|
||||
searchingState = SearchingState.PLACING
|
||||
playerPos = BlockPos(0, -1, 0)
|
||||
placingPos = BlockPos(0, -1, 0)
|
||||
tickCount = 0
|
||||
lastHitVec = null
|
||||
}
|
||||
/* End of tasks */
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package me.zeroeightsix.kami.module.modules.misc
|
|||
import me.zeroeightsix.kami.mixin.extension.syncCurrentPlayItem
|
||||
import me.zeroeightsix.kami.module.Module
|
||||
import me.zeroeightsix.kami.setting.ModuleConfig.setting
|
||||
import me.zeroeightsix.kami.util.InventoryUtils
|
||||
import me.zeroeightsix.kami.util.combat.CombatUtils
|
||||
import me.zeroeightsix.kami.util.threads.safeListener
|
||||
import net.minecraft.block.state.IBlockState
|
||||
|
@ -57,7 +58,7 @@ object AutoTool : Module() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun equipBestTool(blockState: IBlockState) {
|
||||
fun equipBestTool(blockState: IBlockState) {
|
||||
var bestSlot = -1
|
||||
var max = 0.0
|
||||
|
||||
|
@ -75,12 +76,7 @@ object AutoTool : Module() {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (bestSlot != -1) equip(bestSlot)
|
||||
}
|
||||
|
||||
private fun equip(slot: Int) {
|
||||
mc.player.inventory.currentItem = slot
|
||||
mc.playerController.syncCurrentPlayItem()
|
||||
if (bestSlot != -1) InventoryUtils.swapSlot(bestSlot)
|
||||
}
|
||||
|
||||
init {
|
||||
|
|
|
@ -2,9 +2,14 @@ package me.zeroeightsix.kami.module.modules.player
|
|||
|
||||
import me.zeroeightsix.kami.KamiMod
|
||||
import me.zeroeightsix.kami.event.events.PacketEvent
|
||||
import me.zeroeightsix.kami.mixin.extension.pitch
|
||||
import me.zeroeightsix.kami.mixin.extension.yaw
|
||||
import me.zeroeightsix.kami.module.Module
|
||||
import me.zeroeightsix.kami.setting.ModuleConfig.setting
|
||||
import me.zeroeightsix.kami.util.text.MessageSendHelper
|
||||
import net.minecraft.network.play.client.CPacketPlayer
|
||||
import net.minecraft.network.play.client.CPacketPlayerDigging
|
||||
import net.minecraft.util.math.Vec3d
|
||||
import me.zeroeightsix.kami.util.threads.safeListener
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent
|
||||
import org.kamiblue.event.listener.listener
|
||||
|
@ -41,7 +46,16 @@ object PacketLogger : Module() {
|
|||
return@listener
|
||||
}
|
||||
|
||||
lines.add("${sdf.format(Date())}\n${it.packet.javaClass.simpleName}\n${it.packet.javaClass}\n${it.packet}\n\n")
|
||||
lines.add("${sdf.format(Date())}\n${it.packet.javaClass.simpleName}\n${it.packet.javaClass}\n${it.packet}")
|
||||
when (it.packet) {
|
||||
is CPacketPlayerDigging -> {
|
||||
lines.add("\nMining - ${it.packet.position}@${it.packet.facing} - ${it.packet.action}")
|
||||
}
|
||||
is CPacketPlayer -> {
|
||||
lines.add("\nRotation - Pitch: ${it.packet.pitch} Yaw: ${it.packet.yaw}")
|
||||
}
|
||||
}
|
||||
lines.add("\n\n")
|
||||
}
|
||||
|
||||
safeListener<TickEvent.ClientTickEvent> {
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
package me.zeroeightsix.kami.process
|
||||
|
||||
import baritone.api.pathing.goals.GoalNear
|
||||
import baritone.api.process.IBaritoneProcess
|
||||
import baritone.api.process.PathingCommand
|
||||
import baritone.api.process.PathingCommandType
|
||||
import me.zeroeightsix.kami.module.modules.misc.AutoObsidian
|
||||
|
||||
/**
|
||||
* Created by Xiaro on 13/07/20.
|
||||
* Updated by Xiaro on 11/09/20
|
||||
*/
|
||||
object AutoObsidianProcess : IBaritoneProcess {
|
||||
|
||||
override fun isTemporary(): Boolean {
|
||||
|
@ -17,22 +12,22 @@ object AutoObsidianProcess : IBaritoneProcess {
|
|||
}
|
||||
|
||||
override fun priority(): Double {
|
||||
return 2.0
|
||||
return 3.0
|
||||
}
|
||||
|
||||
override fun onLostControl() {}
|
||||
|
||||
override fun displayName0(): String {
|
||||
return "AutoObsidian: " + AutoObsidian.state.toString().toLowerCase()
|
||||
return "AutoObsidian: " + AutoObsidian.state.displayName
|
||||
}
|
||||
|
||||
override fun isActive(): Boolean {
|
||||
return AutoObsidian.isActive()
|
||||
}
|
||||
|
||||
override fun onTick(p0: Boolean, p1: Boolean): PathingCommand? {
|
||||
return if (AutoObsidian.pathing && AutoObsidian.goal != null) {
|
||||
PathingCommand(GoalNear(AutoObsidian.goal, 0), PathingCommandType.SET_GOAL_AND_PATH)
|
||||
} else PathingCommand(null, PathingCommandType.REQUEST_PAUSE)
|
||||
override fun onTick(p0: Boolean, p1: Boolean): PathingCommand {
|
||||
return AutoObsidian.goal?.let {
|
||||
PathingCommand(it, PathingCommandType.SET_GOAL_AND_PATH)
|
||||
} ?: PathingCommand(null, PathingCommandType.REQUEST_PAUSE)
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ object TemporaryPauseProcess : IBaritoneProcess {
|
|||
}
|
||||
|
||||
override fun priority(): Double {
|
||||
return 3.0
|
||||
return 5.0
|
||||
}
|
||||
|
||||
override fun isActive(): Boolean {
|
||||
|
|
|
@ -3,4 +3,6 @@ package me.zeroeightsix.kami.util
|
|||
import net.minecraft.block.Block
|
||||
import net.minecraft.item.Item
|
||||
|
||||
val Block.item: Item get() = Item.getItemFromBlock(this)
|
||||
val Block.item: Item get() = Item.getItemFromBlock(this)
|
||||
|
||||
val Block.id: Int get() = Block.getIdFromBlock(this)
|
|
@ -4,6 +4,7 @@ import kotlinx.coroutines.delay
|
|||
import me.zeroeightsix.kami.event.SafeClientEvent
|
||||
import me.zeroeightsix.kami.manager.managers.PlayerPacketManager
|
||||
import me.zeroeightsix.kami.util.math.RotationUtils
|
||||
import me.zeroeightsix.kami.util.math.corners
|
||||
import me.zeroeightsix.kami.util.threads.runSafeSuspend
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.entity.Entity
|
||||
|
@ -105,6 +106,14 @@ object WorldUtils {
|
|||
return mc.world.getBlockState(pos).block == Blocks.WATER
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given [pos] is able to place block in it
|
||||
*
|
||||
* @return true playing is not colliding with [pos] and there is block below it
|
||||
*/
|
||||
fun isPlaceable(pos: BlockPos, ignoreSelfCollide: Boolean = false) = mc.world.getBlockState(pos).material.isReplaceable
|
||||
&& mc.world.checkNoEntityCollision(AxisAlignedBB(pos), if (ignoreSelfCollide) mc.player else null)
|
||||
|
||||
fun rayTraceTo(blockPos: BlockPos): RayTraceResult? {
|
||||
return mc.world.rayTraceBlocks(mc.player.getPositionEyes(1f), Vec3d(blockPos).add(0.5, 0.5, 0.5))
|
||||
}
|
||||
|
@ -123,23 +132,21 @@ object WorldUtils {
|
|||
return Vec3d(vec.x * 0.5 + 0.5, vec.y * 0.5 + 0.5, vec.z * 0.5 + 0.5)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given [pos] is able to place block in it
|
||||
*
|
||||
* @return true playing is not colliding with [pos] and there is block below it
|
||||
*/
|
||||
fun isPlaceable(pos: BlockPos, ignoreSelfCollide: Boolean = false) = mc.world.getBlockState(pos).material.isReplaceable
|
||||
&& mc.world.checkNoEntityCollision(AxisAlignedBB(pos), if (ignoreSelfCollide) mc.player else null)
|
||||
fun SafeClientEvent.rayTraceHitVec(pos: BlockPos) : RayTraceResult? {
|
||||
val eyePos = player.getPositionEyes(1f)
|
||||
val bb = world.getBlockState(pos).getSelectedBoundingBox(world, pos)
|
||||
|
||||
/**
|
||||
* Checks if given [pos] is able to chest (air above) block in it
|
||||
*
|
||||
* @return true playing is not colliding with [pos] and there is block below it
|
||||
*/
|
||||
fun isPlaceableForChest(pos: BlockPos): Boolean {
|
||||
return isPlaceable(pos) && !mc.world.getBlockState(pos.down()).material.isReplaceable && mc.world.isAirBlock(pos.up())
|
||||
return world.rayTraceBlocks(eyePos, bb.center, false, false, true)?.takeIf {
|
||||
it.isEqualTo(pos)
|
||||
} ?: bb.corners(0.95).mapNotNull { corner ->
|
||||
world.rayTraceBlocks(eyePos, corner, false, false, true)?.takeIf { it.isEqualTo(pos) }
|
||||
}.minByOrNull {
|
||||
it.hitVec?.distanceTo(eyePos) ?: 69420.0
|
||||
}
|
||||
}
|
||||
|
||||
private fun RayTraceResult.isEqualTo(pos: BlockPos) = typeOfHit == RayTraceResult.Type.BLOCK && blockPos == pos
|
||||
|
||||
suspend fun buildStructure(
|
||||
placeSpeed: Float,
|
||||
getPlaceInfo: (HashSet<BlockPos>) -> Pair<EnumFacing, BlockPos>?
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package me.zeroeightsix.kami.util.math
|
||||
|
||||
import me.zeroeightsix.kami.util.math.VectorUtils.plus
|
||||
import me.zeroeightsix.kami.util.math.VectorUtils.times
|
||||
import me.zeroeightsix.kami.util.math.VectorUtils.toVec3d
|
||||
import net.minecraft.util.EnumFacing
|
||||
import net.minecraft.util.math.AxisAlignedBB
|
||||
import net.minecraft.util.math.Vec3d
|
||||
|
||||
val AxisAlignedBB.xLength get() = maxX - minX
|
||||
|
||||
val AxisAlignedBB.yLength get() = maxY - minY
|
||||
|
||||
val AxisAlignedBB.zLength get() = maxY - minY
|
||||
|
||||
val AxisAlignedBB.lengths get() = Vec3d(xLength, yLength, zLength)
|
||||
|
||||
fun AxisAlignedBB.corners(scale: Double) : Array<Vec3d> {
|
||||
val growSizes = lengths * (scale - 1.0)
|
||||
return grow(growSizes.x, growSizes.y, growSizes.z).corners()
|
||||
}
|
||||
|
||||
fun AxisAlignedBB.corners() = arrayOf(
|
||||
Vec3d(minX, minY, minZ),
|
||||
Vec3d(minX, minY, maxZ),
|
||||
Vec3d(minX, maxY, minZ),
|
||||
Vec3d(minX, maxY, maxZ),
|
||||
Vec3d(maxX, minY, minZ),
|
||||
Vec3d(maxX, minY, maxZ),
|
||||
Vec3d(maxX, maxY, minZ),
|
||||
Vec3d(maxX, maxY, maxZ),
|
||||
)
|
||||
|
||||
fun AxisAlignedBB.side(side: EnumFacing, scale: Double = 0.5) : Vec3d {
|
||||
val lengths = lengths
|
||||
val sideDirectionVec = side.directionVec.toVec3d()
|
||||
return lengths * sideDirectionVec * scale + center
|
||||
}
|
|
@ -13,16 +13,21 @@ import kotlin.math.roundToInt
|
|||
enum class Direction(
|
||||
override val displayName: String,
|
||||
val displayNameXY: String,
|
||||
val directionVec: Vec3i
|
||||
val directionVec: Vec3i,
|
||||
val isDiagonal: Boolean
|
||||
) : DisplayEnum {
|
||||
NORTH("North", "-Z", Vec3i(0, 0, -1)),
|
||||
NORTH_EAST("North East", "+X -Z", Vec3i(1, 0, -1)),
|
||||
EAST("East", "+X", Vec3i(1, 0, 0)),
|
||||
SOUTH_EAST("South East", "+X +Z", Vec3i(1, 0, 1)),
|
||||
SOUTH("South", "+Z", Vec3i(0, 0, 1)),
|
||||
SOUTH_WEST("South West", "-X +Z", Vec3i(-1, 0, 1)),
|
||||
WEST("West", "-X", Vec3i(-1, 0, 0)),
|
||||
NORTH_WEST("North West", "-X -Z", Vec3i(-1, 0, -1));
|
||||
NORTH("North", "-Z", Vec3i(0, 0, -1), false),
|
||||
NORTH_EAST("North East", "+X -Z", Vec3i(1, 0, -1), true),
|
||||
EAST("East", "+X", Vec3i(1, 0, 0), false),
|
||||
SOUTH_EAST("South East", "+X +Z", Vec3i(1, 0, 1), true),
|
||||
SOUTH("South", "+Z", Vec3i(0, 0, 1), false),
|
||||
SOUTH_WEST("South West", "-X +Z", Vec3i(-1, 0, 1), true),
|
||||
WEST("West", "-X", Vec3i(-1, 0, 0), false),
|
||||
NORTH_WEST("North West", "-X -Z", Vec3i(-1, 0, -1), true);
|
||||
|
||||
fun clockwise(n: Int = 1) = values()[Math.floorMod((ordinal + n), 8)]
|
||||
|
||||
fun counterClockwise(n: Int = 1) = values()[Math.floorMod((ordinal - n), 8)]
|
||||
|
||||
companion object {
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ object VectorUtils {
|
|||
}
|
||||
|
||||
fun Vec3i.toVec3d(offSet: Vec3d): Vec3d {
|
||||
return Vec3d(x+ offSet.x, y + offSet.y, z + offSet.z)
|
||||
return Vec3d(x + offSet.x, y + offSet.y, z + offSet.z)
|
||||
}
|
||||
|
||||
fun Vec3i.toVec3d(xOffset: Double, yOffset: Double, zOffset: Double): Vec3d {
|
||||
|
@ -122,4 +122,16 @@ object VectorUtils {
|
|||
fun Entity.distanceTo(chunkPos: ChunkPos): Double {
|
||||
return hypot(chunkPos.x * 16 + 8 - posX, chunkPos.z * 16 + 8 - posZ)
|
||||
}
|
||||
|
||||
fun Vec3i.multiply(multiplier: Int): Vec3i {
|
||||
return Vec3i(x * multiplier, y * multiplier, z * multiplier)
|
||||
}
|
||||
|
||||
infix operator fun Vec3d.times(vec3d: Vec3d): Vec3d = Vec3d(x * vec3d.x, y * vec3d.y, z * vec3d.z)
|
||||
|
||||
infix operator fun Vec3d.times(multiplier: Double): Vec3d = Vec3d(x * multiplier, y * multiplier, z * multiplier)
|
||||
|
||||
infix operator fun Vec3d.plus(vec3d: Vec3d): Vec3d = add(vec3d)
|
||||
|
||||
infix operator fun Vec3d.minus(vec3d: Vec3d): Vec3d = subtract(vec3d)
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ fun runSafe(block: SafeClientEvent.() -> Unit) {
|
|||
ClientEvent().toSafe()?.let { block(it) }
|
||||
}
|
||||
|
||||
fun <R> runSafe(block: SafeClientEvent.() -> R): R? {
|
||||
fun <R> runSafeR(block: SafeClientEvent.() -> R): R? {
|
||||
return ClientEvent().toSafe()?.let { block(it) }
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue