From dab40e2262cbd736ec80b7b7efab35f17cd17b48 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Nov 2025 08:34:18 +0000 Subject: [PATCH] fix: Tracer direction now matches bot facing Root cause: startFiring() sent ChangeFireStateMessage_Start before updating orientation. Client caches orientation at fire-start for tracer rendering, so tracers went in spawn direction. Fix: In startFiring(), calculate target yaw, update orientation, broadcast PlayerState with correct facing, THEN send fire state. Also fixed facingYawUpper to be 0 (relative to body) instead of absolute yaw value. --- .../net/psforever/actors/bot/BotManager.scala | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/src/main/scala/net/psforever/actors/bot/BotManager.scala b/src/main/scala/net/psforever/actors/bot/BotManager.scala index dd59a696..3539876b 100644 --- a/src/main/scala/net/psforever/actors/bot/BotManager.scala +++ b/src/main/scala/net/psforever/actors/bot/BotManager.scala @@ -362,16 +362,16 @@ class BotManager(zone: Zone) extends Actor { player.GUID, player.Position, player.Velocity, - player.Orientation.z, - 0f, - player.Orientation.z, + player.Orientation.z, // facingYaw - body direction + 0f, // facingPitch + 0f, // facingYawUpper - relative to body, 0 = looking straight ahead timestamp, is_crouching = false, is_jumping = false, jump_thrust = false, is_cloaked = false, spectator = false, - weaponInHand = true // Weapon is always drawn (set at spawn) + weaponInHand = true // Weapon is always drawn (set at spawn) ) ) } @@ -538,7 +538,7 @@ class BotManager(zone: Zone) extends Actor { if (ticksSinceAcquired >= recognitionTime) { // Start firing if not already if (!combat.isFiring) { - startFiring(botState, targetPlayer.Name) + startFiring(botState, targetPlayer) // Pass target for orientation calculation combat = combat.copy(isFiring = true, burstStartTick = tickCount, shotsFired = 0) } @@ -578,11 +578,47 @@ class BotManager(zone: Zone) extends Actor { combat } - /** Broadcast that bot started firing - uses LocalEvents like turrets for proper client rendering */ - private def startFiring(botState: BotState, targetName: String): Unit = { - getWeapon(botState.player).foreach { weapon => - log.info(s"${botState.name} is attacking $targetName") - // Use LocalEvents like turrets do for proper tracer rendering + /** + * Broadcast that bot started firing. + * CRITICAL: Must update orientation and broadcast PlayerState BEFORE sending fire state, + * otherwise client will use stale orientation for tracer direction. + */ + private def startFiring(botState: BotState, target: Player): Unit = { + val player = botState.player + + // Calculate yaw to face target + val dx = target.Position.x - player.Position.x + val dy = target.Position.y - player.Position.y + val targetYaw = math.toDegrees(math.atan2(dx, dy)).toFloat + + // Update bot's orientation to face target + player.Orientation = Vector3(0, 0, targetYaw) + + // Broadcast PlayerState with correct orientation BEFORE fire state + // This ensures client has correct facing when fire state arrives + val timestamp = (System.currentTimeMillis() % 65536).toInt + zone.AvatarEvents ! AvatarServiceMessage( + zone.id, + AvatarAction.PlayerState( + player.GUID, + player.Position, + player.Velocity, + targetYaw, // facingYaw - body direction + 0f, // facingPitch + 0f, // facingYawUpper - relative to body, 0 = looking straight + timestamp, + is_crouching = false, + is_jumping = false, + jump_thrust = false, + is_cloaked = false, + spectator = false, + weaponInHand = true + ) + ) + + // NOW send fire state - client should have correct orientation + getWeapon(player).foreach { weapon => + log.info(s"${botState.name} is attacking ${target.Name}") zone.LocalEvents ! LocalServiceMessage( zone.id, LocalAction.SendResponse(ChangeFireStateMessage_Start(weapon.GUID))