hibiscus/src/main/kotlin/codes/som/hibiscus/features/visual/Freecam.kt

270 lines
8.9 KiB
Kotlin

package codes.som.hibiscus.features.visual
import codes.som.hibiscus.HibiscusMod
import codes.som.hibiscus.api.feature.Feature
import codes.som.hibiscus.api.feature.FeatureCategory
import codes.som.hibiscus.events.*
import codes.som.hibiscus.mc
import codes.som.hibiscus.mixins.MixinExtPlayerInteractEntityC2SPacket
import codes.som.hibiscus.player
import codes.som.hibiscus.util.ext.requireExtension
import codes.som.hibiscus.util.graphics.MinecraftRenderPipelineProgress
import codes.som.hibiscus.world
import net.minecraft.client.input.Input
import net.minecraft.client.network.ClientPlayerEntity
import net.minecraft.client.option.Perspective
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityType
import net.minecraft.entity.MovementType
import net.minecraft.nbt.NbtCompound
import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket
import net.minecraft.util.math.MathHelper
import net.minecraft.util.math.Vec3d
import net.minecraft.world.World
import org.lwjgl.glfw.GLFW.GLFW_KEY_TAB
import org.lwjgl.glfw.GLFW.GLFW_PRESS
class Freecam : Feature("Freecam", FeatureCategory.VISUAL) {
private var camEntity: Entity? = null
private var playerInput: Input? = null
private var isControllingPlayer: Boolean = false
private var origYaw = 0F
private var origPitch = 0F
private val toggleKey by values.key("Toggle Key", GLFW_KEY_TAB)
private val speed by values.float("Speed", 1.0f, 0.01f, 50.0f)
init {
var isPlayerTicking = false
on { _: PlayerPreTickEvent -> isPlayerTicking = true }
on { _: PlayerTickEvent -> isPlayerTicking = false }
on { event: WorldCullingEvent -> event.cancel() }
on { event: GetRenderViewEntityEvent ->
val view = camEntity ?: return@on
if (isPlayerTicking)
return@on
if (MinecraftRenderPipelineProgress.isRenderingEntities || MinecraftRenderPipelineProgress.isDrawingUI)
return@on
if (isControllingPlayer && MinecraftRenderPipelineProgress.isProcessingInput)
return@on
event.viewEntity = view
}
on { _: PlayerPreTickEvent ->
val view = camEntity ?: return@on
if (isControllingPlayer) {
origYaw = player.yaw
origPitch = player.pitch
}
player.yaw = origYaw
player.pitch = origPitch
with(view) {
prevHorizontalSpeed = horizontalSpeed
prevX = x
prevY = y
prevZ = z
prevPitch = pitch
prevYaw = yaw
}
if (!isControllingPlayer) {
val yaw = MathHelper.RADIANS_PER_DEGREE * view.yaw
val lookVec = Vec3d(-MathHelper.sin(yaw).toDouble(), 0.0, MathHelper.cos(yaw).toDouble())
var movement = Vec3d.ZERO
if (mc.options.forwardKey.isPressed)
movement = movement.add(0.0, 0.0, 1.0)
if (mc.options.backKey.isPressed)
movement = movement.add(0.0, 0.0, -1.0)
if (mc.options.rightKey.isPressed)
movement = movement.add(1.0, 0.0, 0.0)
if (mc.options.leftKey.isPressed)
movement = movement.add(-1.0, 0.0, 0.0)
if (mc.options.jumpKey.isPressed)
movement = movement.add(0.0, 1.0, 0.0)
if (mc.options.sneakKey.isPressed)
movement = movement.add(0.0, -1.0, 0.0)
// one day i will learn the matrix math for this
movement = lookVec
.multiply(movement.z)
.add(lookVec.rotateY(-MathHelper.HALF_PI).multiply(movement.x))
.add(0.0, movement.y, 0.0)
.multiply(speed.toDouble())
view.move(MovementType.SELF, movement) // Update entity positional state properly by calling move()
}
}
on { event: KeyEvent ->
if (event.action == GLFW_PRESS && event.key == toggleKey) {
isControllingPlayer = !isControllingPlayer
updatePlayerControl()
}
}
var yawPreRender = 0F
var pitchPreRender = 0F
var prevPitchPreRender = 0F
on { _: PreRenderWorldEvent ->
yawPreRender = player.yaw
pitchPreRender = player.pitch
prevPitchPreRender = player.prevPitch
camEntity?.let { view ->
player.yaw = view.yaw
player.pitch = view.pitch
player.prevPitch = view.prevPitch
}
}
var lastMouseX = 0.0
var lastMouseY = 0.0
var mouseDeltaX = 0.0
var mouseDeltaY = 0.0
HibiscusMod.bus.register { _: PostRenderWorldEvent ->
mouseDeltaX = mc.mouse.x - lastMouseX
mouseDeltaY = mc.mouse.y - lastMouseY
lastMouseX = mc.mouse.x
lastMouseY = mc.mouse.y
}
// TODO: On post render world, update the camEntity's view angles based on mouse delta
on { _: PostRenderWorldEvent ->
val view = camEntity ?: return@on
if (isControllingPlayer)
return@on
if (mc.isWindowFocused && mc.currentScreen == null) {
val mouseSensAdj = mc.options.mouseSensitivity * 0.6 + 0.2
val mouseSensAngleCoeff = mouseSensAdj * mouseSensAdj * mouseSensAdj * 8.0
val invertYCoeff = if (mc.options.invertYMouse) -1 else 1
view.changeLookDirection(
mouseDeltaX * mouseSensAngleCoeff,
mouseDeltaY * mouseSensAngleCoeff * invertYCoeff
)
}
}
// TODO: Push / pop origYaw so that the player matches their server-side look angles
var perspectivePreRender: Perspective? = null
on { event: PreRenderEntitiesEvent ->
player.yaw = yawPreRender
player.pitch = pitchPreRender
player.prevPitch = prevPitchPreRender
if (!isControllingPlayer) {
player.yaw = origYaw
player.pitch = origPitch
}
perspectivePreRender = mc.options.perspective
mc.options.perspective = Perspective.THIRD_PERSON_BACK
mc.gameRenderer.camera.update(mc.world, mc.player, true, false, event.delta)
}
on { event: PostRenderEntitiesEvent ->
mc.options.perspective = perspectivePreRender!!
player.yaw = yawPreRender
player.pitch = pitchPreRender
player.prevPitch = prevPitchPreRender
mc.gameRenderer.camera.update(
mc.world,
mc.getCameraEntity() ?: mc.player,
!mc.options.perspective.isFirstPerson,
mc.options.perspective.isFrontView,
event.delta
)
}
on { event: SendPacketEvent ->
val (packet) = event
if (packet is PlayerInteractEntityC2SPacket) {
requireExtension<MixinExtPlayerInteractEntityC2SPacket>(packet)
if (packet.typeHandler.type == PlayerInteractEntityC2SPacket.InteractType.ATTACK && packet.entityId == player.id) {
event.cancel()
}
}
}
on { event: RenderHandEvent ->
if (!isControllingPlayer)
event.cancel()
}
}
private fun updatePlayerControl() {
if (isControllingPlayer) {
if (playerInput != null)
player.input = playerInput
} else {
playerInput = player.input
player.input = Input()
player.input.sneaking = playerInput!!.sneaking
}
}
private fun setupCamera() {
isControllingPlayer = false
camEntity = FreecamControllerEntity(player, world)
updatePlayerControl()
origYaw = player.yaw
origPitch = player.pitch
}
override fun onEnable() {
if (mc.world == null)
return
setupCamera()
}
override fun onDisable() {
if (mc.world == null)
return
isControllingPlayer = true
updatePlayerControl()
camEntity = null
playerInput = null
}
class FreecamControllerEntity(localPlayer: ClientPlayerEntity, world: World) : Entity(EntityType.PLAYER, world) {
init {
updatePositionAndAngles(
localPlayer.x, localPlayer.y, localPlayer.z,
localPlayer.yaw, localPlayer.pitch
)
setRotation(localPlayer.yaw, localPlayer.pitch)
noClip = true
setNoGravity(true)
}
override fun initDataTracker() {
}
override fun readCustomDataFromNbt(nbt: NbtCompound?) {
}
override fun writeCustomDataToNbt(nbt: NbtCompound?) {
}
override fun createSpawnPacket() = null
}
}