From 10f682b72ee730171f44daed6f744cd62c0e4c63 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Nov 2025 06:55:29 +0000 Subject: [PATCH] fix: Correct weapon draw timing and add LOS check Weapon draw fix: - Spawn bots with hands down (don't set DrawnSlot on spawn) - After 2 second delay, SET DrawnSlot=2 then broadcast ObjectHeld - This fixes the visual bug where weapon appeared out, then went away Line-of-sight check: - Added Sidedness.equals() check to findTarget and target validation - Uses same logic as turrets - bot and target must be on same side of walls (both inside or both outside a building) - Prevents bots from shooting through walls --- .../net/psforever/actors/bot/BotManager.scala | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/scala/net/psforever/actors/bot/BotManager.scala b/src/main/scala/net/psforever/actors/bot/BotManager.scala index d9b46deb..823854ae 100644 --- a/src/main/scala/net/psforever/actors/bot/BotManager.scala +++ b/src/main/scala/net/psforever/actors/bot/BotManager.scala @@ -20,6 +20,7 @@ import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.util.DefinitionUtil import net.psforever.zones.Zones +import net.psforever.objects.serverobject.interior.Sidedness import scala.collection.mutable import scala.concurrent.ExecutionContext.Implicits.global @@ -152,7 +153,7 @@ class BotManager(zone: Zone) extends Actor { player.Position = position player.Orientation = Vector3(0, 0, 0) DefinitionUtil.applyDefaultLoadout(player) - player.DrawnSlot = 2 // Draw suppressor (slot 2) so bot can shoot + // DON'T set DrawnSlot here - spawn with hands down, draw weapon after delay player.Spawn() val typedSystem = context.system.toTyped @@ -304,7 +305,7 @@ class BotManager(zone: Zone) extends Actor { player.Position = info.spawnPosition player.Orientation = Vector3(0, 0, 0) DefinitionUtil.applyDefaultLoadout(player) - player.DrawnSlot = 2 // Draw suppressor (slot 2) so bot can shoot + // DON'T set DrawnSlot here - spawn with hands down, draw weapon after delay player.Spawn() val typedSystem = context.system.toTyped @@ -335,10 +336,13 @@ class BotManager(zone: Zone) extends Actor { // Check if bot is ready (spawn delay passed) and weapon not yet drawn val isReady = tickCount >= botState.readyTick if (isReady && !botState.weaponDrawn) { + // Set DrawnSlot to weapon slot 2 (suppressor), then broadcast + val previousSlot = player.DrawnSlot + player.DrawnSlot = 2 // Now broadcast the weapon draw - client has had time to load zone.AvatarEvents ! AvatarServiceMessage( zone.id, - AvatarAction.ObjectHeld(player.GUID, player.DrawnSlot, player.LastDrawnSlot) + AvatarAction.ObjectHeld(player.GUID, player.DrawnSlot, previousSlot) ) log.info(s"${botState.name} has drawn a suppressor from its holster") currentBotState = currentBotState.copy(weaponDrawn = true) @@ -433,13 +437,15 @@ class BotManager(zone: Zone) extends Actor { private def findTarget(botState: BotState): Option[Player] = { val player = botState.player val botFaction = botState.avatar.faction + val botSide = player.WhichSide zone.LivePlayers .filter { p => p.isAlive && p.Faction != botFaction && !p.Name.startsWith("xxBOTxx") && // Don't target other bots for now - Vector3.Distance(player.Position, p.Position) <= MaxEngagementRange + Vector3.Distance(player.Position, p.Position) <= MaxEngagementRange && + Sidedness.equals(botSide, p.WhichSide) // Must be on same side of walls (LOS check) } .sortBy(p => Vector3.Distance(player.Position, p.Position)) .headOption @@ -486,7 +492,9 @@ class BotManager(zone: Zone) extends Actor { // Find or validate target val currentTarget = combat.target.flatMap(guid => zone.LivePlayers.find(_.GUID == guid)) val targetStillValid = currentTarget.exists { t => - t.isAlive && Vector3.Distance(player.Position, t.Position) <= MaxEngagementRange + t.isAlive && + Vector3.Distance(player.Position, t.Position) <= MaxEngagementRange && + Sidedness.equals(player.WhichSide, t.WhichSide) // LOS check - same side of walls } if (!targetStillValid) {