270 lines
8.9 KiB
Kotlin
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
|
|
}
|
|
}
|