package codes.som.hibiscus.features.movement import codes.som.hibiscus.api.event.EventPhase 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.MixinExtUpdatePlayerAbilitiesC2SPacket import codes.som.hibiscus.player import codes.som.hibiscus.subsystems.tps.TPSDetectionSubsystem import codes.som.hibiscus.util.ext.requireExtension import codes.som.hibiscus.util.math.calcMoveVec import codes.som.hibiscus.util.math.getMoveInputVecFromPlayerInput import codes.som.hibiscus.world import net.minecraft.entity.MovementType import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket import net.minecraft.network.packet.c2s.play.UpdatePlayerAbilitiesC2SPacket import net.minecraft.util.math.Vec3d import net.minecraft.util.shape.VoxelShape class Flight : Feature("Flight", FeatureCategory.MOVEMENT) { private var flyKickCounter = 0 private var lockY = 0.0 private val spoofAbilityPackets by values.bool("Spoof Outgoing Ability Packets", true) private val vanillaKickBypass by values.bool("Vanilla Kick Bypass", true) private val mode by values.enum("Mode", FlightMode.HARD) private val speed by values.float("Speed", 1.0f, 0.01f, 50.0f) init { on(cond = { mode == FlightMode.VANILLA }) { _: PlayerTickEvent -> player.abilities.flying = true player.abilities.flySpeed = speed * 0.05f } on(cond = { mode == FlightMode.HARD }) { event: MovePlayerEvent -> if (event.movementType != MovementType.SELF) return@on val movement = calcMoveVec(getMoveInputVecFromPlayerInput(player.input), player.yaw) .multiply(if (player.isSprinting) 1.5 else 0.75) .multiply(1.0, 0.75, 1.0) .multiply(speed.toDouble()) // TODO: Some form of acceleration event.x = movement.x event.y = movement.y event.z = movement.z player.velocity = movement.multiply(0.25, 0.25, 0.25) } on(cond = { mode != FlightMode.VANILLA }) { _: PlayerInputTickEvent -> // player.input.sneaking = false } var lastMoveTime: Long = -1 on(cond = { mode == FlightMode.MAXIMUM }, phase = EventPhase.AFTER) { event: MovePlayerEvent -> if (event.movementType != MovementType.SELF) return@on var tpsMultiplier = TPSDetectionSubsystem.average().coerceAtMost(1.0) if (tpsMultiplier < 1) { tpsMultiplier = 1 - (1 - tpsMultiplier) * 4.0 } val currTime = System.currentTimeMillis() val movement = if (lastMoveTime == -1L || currTime - lastMoveTime >= (1000.0 / 21.0) * tpsMultiplier) { calcMoveVec(getMoveInputVecFromPlayerInput(player.input), player.yaw) .normalize() .multiply(speed.toDouble() * (10.0 - 1 / 8)) .multiply(1.0, 0.5, 1.0) } else { Vec3d.ZERO } if (movement != Vec3d.ZERO) lastMoveTime = currTime event.x = movement.x event.y = movement.y event.z = movement.z player.velocity = Vec3d.ZERO } on(cond = { spoofAbilityPackets }) { event: SendPacketEvent -> val (packet) = event if (packet is UpdatePlayerAbilitiesC2SPacket) { requireExtension(packet) packet.setFlying(player.abilities.allowFlying) } if (packet is ClientCommandC2SPacket) { if (packet.mode == ClientCommandC2SPacket.Mode.PRESS_SHIFT_KEY) { event.cancel() } } } on(cond = { vanillaKickBypass }) { event: NetworkMovingEvent -> flyKickCounter++ if (flyKickCounter >= 16) { if (flyKickCounter == 16) lockY = event.y - 0.0625 val playerNotColliding = world.getBlockCollisions( null, player.boundingBox .offset(0.0, lockY - player.y, 0.0) .expand(0.0625) .stretch(0.0, -0.55, 0.0) ).all(VoxelShape::isEmpty) if (playerNotColliding) event.y = lockY } if (flyKickCounter >= 24) flyKickCounter = 0 } } override fun onEnable() { if (mc.player != null) lockY = player.y - 0.03126 } override fun onDisable() { player.abilities.flying = false player.abilities.flySpeed = 0.05f } override fun createFeatureCommand() = super.createFeatureCommand().apply { alias("fly") } enum class FlightMode { VANILLA, HARD, MAXIMUM; override fun toString(): String { return name[0] + name.substring(1).lowercase() } } }