From b39f95354a61946abe5ed009158c932add943692 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Tue, 24 Mar 2026 16:37:58 -0400 Subject: [PATCH] extract common messages into a common handler endpoint; created an interface for handlers thus that all handler endpoints can be polled if a specific handler endpoint does not support a message --- .../actors/session/SessionActor.scala | 70 +- .../session/csr/AvatarHandlerLogic.scala | 947 +++++++------- .../session/normal/AvatarHandlerLogic.scala | 1155 ++++++++--------- .../session/normal/GalaxyHandlerLogic.scala | 100 +- .../session/normal/LocalHandlerLogic.scala | 440 +++---- .../session/normal/VehicleHandlerLogic.scala | 639 +++++---- .../spectator/AvatarHandlerLogic.scala | 946 +++++++------- .../spectator/VehicleHandlerLogic.scala | 362 +++--- .../support/CommonHandlerFunctions.scala | 105 ++ .../session/support/CommonHandlerLogic.scala | 67 + .../support/SessionAvatarHandlers.scala | 10 +- .../support/SessionGalaxyHandlers.scala | 7 +- .../support/SessionLocalHandlers.scala | 5 +- .../support/SessionVehicleHandlers.scala | 5 +- .../session/support/ZoningOperations.scala | 2 +- .../net/psforever/objects/Vehicles.scala | 1 - .../resourcesilo/ResourceSiloControl.scala | 2 - .../services/avatar/AvatarAction.scala | 2 +- .../avatar/support/CorpseRemovalActor.scala | 3 +- .../local/support/CaptureFlagManager.scala | 1 - .../local/support/HackCaptureActor.scala | 2 +- 21 files changed, 2391 insertions(+), 2480 deletions(-) create mode 100644 src/main/scala/net/psforever/actors/session/support/CommonHandlerFunctions.scala create mode 100644 src/main/scala/net/psforever/actors/session/support/CommonHandlerLogic.scala diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 578381968..9b83a7789 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -3,7 +3,7 @@ package net.psforever.actors.session import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware, typed} import net.psforever.actors.session.normal.NormalMode -import net.psforever.actors.session.support.ZoningOperations +import net.psforever.actors.session.support.{CommonHandlerFunctions, CommonHandlerLogic, HandlerFilter, ZoningOperations} import net.psforever.objects.TurretDeployable import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.containable.Containable @@ -108,6 +108,21 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con private[this] var mode: PlayerMode = NormalMode private[this] var logic: ModeLogic = _ + private val commonHandlerLogic: CommonHandlerLogic = new CommonHandlerLogic(data, context) + private def listOfHandlers: Seq[CommonHandlerFunctions] = { + if (logic == null) { + List.empty + } else { + List( + logic.avatarResponse, + logic.local, + logic.vehicleResponse, + logic.galaxy, + commonHandlerLogic + ) + } + } + override def postStop(): Unit = { clientKeepAlive.cancel() data.stop() @@ -175,21 +190,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con logic.squad.handle(reply, excluded) case envelope: GenericResponseEnvelope => - val GenericResponseEnvelope(toChannel, guid, reply) = envelope - envelope.stamp match { - case AvatarStamp => - logic.avatarResponse.handle(toChannel, guid, reply) - case LocalStamp => - logic.local.handle(toChannel, guid, reply) - case VehicleStamp => - logic.vehicleResponse.handle(toChannel, guid, reply) - case GalaxyStamp => - logic.galaxy.handle(reply) - case Undelivered => - log.error(s"received a message's response that was not delivered by an event system - $reply") - case unknownStamp => - log.error(s"received a message's response from an unknown event system - stamp: $unknownStamp") - } + handleGenericResponseEnvelope(envelope) case Mountable.MountMessages(tplayer, reply) => logic.mountResponse.handle(tplayer, reply) @@ -375,6 +376,43 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con logic.general.handleReceiveDefaultMessage(default, sender) } + private def handleGenericResponseEnvelope(envelope: GenericResponseEnvelope): Unit = { + val GenericResponseEnvelope(_, guid, reply) = envelope + //try use the stamp to match the specific handler + val primaryHandler = envelope.stamp match { + case Undelivered => + log.error(s"received a message's response that was not processed by an event system - $reply") + CommonHandlerFunctions.HandleAnything + case AvatarStamp => + logic.avatarResponse + case LocalStamp => + logic.local + case VehicleStamp => + logic.vehicleResponse + case GalaxyStamp => + logic.galaxy + case unknownStamp => + log.error(s"received a message's response from an unknown event system - stamp: $unknownStamp") + CommonHandlerFunctions.HandleNothing + } + //try the handler on the input message + lazy val alwaysAllowFilter = HandlerFilter.Allow(guid) + if (primaryHandler.handleWith(guid).isDefinedAt(reply)) { + primaryHandler.receive.apply(reply) + } else if (!primaryHandler.handleWith(alwaysAllowFilter).isDefinedAt(reply)) { + //check a list of all handlers for any potentially valid case + val potentiallyValidHandlers = listOfHandlers.filter(_.handleWith(alwaysAllowFilter).isDefinedAt(reply)) + if (potentiallyValidHandlers.nonEmpty) { + potentiallyValidHandlers + .find(_.handleWith(guid).isDefinedAt(reply)) + .foreach(_.receive.apply(reply)) + //arrive here without processing input, the guard for a handler blocked a case; not gonna fault + } else { + log.error(s"received completely unhandled response message - $envelope") + } + } + } + private def handleGamePkt: PlanetSideGamePacket => Unit = { case packet: ConnectToWorldRequestMessage => logic.general.handleConnectToWorldRequest(packet) diff --git a/src/main/scala/net/psforever/actors/session/csr/AvatarHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/csr/AvatarHandlerLogic.scala index 1ef36f908..0b8cbc2fd 100644 --- a/src/main/scala/net/psforever/actors/session/csr/AvatarHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/AvatarHandlerLogic.scala @@ -1,6 +1,7 @@ // Copyright (c) 2024 PSForever package net.psforever.actors.session.csr +import akka.actor.Actor.Receive import akka.actor.{ActorContext, typed} import net.psforever.actors.session.SessionActor import net.psforever.actors.session.normal.NormalMode @@ -11,10 +12,10 @@ import net.psforever.objects.inventory.Container import net.psforever.objects.serverobject.containable.ContainableBehavior import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.vital.RevivingActivity -import net.psforever.packet.game.{AvatarImplantMessage, CreateShortcutMessage, HitHint, ImplantAction} +import net.psforever.packet.game.{AvatarImplantMessage, CreateShortcutMessage, ImplantAction} import net.psforever.services.avatar.{AvatarAction, AvatarStamp} import net.psforever.services.base.envelope.GenericResponseEnvelope -import net.psforever.services.base.message.{ChangeAmmo, ChangeFireState_Start, ChangeFireState_Stop, ConcealPlayer, EventResponse, GenericObjectAction, HintsAtAttacker, ObjectDelete, PlanetsideAttribute, ReloadTool, SendResponse, SetEmpire, WeaponDryFire} +import net.psforever.services.base.message.{ChangeAmmo, ChangeFireState_Start, ChangeFireState_Stop, EventResponse, ReloadTool, WeaponDryFire} import net.psforever.types.ImplantType // @@ -27,7 +28,7 @@ import net.psforever.objects.inventory.InventoryItem import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal} import net.psforever.objects.zones.Zoning import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent -import net.psforever.packet.game.{ArmorChangedMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, DestroyMessage, DrowningTarget, GenericActionMessage, GenericObjectActionMessage, ItemTransactionResultMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectHeldMessage, OxygenStateMessage, PlanetsideAttributeMessage, PlayerStateMessage, ProjectileStateMessage, ReloadMessage, SetEmpireMessage, UseItemMessage, WeaponDryFireMessage} +import net.psforever.packet.game.{ArmorChangedMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, DestroyMessage, DrowningTarget, GenericActionMessage, GenericObjectActionMessage, ItemTransactionResultMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectHeldMessage, OxygenStateMessage, PlanetsideAttributeMessage, PlayerStateMessage, ProjectileStateMessage, ReloadMessage, UseItemMessage, WeaponDryFireMessage} import net.psforever.services.Service import net.psforever.types.{ChatMessageType, PlanetSideGUID, TransactionType, Vector3} import net.psforever.util.Config @@ -43,538 +44,500 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor - /** - * na - * @param toChannel na - * @param guid na - * @param reply na - */ - def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit = { - val resolvedPlayerGuid = if (player != null && player.HasGUID) { - player.GUID - } else { - Service.defaultPlayerGUID - } - val isNotSameTarget = resolvedPlayerGuid != guid - val isSameTarget = !isNotSameTarget - reply match { - /* special messages */ - case AvatarAction.TeardownConnection() if player.spectator => - context.self ! SessionActor.SetMode(CustomerServiceRepresentativeMode) - context.self.forward(GenericResponseEnvelope(AvatarStamp, toChannel, guid, reply)) + private var tempGuid: PlanetSideGUID = Service.defaultPlayerGUID - case AvatarAction.TeardownConnection() => - context.self ! SessionActor.SetMode(NormalMode) - context.self.forward(GenericResponseEnvelope(AvatarStamp, toChannel, guid, reply)) + override def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit = { + tempGuid = guid + super.handle(toChannel, guid, reply) + } - /* really common messages (very frequently, every life) */ - case pstate @ AvatarAction.PlayerState( - pos, - vel, - yaw, - pitch, - yawUpper, - _, - isCrouching, - isJumping, - jumpThrust, - isCloaking, - isNotRendered, - canSeeReallyFar - ) if isNotSameTarget => - val pstateToSave = pstate.copy(timestamp = 0) - val (lastMsg, lastTime, lastPosition, wasVisible, wasShooting) = ops.lastSeenStreamMessage.get(guid.guid) match { - case Some(SessionAvatarHandlers.LastUpstream(Some(msg), visible, shooting, time)) => (Some(msg), time, msg.pos, visible, shooting) - case _ => (None, 0L, Vector3.Zero, false, None) - } - val drawConfig = Config.app.game.playerDraw //m - val maxRange = drawConfig.rangeMax * drawConfig.rangeMax //sq.m - val ourPosition = player.Position //xyz - val currentDistance = Vector3.DistanceSquared(ourPosition, pos) //sq.m - val inDrawableRange = currentDistance <= maxRange - val now = System.currentTimeMillis() //ms - if ( - sessionLogic.zoning.zoningStatus != Zoning.Status.Deconstructing && - !isNotRendered && inDrawableRange + override def handleWith(guid: PlanetSideGUID): Receive = { + tempGuid = guid + super.handleWith(guid) + } + + def receive: Receive = { + /* special messages */ + case AvatarAction.TeardownConnection if player.spectator => + context.self ! SessionActor.SetMode(CustomerServiceRepresentativeMode) + context.self.forward(GenericResponseEnvelope(AvatarStamp, "", tempGuid, AvatarAction.TeardownConnection)) + + case AvatarAction.TeardownConnection => + context.self ! SessionActor.SetMode(NormalMode) + context.self.forward(GenericResponseEnvelope(AvatarStamp, "", tempGuid, AvatarAction.TeardownConnection)) + + /* really common messages (very frequently, every life) */ + case pstate @ AvatarAction.PlayerState( + pos, + vel, + yaw, + pitch, + yawUpper, + _, + isCrouching, + isJumping, + jumpThrust, + isCloaking, + isNotRendered, + canSeeReallyFar + ) if filter.isNotSameTarget => + val pstateToSave = pstate.copy(timestamp = 0) + val (lastMsg, lastTime, lastPosition, wasVisible, wasShooting) = ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid) match { + case Some(SessionAvatarHandlers.LastUpstream(Some(msg), visible, shooting, time)) => (Some(msg), time, msg.pos, visible, shooting) + case _ => (None, 0L, Vector3.Zero, false, None) + } + val drawConfig = Config.app.game.playerDraw //m + val maxRange = drawConfig.rangeMax * drawConfig.rangeMax //sq.m + val ourPosition = player.Position //xyz + val currentDistance = Vector3.DistanceSquared(ourPosition, pos) //sq.m + val inDrawableRange = currentDistance <= maxRange + val now = System.currentTimeMillis() //ms + if ( + sessionLogic.zoning.zoningStatus != Zoning.Status.Deconstructing && + !isNotRendered && inDrawableRange + ) { + //conditions where visibility is assured + val durationSince = now - lastTime //ms + lazy val previouslyInDrawableRange = Vector3.DistanceSquared(ourPosition, lastPosition) <= maxRange + lazy val targetDelay = { + val populationOver = math.max( + 0, + sessionLogic.localSector.livePlayerList.size - drawConfig.populationThreshold + ) + val distanceAdjustment = math.pow(populationOver / drawConfig.populationStep * drawConfig.rangeStep, 2) //sq.m + val adjustedDistance = currentDistance + distanceAdjustment //sq.m + drawConfig.ranges.lastIndexWhere { dist => adjustedDistance > dist * dist } match { + case -1 => 1 + case index => drawConfig.delays(index) + } + } //ms + if (!wasVisible || + !previouslyInDrawableRange || + durationSince > drawConfig.delayMax || + (!lastMsg.contains(pstateToSave) && + (canSeeReallyFar || + currentDistance < drawConfig.rangeMin * drawConfig.rangeMin || + sessionLogic.general.canSeeReallyFar || + durationSince > targetDelay + ) + ) ) { - //conditions where visibility is assured - val durationSince = now - lastTime //ms - lazy val previouslyInDrawableRange = Vector3.DistanceSquared(ourPosition, lastPosition) <= maxRange - lazy val targetDelay = { - val populationOver = math.max( - 0, - sessionLogic.localSector.livePlayerList.size - drawConfig.populationThreshold + //must draw + sendResponse( + PlayerStateMessage( + filter.resolvedPlayerGuid, + pos, + vel, + yaw, + pitch, + yawUpper, + timestamp = 0, //is this okay? + isCrouching, + isJumping, + jumpThrust, + isCloaking ) - val distanceAdjustment = math.pow(populationOver / drawConfig.populationStep * drawConfig.rangeStep, 2) //sq.m - val adjustedDistance = currentDistance + distanceAdjustment //sq.m - drawConfig.ranges.lastIndexWhere { dist => adjustedDistance > dist * dist } match { - case -1 => 1 - case index => drawConfig.delays(index) - } - } //ms - if (!wasVisible || - !previouslyInDrawableRange || - durationSince > drawConfig.delayMax || - (!lastMsg.contains(pstateToSave) && - (canSeeReallyFar || - currentDistance < drawConfig.rangeMin * drawConfig.rangeMin || - sessionLogic.general.canSeeReallyFar || - durationSince > targetDelay - ) - ) - ) { - //must draw - sendResponse( - PlayerStateMessage( - guid, - pos, - vel, - yaw, - pitch, - yawUpper, - timestamp = 0, //is this okay? - isCrouching, - isJumping, - jumpThrust, - isCloaking - ) - ) - ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, now)) - } else { - //is visible, but skip reinforcement - ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, lastTime)) - } + ) + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, now)) } else { - //conditions where the target is not currently visible - if (wasVisible) { - //the target was JUST PREVIOUSLY visible; one last draw to move target beyond a renderable distance - val lat = (1 + ops.hidingPlayerRandomizer.nextInt(continent.map.scale.height.toInt)).toFloat - sendResponse( - PlayerStateMessage( - guid, - Vector3(1f, lat, 1f), - vel=None, - facingYaw=0f, - facingPitch=0f, - facingYawUpper=0f, - timestamp=0, //is this okay? - is_cloaked = isCloaking - ) + //is visible, but skip reinforcement + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, lastTime)) + } + } else { + //conditions where the target is not currently visible + if (wasVisible) { + //the target was JUST PREVIOUSLY visible; one last draw to move target beyond a renderable distance + val lat = (1 + ops.hidingPlayerRandomizer.nextInt(continent.map.scale.height.toInt)).toFloat + sendResponse( + PlayerStateMessage( + filter.resolvedPlayerGuid, + Vector3(1f, lat, 1f), + vel=None, + facingYaw=0f, + facingPitch=0f, + facingYawUpper=0f, + timestamp=0, //is this okay? + is_cloaked = isCloaking ) - ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, now)) - } else { - //skip drawing altogether - ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, lastTime)) - } + ) + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, now)) + } else { + //skip drawing altogether + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, lastTime)) + } + } + + case AvatarAction.AvatarImplant(ImplantAction.Add, implant_slot, value) + if value == ImplantType.SecondWind.value => + sendResponse(AvatarImplantMessage(filter.resolvedPlayerGuid, ImplantAction.Add, implant_slot, 7)) + //second wind does not normally load its icon into the shortcut hotbar + avatar + .shortcuts + .zipWithIndex + .find { case (s, _) => s.isEmpty} + .foreach { case (_, index) => + sendResponse(CreateShortcutMessage(filter.resolvedPlayerGuid, index + 1, Some(ImplantType.SecondWind.shortcut))) } - case AvatarAction.AvatarImplant(ImplantAction.Add, implant_slot, value) - if value == ImplantType.SecondWind.value => - sendResponse(AvatarImplantMessage(resolvedPlayerGuid, ImplantAction.Add, implant_slot, 7)) - //second wind does not normally load its icon into the shortcut hotbar - avatar - .shortcuts - .zipWithIndex - .find { case (s, _) => s.isEmpty} - .foreach { case (_, index) => - sendResponse(CreateShortcutMessage(resolvedPlayerGuid, index + 1, Some(ImplantType.SecondWind.shortcut))) - } - - case AvatarAction.AvatarImplant(ImplantAction.Remove, implant_slot, value) - if value == ImplantType.SecondWind.value => - sendResponse(AvatarImplantMessage(resolvedPlayerGuid, ImplantAction.Remove, implant_slot, value)) - //second wind does not normally unload its icon from the shortcut hotbar - val shortcut = { - val imp = ImplantType.SecondWind.shortcut - net.psforever.objects.avatar.Shortcut(imp.code, imp.tile) //case class - } - avatar - .shortcuts - .zipWithIndex - .find { case (s, _) => s.contains(shortcut) } - .foreach { case (_, index) => - sendResponse(CreateShortcutMessage(resolvedPlayerGuid, index + 1, None)) - } - - case AvatarAction.AvatarImplant(action, implant_slot, value) => - sendResponse(AvatarImplantMessage(resolvedPlayerGuid, action, implant_slot, value)) - - case AvatarAction.ObjectHeld(slot, _) - if isSameTarget && player.VisibleSlots.contains(slot) => - sendResponse(ObjectHeldMessage(guid, slot, unk1=true)) - //Stop using proximity terminals if player unholsters a weapon - continent.GUID(sessionLogic.terminals.usingMedicalTerminal).collect { - case term: Terminal with ProximityUnit => sessionLogic.terminals.StopUsingProximityUnit(term) - } - if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) { - sessionLogic.zoning.spawn.stopDeconstructing() + case AvatarAction.AvatarImplant(ImplantAction.Remove, implant_slot, value) + if value == ImplantType.SecondWind.value => + sendResponse(AvatarImplantMessage(filter.resolvedPlayerGuid, ImplantAction.Remove, implant_slot, value)) + //second wind does not normally unload its icon from the shortcut hotbar + val shortcut = { + val imp = ImplantType.SecondWind.shortcut + net.psforever.objects.avatar.Shortcut(imp.code, imp.tile) //case class + } + avatar + .shortcuts + .zipWithIndex + .find { case (s, _) => s.contains(shortcut) } + .foreach { case (_, index) => + sendResponse(CreateShortcutMessage(filter.resolvedPlayerGuid, index + 1, None)) } - case AvatarAction.ObjectHeld(slot, _) - if isSameTarget && slot > -1 => - sendResponse(ObjectHeldMessage(guid, slot, unk1=true)) + case AvatarAction.AvatarImplant(action, implant_slot, value) => + sendResponse(AvatarImplantMessage(filter.resolvedPlayerGuid, action, implant_slot, value)) - case AvatarAction.ObjectHeld(_, _) - if isSameTarget => () + case AvatarAction.ObjectHeld(slot, _) + if filter.isSameTarget && player.VisibleSlots.contains(slot) => + sendResponse(ObjectHeldMessage(filter.resolvedPlayerGuid, slot, unk1=true)) + //Stop using proximity terminals if player unholsters a weapon + continent.GUID(sessionLogic.terminals.usingMedicalTerminal).collect { + case term: Terminal with ProximityUnit => sessionLogic.terminals.StopUsingProximityUnit(term) + } + if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) { + sessionLogic.zoning.spawn.stopDeconstructing() + } - case AvatarAction.ObjectHeld(_, previousSlot) => - sendResponse(ObjectHeldMessage(guid, previousSlot, unk1=false)) + case AvatarAction.ObjectHeld(slot, _) + if filter.isSameTarget && slot > -1 => + sendResponse(ObjectHeldMessage(filter.resolvedPlayerGuid, slot, unk1=true)) - case ChangeFireState_Start(weaponGuid) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } => - sendResponse(ChangeFireStateMessage_Start(weaponGuid)) - val entry = ops.lastSeenStreamMessage(guid.guid) - ops.lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = Some(weaponGuid))) + case AvatarAction.ObjectHeld(_, _) + if filter.isSameTarget => () - case ChangeFireState_Start(weaponGuid) - if isNotSameTarget => - sendResponse(ChangeFireStateMessage_Start(weaponGuid)) + case AvatarAction.ObjectHeld(_, previousSlot) => + sendResponse(ObjectHeldMessage(filter.resolvedPlayerGuid, previousSlot, unk1=false)) - case ChangeFireState_Stop(weaponGuid) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { msg => msg.visible || msg.shooting.nonEmpty } => - sendResponse(ChangeFireStateMessage_Stop(weaponGuid)) - val entry = ops.lastSeenStreamMessage(guid.guid) - ops.lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = None)) + case ChangeFireState_Start(weaponGuid) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { _.visible } => + sendResponse(ChangeFireStateMessage_Start(weaponGuid)) + val entry = ops.lastSeenStreamMessage(filter.resolvedPlayerGuid.guid) + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, entry.copy(shooting = Some(weaponGuid))) - case ChangeFireState_Stop(weaponGuid) - if isNotSameTarget => - sendResponse(ChangeFireStateMessage_Stop(weaponGuid)) + case ChangeFireState_Stop(weaponGuid) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { msg => msg.visible || msg.shooting.nonEmpty } => + sendResponse(ChangeFireStateMessage_Stop(weaponGuid)) + val entry = ops.lastSeenStreamMessage(filter.resolvedPlayerGuid.guid) + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, entry.copy(shooting = None)) - case AvatarAction.LoadCreatedPlayer(pkt) if isNotSameTarget => - sendResponse(pkt) + case AvatarAction.LoadCreatedPlayer(pkt) if filter.isNotSameTarget => + sendResponse(pkt) - case AvatarAction.EquipmentCreatedInHand(pkt) if isNotSameTarget => - sendResponse(pkt) + case AvatarAction.EquipmentCreatedInHand(pkt) if filter.isNotSameTarget => + sendResponse(pkt) - case PlanetsideAttribute(target_guid, attributeType, attributeValue) if isNotSameTarget => - sendResponse(PlanetsideAttributeMessage(target_guid, attributeType, attributeValue)) + case AvatarAction.Destroy(victim, killer, weapon, pos) => + // guid = victim // killer = killer + sendResponse(DestroyMessage(victim, killer, weapon, pos)) - case GenericObjectAction(objectGuid, actionCode) if isNotSameTarget => - sendResponse(GenericObjectActionMessage(objectGuid, actionCode)) + case AvatarAction.DestroyDisplay(killer, victim, method, unk) => + sendResponse(ops.destroyDisplayMessage(killer, victim, method, unk)) - case HintsAtAttacker(sourceGuid) if player.isAlive => - sendResponse(HitHint(sourceGuid, guid)) - sessionLogic.zoning.CancelZoningProcess() + case AvatarAction.TerminalOrderResult(terminalGuid, action, result) + if result && (action == TransactionType.Buy || action == TransactionType.Loadout) => + sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) + sessionLogic.terminals.lastTerminalOrderFulfillment = true + AvatarActor.savePlayerData(player) - case AvatarAction.Destroy(victim, killer, weapon, pos) => - // guid = victim // killer = killer - sendResponse(DestroyMessage(victim, killer, weapon, pos)) + case AvatarAction.TerminalOrderResult(terminalGuid, action, result) => + sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) + sessionLogic.terminals.lastTerminalOrderFulfillment = true - case AvatarAction.DestroyDisplay(killer, victim, method, unk) => - sendResponse(ops.destroyDisplayMessage(killer, victim, method, unk)) - - case AvatarAction.TerminalOrderResult(terminalGuid, action, result) - if result && (action == TransactionType.Buy || action == TransactionType.Loadout) => - sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) - sessionLogic.terminals.lastTerminalOrderFulfillment = true - AvatarActor.savePlayerData(player) - - case AvatarAction.TerminalOrderResult(terminalGuid, action, result) => - sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) - sessionLogic.terminals.lastTerminalOrderFulfillment = true - - case AvatarAction.ChangeExosuit( - target, - armor, - exosuit, - subtype, - _, - maxhand, - oldHolsters, - holsters, - oldInventory, - inventory, - drop, - delete - ) if resolvedPlayerGuid == target => - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) - //happening to this player - //cleanup - sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=false)) - (oldHolsters ++ oldInventory ++ delete).foreach { - case (_, dguid) => sendResponse(ObjectDeleteMessage(dguid, unk1=0)) - } - //functionally delete - delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) } - //redraw - if (maxhand) { - TaskWorkflow.execute(HoldNewEquipmentUp(player)( - Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), - 0 - )) - } - //draw free hand - player.FreeHand.Equipment.foreach { obj => + case AvatarAction.ChangeExosuit( + target, + armor, + exosuit, + subtype, + _, + maxhand, + oldHolsters, + holsters, + oldInventory, + inventory, + drop, + delete + ) if filter.resolvedPlayerGuid == target => + sendResponse(ArmorChangedMessage(target, exosuit, subtype)) + sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) + //happening to this player + //cleanup + sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=false)) + (oldHolsters ++ oldInventory ++ delete).foreach { + case (_, dguid) => sendResponse(ObjectDeleteMessage(dguid, unk1=0)) + } + //functionally delete + delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) } + //redraw + if (maxhand) { + TaskWorkflow.execute(HoldNewEquipmentUp(player)( + Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), + 0 + )) + } + //draw free hand + player.FreeHand.Equipment.foreach { obj => + val definition = obj.Definition + sendResponse( + ObjectCreateDetailedMessage( + definition.ObjectId, + obj.GUID, + ObjectCreateMessageParent(target, Player.FreeHandSlot), + definition.Packet.DetailedConstructorData(obj).get + ) + ) + } + //draw holsters and inventory + (holsters ++ inventory).foreach { + case InventoryItem(obj, index) => val definition = obj.Definition sendResponse( ObjectCreateDetailedMessage( definition.ObjectId, obj.GUID, - ObjectCreateMessageParent(target, Player.FreeHandSlot), + ObjectCreateMessageParent(target, index), definition.Packet.DetailedConstructorData(obj).get ) ) - } - //draw holsters and inventory - (holsters ++ inventory).foreach { - case InventoryItem(obj, index) => - val definition = obj.Definition - sendResponse( - ObjectCreateDetailedMessage( - definition.ObjectId, - obj.GUID, - ObjectCreateMessageParent(target, index), - definition.Packet.DetailedConstructorData(obj).get - ) + } + DropLeftovers(player)(drop) + + case AvatarAction.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, drop, delete) => + sendResponse(ArmorChangedMessage(target, exosuit, subtype)) + sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) + //happening to some other player + sendResponse(ObjectHeldMessage(target, slot, unk1 = false)) + //cleanup + val dropPred = ContainableBehavior.DropPredicate(player) + val deleteFromDrop = drop.filterNot(dropPred) + (oldHolsters ++ delete ++ deleteFromDrop.map(f =>(f.obj, f.GUID))) + .distinctBy(_._2) + .foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } + //draw holsters + holsters.foreach { + case InventoryItem(obj, index) => + val definition = obj.Definition + sendResponse( + ObjectCreateMessage( + definition.ObjectId, + obj.GUID, + ObjectCreateMessageParent(target, index), + definition.Packet.ConstructorData(obj).get ) - } - DropLeftovers(player)(drop) - - case AvatarAction.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, drop, delete) => - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) - //happening to some other player - sendResponse(ObjectHeldMessage(target, slot, unk1 = false)) - //cleanup - val dropPred = ContainableBehavior.DropPredicate(player) - val deleteFromDrop = drop.filterNot(dropPred) - (oldHolsters ++ delete ++ deleteFromDrop.map(f =>(f.obj, f.GUID))) - .distinctBy(_._2) - .foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } - //draw holsters - holsters.foreach { - case InventoryItem(obj, index) => - val definition = obj.Definition - sendResponse( - ObjectCreateMessage( - definition.ObjectId, - obj.GUID, - ObjectCreateMessageParent(target, index), - definition.Packet.ConstructorData(obj).get - ) - ) - } - - case AvatarAction.ChangeLoadout( - target, - armor, - exosuit, - subtype, - _, - maxhand, - oldHolsters, - holsters, - oldInventory, - inventory, - drops - ) if resolvedPlayerGuid == target => - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) - //happening to this player - sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=true)) - //cleanup - (oldHolsters ++ oldInventory).foreach { - case (obj, objGuid) => - sendResponse(ObjectDeleteMessage(objGuid, unk1=0)) - TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) - } - drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0))) - //redraw - if (maxhand) { - sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong)) - TaskWorkflow.execute(HoldNewEquipmentUp(player)( - Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), - slot = 0 - )) - } - (holsters ++ inventory).foreach { case InventoryItem(item, slot) => - TaskWorkflow.execute(PutLoadoutEquipmentInInventory(player)(item, slot)) - } - DropLeftovers(player)(drops) - - case AvatarAction.ChangeLoadout(target, armor, exosuit, subtype, slot, _, oldHolsters, _, _, _, _) => - //redraw handled by callbacks - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) - //happening to some other player - sendResponse(ObjectHeldMessage(target, slot, unk1=false)) - //cleanup - oldHolsters.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } - - case AvatarAction.UseKit(kguid, kObjId) => - sendResponse( - UseItemMessage( - resolvedPlayerGuid, - kguid, - resolvedPlayerGuid, - unk2 = 4294967295L, - unk3 = false, - unk4 = Vector3.Zero, - unk5 = Vector3.Zero, - unk6 = 126, - unk7 = 0, //sequence time? - unk8 = 137, - kObjId ) - ) - sendResponse(ObjectDeleteMessage(kguid, unk1=0)) + } - case AvatarAction.KitNotUsed(_, "") => - sessionLogic.general.kitToBeUsed = None - - case AvatarAction.KitNotUsed(_, msg) => - sessionLogic.general.kitToBeUsed = None - sendResponse(ChatMsg(ChatMessageType.UNK_225, msg)) - - case SendResponse(msgs) => - msgs.foreach(sendResponse) - - /* common messages (maybe once every respawn) */ - case ReloadTool(itemGuid) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } => - sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0)) - - case AvatarAction.Killed(_, mount) => - //pure logic - sessionLogic.shooting.shotsWhileDead = 0 - sessionLogic.zoning.CancelZoningProcess() - sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive - sessionLogic.zoning.zoningStatus = Zoning.Status.None - continent.GUID(mount).collect { - case obj: Vehicle if obj.Destroyed => - ops.killedWhileMounted(obj, resolvedPlayerGuid) - sessionLogic.vehicles.ConditionalDriverVehicleControl(obj) - sessionLogic.general.unaccessContainer(obj) - - case obj: Vehicle => - ops.killedWhileMounted(obj, resolvedPlayerGuid) - sessionLogic.vehicles.ConditionalDriverVehicleControl(obj) - - case obj: PlanetSideGameObject with Mountable with Container if obj.Destroyed => - ops.killedWhileMounted(obj, resolvedPlayerGuid) - sessionLogic.general.unaccessContainer(obj) - - case obj: PlanetSideGameObject with Mountable with Container => - ops.killedWhileMounted(obj, resolvedPlayerGuid) - - case obj: PlanetSideGameObject with Mountable => - ops.killedWhileMounted(obj, resolvedPlayerGuid) - } - //player state changes - sessionLogic.general.dropSpecialSlotItem() - sessionLogic.general.toggleMaxSpecialState(enable = false) - player.FreeHand.Equipment.foreach(DropEquipmentFromInventory(player)(_)) - AvatarActor.updateToolDischargeFor(avatar) - AvatarActor.savePlayerLocation(player) - ops.revive() - avatarActor ! AvatarActor.InitializeImplants - //render - CustomerServiceRepresentativeMode.renderPlayer(sessionLogic, continent, player) - - case AvatarAction.ReleasePlayer(tplayer) if isNotSameTarget => - sessionLogic.zoning.spawn.DepictPlayerAsCorpse(tplayer) - - case AvatarAction.Revive(revivalTargetGuid) - if resolvedPlayerGuid == revivalTargetGuid => - ops.revive() - player.Actor ! Player.Revive - player.History - .findLast { _.isInstanceOf[RevivingActivity] } - .map { - case activity: RevivingActivity - if System.currentTimeMillis() - activity.time < 5000L => - val reviveMessage = s"@YouHaveBeenMessage^revived~^${activity.user.unique.name}~" - sendResponse(ChatMsg(ChatMessageType.UNK_227, reviveMessage)) - None - } - - /* uncommon messages (utility, or once in a while) */ - case ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } => - ops.changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data) - sendResponse(ChangeAmmoMessage(weapon_guid, 1)) - - case ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) - if isNotSameTarget => - ops.changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data) - - case AvatarAction.ChangeFireMode(itemGuid, mode) if isNotSameTarget => - sendResponse(ChangeFireModeMessage(itemGuid, mode)) - - case ConcealPlayer(_) => - sendResponse(GenericObjectActionMessage(guid, code=9)) - - case AvatarAction.EnvironmentalDamage(_, _, _) => - //TODO damage marker? - sessionLogic.zoning.CancelZoningProcess() - - case AvatarAction.DropCreatedItem(pkt) if isNotSameTarget => - sendResponse(pkt) - - case ObjectDelete(itemGuid, unk) if isNotSameTarget => - sendResponse(ObjectDeleteMessage(itemGuid, unk)) - - /* rare messages */ - case SetEmpire(objectGuid, faction) if isNotSameTarget => - sendResponse(SetEmpireMessage(objectGuid, faction)) - - case AvatarAction.DropSpecialItem() => - sessionLogic.general.dropSpecialSlotItem() - - case AvatarAction.OxygenState(player, vehicle) => - sendResponse(OxygenStateMessage( - DrowningTarget(player.guid, player.progress, player.state), - vehicle.flatMap { vinfo => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state)) } + case AvatarAction.ChangeLoadout( + target, + armor, + exosuit, + subtype, + _, + maxhand, + oldHolsters, + holsters, + oldInventory, + inventory, + drops + ) if filter.resolvedPlayerGuid == target => + sendResponse(ArmorChangedMessage(target, exosuit, subtype)) + sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) + //happening to this player + sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=true)) + //cleanup + (oldHolsters ++ oldInventory).foreach { + case (obj, objGuid) => + sendResponse(ObjectDeleteMessage(objGuid, unk1=0)) + TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) + } + drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0))) + //redraw + if (maxhand) { + sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong)) + TaskWorkflow.execute(HoldNewEquipmentUp(player)( + Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), + slot = 0 )) + } + (holsters ++ inventory).foreach { case InventoryItem(item, slot) => + TaskWorkflow.execute(PutLoadoutEquipmentInInventory(player)(item, slot)) + } + DropLeftovers(player)(drops) - case AvatarAction.LoadCreatedProjectile(pkt) if isNotSameTarget => - sendResponse(pkt) + case AvatarAction.ChangeLoadout(target, armor, exosuit, subtype, slot, _, oldHolsters, _, _, _, _) => + //redraw handled by callbacks + sendResponse(ArmorChangedMessage(target, exosuit, subtype)) + sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) + //happening to some other player + sendResponse(ObjectHeldMessage(target, slot, unk1=false)) + //cleanup + oldHolsters.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } - case AvatarAction.ProjectileState(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid) if isNotSameTarget => - sendResponse(ProjectileStateMessage(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid)) - - case AvatarAction.ProjectileExplodes(projectileGuid, projectile) => - sendResponse( - ProjectileStateMessage( - projectileGuid, - projectile.Position, - shot_vel = Vector3.Zero, - projectile.Orientation, - sequence_num=0, - end=true, - hit_target_guid=PlanetSideGUID(0) - ) + case AvatarAction.UseKit(kguid, kObjId) => + sendResponse( + UseItemMessage( + filter.resolvedPlayerGuid, + kguid, + filter.resolvedPlayerGuid, + unk2 = 4294967295L, + unk3 = false, + unk4 = Vector3.Zero, + unk5 = Vector3.Zero, + unk6 = 126, + unk7 = 0, //sequence time? + unk8 = 137, + kObjId ) - sendResponse(ObjectDeleteMessage(projectileGuid, unk1=2)) + ) + sendResponse(ObjectDeleteMessage(kguid, unk1=0)) - case AvatarAction.ProjectileAutoLockAwareness(mode) => - sendResponse(GenericActionMessage(mode)) + case AvatarAction.KitNotUsed(_, "") => + sessionLogic.general.kitToBeUsed = None - case AvatarAction.PutDownFDU(target) if isNotSameTarget => - sendResponse(GenericObjectActionMessage(target, code=53)) + case AvatarAction.KitNotUsed(_, msg) => + sessionLogic.general.kitToBeUsed = None + sendResponse(ChatMsg(ChatMessageType.UNK_225, msg)) - case AvatarAction.StowEquipment(target, slot, item) if isNotSameTarget => - val definition = item.Definition - sendResponse( - ObjectCreateDetailedMessage( - definition.ObjectId, - item.GUID, - ObjectCreateMessageParent(target, slot), - definition.Packet.DetailedConstructorData(item).get - ) - ) - case WeaponDryFire(weaponGuid) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } => - continent.GUID(weaponGuid).collect { - case tool: Tool if tool.Magazine == 0 => - // check that the magazine is still empty before sending WeaponDryFireMessage - // if it has been reloaded since then, other clients will not see it firing - sendResponse(WeaponDryFireMessage(weaponGuid)) + /* common messages (maybe once every respawn) */ + case ReloadTool(itemGuid) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { _.visible } => + sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0)) + + case AvatarAction.Killed(_, mount) => + //pure logic + sessionLogic.shooting.shotsWhileDead = 0 + sessionLogic.zoning.CancelZoningProcess() + sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive + sessionLogic.zoning.zoningStatus = Zoning.Status.None + continent.GUID(mount).collect { + case obj: Vehicle if obj.Destroyed => + ops.killedWhileMounted(obj, filter.resolvedPlayerGuid) + sessionLogic.vehicles.ConditionalDriverVehicleControl(obj) + sessionLogic.general.unaccessContainer(obj) + + case obj: Vehicle => + ops.killedWhileMounted(obj, filter.resolvedPlayerGuid) + sessionLogic.vehicles.ConditionalDriverVehicleControl(obj) + + case obj: PlanetSideGameObject with Mountable with Container if obj.Destroyed => + ops.killedWhileMounted(obj, filter.resolvedPlayerGuid) + sessionLogic.general.unaccessContainer(obj) + + case obj: PlanetSideGameObject with Mountable with Container => + ops.killedWhileMounted(obj, filter.resolvedPlayerGuid) + + case obj: PlanetSideGameObject with Mountable => + ops.killedWhileMounted(obj, filter.resolvedPlayerGuid) + } + //player state changes + sessionLogic.general.dropSpecialSlotItem() + sessionLogic.general.toggleMaxSpecialState(enable = false) + player.FreeHand.Equipment.foreach(DropEquipmentFromInventory(player)(_)) + AvatarActor.updateToolDischargeFor(avatar) + AvatarActor.savePlayerLocation(player) + ops.revive() + avatarActor ! AvatarActor.InitializeImplants + //render + CustomerServiceRepresentativeMode.renderPlayer(sessionLogic, continent, player) + + case AvatarAction.ReleasePlayer(tplayer) if filter.isNotSameTarget => + sessionLogic.zoning.spawn.DepictPlayerAsCorpse(tplayer) + + case AvatarAction.Revive(revivalTargetGuid) + if filter.resolvedPlayerGuid == revivalTargetGuid => + ops.revive() + player.Actor ! Player.Revive + player.History + .findLast { _.isInstanceOf[RevivingActivity] } + .map { + case activity: RevivingActivity + if System.currentTimeMillis() - activity.time < 5000L => + val reviveMessage = s"@YouHaveBeenMessage^revived~^${activity.user.unique.name}~" + sendResponse(ChatMsg(ChatMessageType.UNK_227, reviveMessage)) + None } - case _ => () - } + /* uncommon messages (utility, or once in a while) */ + case ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { _.visible } => + ops.changeAmmoProcedure(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data) + sendResponse(ChangeAmmoMessage(weapon_guid, 1)) + + case AvatarAction.ChangeFireMode(itemGuid, mode) if filter.isNotSameTarget => + sendResponse(ChangeFireModeMessage(itemGuid, mode)) + + case AvatarAction.EnvironmentalDamage(_, _, _) => + //TODO damage marker? + sessionLogic.zoning.CancelZoningProcess() + + case AvatarAction.DropCreatedItem(pkt) if filter.isNotSameTarget => + sendResponse(pkt) + + /* rare messages */ + case AvatarAction.DropSpecialItem() => + sessionLogic.general.dropSpecialSlotItem() + + case AvatarAction.OxygenState(player, vehicle) => + sendResponse(OxygenStateMessage( + DrowningTarget(player.guid, player.progress, player.state), + vehicle.flatMap { vinfo => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state)) } + )) + + case AvatarAction.LoadCreatedProjectile(pkt) if filter.isNotSameTarget => + sendResponse(pkt) + + case AvatarAction.ProjectileState(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid) if filter.isNotSameTarget => + sendResponse(ProjectileStateMessage(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid)) + + case AvatarAction.ProjectileExplodes(projectileGuid, projectile) => + sendResponse( + ProjectileStateMessage( + projectileGuid, + projectile.Position, + shot_vel = Vector3.Zero, + projectile.Orientation, + sequence_num=0, + end=true, + hit_target_guid=PlanetSideGUID(0) + ) + ) + sendResponse(ObjectDeleteMessage(projectileGuid, unk1=2)) + + case AvatarAction.ProjectileAutoLockAwareness(mode) => + sendResponse(GenericActionMessage(mode)) + + case AvatarAction.PutDownFDU(target) if filter.isNotSameTarget => + sendResponse(GenericObjectActionMessage(target, code=53)) + + case AvatarAction.StowEquipment(target, slot, item) if filter.isNotSameTarget => + val definition = item.Definition + sendResponse( + ObjectCreateDetailedMessage( + definition.ObjectId, + item.GUID, + ObjectCreateMessageParent(target, slot), + definition.Packet.DetailedConstructorData(item).get + ) + ) + + case WeaponDryFire(weaponGuid) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { _.visible } => + continent.GUID(weaponGuid).collect { + case tool: Tool if tool.Magazine == 0 => + // check that the magazine is still empty before sending WeaponDryFireMessage + // if it has been reloaded since then, other clients will not see it firing + sendResponse(WeaponDryFireMessage(weaponGuid)) + } } } diff --git a/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala index b5f188066..25c318c3d 100644 --- a/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala @@ -1,6 +1,7 @@ // Copyright (c) 2024 PSForever package net.psforever.actors.session.normal +import akka.actor.Actor.Receive import akka.actor.{ActorContext, typed} import net.psforever.actors.session.support.AvatarHandlerFunctions import net.psforever.actors.zone.ZoneActor @@ -14,7 +15,7 @@ import net.psforever.objects.vital.interaction.Adversarial import net.psforever.packet.game.{AvatarImplantMessage, CreateShortcutMessage, ImplantAction, PlanetsideStringAttributeMessage} import net.psforever.services.avatar.AvatarAction import net.psforever.services.base.envelope.MessageEnvelope -import net.psforever.services.base.message.{ChangeAmmo, ChangeFireState_Start, ChangeFireState_Stop, ConcealPlayer, EventResponse, GenericObjectAction, HintsAtAttacker, ObjectDelete, PlanetsideAttribute, ReloadTool, SendResponse, SetEmpire, WeaponDryFire} +import net.psforever.services.base.message.{ChangeAmmo, ChangeFireState_Start, ChangeFireState_Stop, ObjectDelete, PlanetsideAttribute, ReloadTool, WeaponDryFire} import net.psforever.types.ImplantType import scala.concurrent.duration._ @@ -30,8 +31,7 @@ import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal} import net.psforever.objects.vital.etc.ExplodingEntityReason import net.psforever.objects.zones.Zoning import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent -import net.psforever.packet.game.{ArmorChangedMessage, AvatarDeadStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, DeadState, DestroyMessage, DrowningTarget, GenericActionMessage, GenericObjectActionMessage, HitHint, ItemTransactionResultMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectHeldMessage, OxygenStateMessage, PlanetsideAttributeMessage, PlayerStateMessage, ProjectileStateMessage, ReloadMessage, SetEmpireMessage, UseItemMessage, WeaponDryFireMessage} -import net.psforever.services.Service +import net.psforever.packet.game.{ArmorChangedMessage, AvatarDeadStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, DeadState, DestroyMessage, DrowningTarget, GenericActionMessage, GenericObjectActionMessage, ItemTransactionResultMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectHeldMessage, OxygenStateMessage, PlanetsideAttributeMessage, PlayerStateMessage, ProjectileStateMessage, ReloadMessage, UseItemMessage, WeaponDryFireMessage} import net.psforever.types.{ChatMessageType, PlanetSideGUID, TransactionType, Vector3} import net.psforever.util.Config @@ -46,644 +46,593 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor - /** - * na - * @param toChannel na - * @param guid na - * @param reply na - */ - def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit = { - val resolvedPlayerGuid = if (player != null && player.HasGUID) { - player.GUID - } else { - Service.defaultPlayerGUID - } - val isNotSameTarget = resolvedPlayerGuid != guid - val isSameTarget = !isNotSameTarget - reply match { - /* special messages */ - case AvatarAction.TeardownConnection() => - log.trace(s"ending ${player.Name}'s old session by event system request (relog)") - context.stop(context.self) + def receive: Receive = { + /* special messages */ + case AvatarAction.TeardownConnection => + log.trace(s"ending ${player.Name}'s old session by event system request (relog)") + context.stop(context.self) - /* really common messages (very frequently, every life) */ - case pstate @ AvatarAction.PlayerState( - pos, - vel, - yaw, - pitch, - yawUpper, - _, - isCrouching, - isJumping, - jumpThrust, - isCloaking, - isNotRendered, - canSeeReallyFar - ) if isNotSameTarget => - val pstateToSave = pstate.copy(timestamp = 0) - val (lastMsg, lastTime, lastPosition, wasVisible, wasShooting) = ops.lastSeenStreamMessage.get(guid.guid) match { - case Some(SessionAvatarHandlers.LastUpstream(Some(msg), visible, shooting, time)) => (Some(msg), time, msg.pos, visible, shooting) - case _ => (None, 0L, Vector3.Zero, false, None) - } - val drawConfig = Config.app.game.playerDraw //m - val maxRange = drawConfig.rangeMax * drawConfig.rangeMax //sq.m - val ourPosition = player.Position //xyz - val currentDistance = Vector3.DistanceSquared(ourPosition, pos) //sq.m - val inDrawableRange = currentDistance <= maxRange - val now = System.currentTimeMillis() //ms - if ( - sessionLogic.zoning.zoningStatus != Zoning.Status.Deconstructing && - !isNotRendered && inDrawableRange + /* really common messages (very frequently, every life) */ + case pstate @ AvatarAction.PlayerState( + pos, + vel, + yaw, + pitch, + yawUpper, + _, + isCrouching, + isJumping, + jumpThrust, + isCloaking, + isNotRendered, + canSeeReallyFar + ) if filter.isNotSameTarget => + val pstateToSave = pstate.copy(timestamp = 0) + val (lastMsg, lastTime, lastPosition, wasVisible, wasShooting) = ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid) match { + case Some(SessionAvatarHandlers.LastUpstream(Some(msg), visible, shooting, time)) => (Some(msg), time, msg.pos, visible, shooting) + case _ => (None, 0L, Vector3.Zero, false, None) + } + val drawConfig = Config.app.game.playerDraw //m + val maxRange = drawConfig.rangeMax * drawConfig.rangeMax //sq.m + val ourPosition = player.Position //xyz + val currentDistance = Vector3.DistanceSquared(ourPosition, pos) //sq.m + val inDrawableRange = currentDistance <= maxRange + val now = System.currentTimeMillis() //ms + if ( + sessionLogic.zoning.zoningStatus != Zoning.Status.Deconstructing && + !isNotRendered && inDrawableRange + ) { + //conditions where visibility is assured + val durationSince = now - lastTime //ms + lazy val previouslyInDrawableRange = Vector3.DistanceSquared(ourPosition, lastPosition) <= maxRange + lazy val targetDelay = { + val populationOver = math.max( + 0, + sessionLogic.localSector.livePlayerList.size - drawConfig.populationThreshold + ) + val distanceAdjustment = math.pow(populationOver / drawConfig.populationStep * drawConfig.rangeStep, 2) //sq.m + val adjustedDistance = currentDistance + distanceAdjustment //sq.m + drawConfig.ranges.lastIndexWhere { dist => adjustedDistance > dist * dist } match { + case -1 => 1 + case index => drawConfig.delays(index) + } + } //ms + if (!wasVisible || + !previouslyInDrawableRange || + durationSince > drawConfig.delayMax || + (!lastMsg.contains(pstateToSave) && + (canSeeReallyFar || + currentDistance < drawConfig.rangeMin * drawConfig.rangeMin || + sessionLogic.general.canSeeReallyFar || + durationSince > targetDelay + ) + ) ) { - //conditions where visibility is assured - val durationSince = now - lastTime //ms - lazy val previouslyInDrawableRange = Vector3.DistanceSquared(ourPosition, lastPosition) <= maxRange - lazy val targetDelay = { - val populationOver = math.max( - 0, - sessionLogic.localSector.livePlayerList.size - drawConfig.populationThreshold + //must draw + sendResponse( + PlayerStateMessage( + filter.resolvedPlayerGuid, + pos, + vel, + yaw, + pitch, + yawUpper, + timestamp = 0, //is this okay? + isCrouching, + isJumping, + jumpThrust, + isCloaking ) - val distanceAdjustment = math.pow(populationOver / drawConfig.populationStep * drawConfig.rangeStep, 2) //sq.m - val adjustedDistance = currentDistance + distanceAdjustment //sq.m - drawConfig.ranges.lastIndexWhere { dist => adjustedDistance > dist * dist } match { - case -1 => 1 - case index => drawConfig.delays(index) - } - } //ms - if (!wasVisible || - !previouslyInDrawableRange || - durationSince > drawConfig.delayMax || - (!lastMsg.contains(pstateToSave) && - (canSeeReallyFar || - currentDistance < drawConfig.rangeMin * drawConfig.rangeMin || - sessionLogic.general.canSeeReallyFar || - durationSince > targetDelay - ) - ) - ) { - //must draw - sendResponse( - PlayerStateMessage( - guid, - pos, - vel, - yaw, - pitch, - yawUpper, - timestamp = 0, //is this okay? - isCrouching, - isJumping, - jumpThrust, - isCloaking - ) - ) - ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, now)) - } else { - //is visible, but skip reinforcement - ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, lastTime)) - } + ) + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, now)) } else { - //conditions where the target is not currently visible - if (wasVisible) { - //the target was JUST PREVIOUSLY visible; one last draw to move target beyond a renderable distance - val lat = (1 + ops.hidingPlayerRandomizer.nextInt(continent.map.scale.height.toInt)).toFloat - sendResponse( - PlayerStateMessage( - guid, - Vector3(1f, lat, 1f), - vel=None, - facingYaw=0f, - facingPitch=0f, - facingYawUpper=0f, - timestamp=0, //is this okay? - is_cloaked = isCloaking - ) + //is visible, but skip reinforcement + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, lastTime)) + } + } else { + //conditions where the target is not currently visible + if (wasVisible) { + //the target was JUST PREVIOUSLY visible; one last draw to move target beyond a renderable distance + val lat = (1 + ops.hidingPlayerRandomizer.nextInt(continent.map.scale.height.toInt)).toFloat + sendResponse( + PlayerStateMessage( + filter.resolvedPlayerGuid, + Vector3(1f, lat, 1f), + vel=None, + facingYaw=0f, + facingPitch=0f, + facingYawUpper=0f, + timestamp=0, //is this okay? + is_cloaked = isCloaking ) - ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, now)) - } else { - //skip drawing altogether - ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, lastTime)) + ) + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, now)) + } else { + //skip drawing altogether + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, lastTime)) + } + } + + case AvatarAction.AvatarImplant(ImplantAction.Add, implant_slot, value) + if value == ImplantType.SecondWind.value => + sendResponse(AvatarImplantMessage(filter.resolvedPlayerGuid, ImplantAction.Add, implant_slot, 7)) + //second wind does not normally load its icon into the shortcut hotbar + avatar + .shortcuts + .zipWithIndex + .find { case (s, _) => s.isEmpty} + .foreach { case (_, index) => + sendResponse(CreateShortcutMessage(filter.resolvedPlayerGuid, index + 1, Some(ImplantType.SecondWind.shortcut))) + } + + case AvatarAction.AvatarImplant(ImplantAction.Remove, implant_slot, value) + if value == ImplantType.SecondWind.value => + sendResponse(AvatarImplantMessage(filter.resolvedPlayerGuid, ImplantAction.Remove, implant_slot, value)) + //second wind does not normally unload its icon from the shortcut hotbar + val shortcut = { + val imp = ImplantType.SecondWind.shortcut + net.psforever.objects.avatar.Shortcut(imp.code, imp.tile) //case class + } + avatar + .shortcuts + .zipWithIndex + .find { case (s, _) => s.contains(shortcut) } + .foreach { case (_, index) => + sendResponse(CreateShortcutMessage(filter.resolvedPlayerGuid, index + 1, None)) + } + + case AvatarAction.AvatarImplant(action, implant_slot, value) => + sendResponse(AvatarImplantMessage(filter.resolvedPlayerGuid, action, implant_slot, value)) + + case AvatarAction.ObjectHeld(slot, _) + if filter.isSameTarget && player.VisibleSlots.contains(slot) => + sendResponse(ObjectHeldMessage(filter.resolvedPlayerGuid, slot, unk1=true)) + //Stop using proximity terminals if player unholsters a weapon + continent.GUID(sessionLogic.terminals.usingMedicalTerminal).collect { + case term: Terminal with ProximityUnit => sessionLogic.terminals.StopUsingProximityUnit(term) + } + if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) { + sessionLogic.zoning.spawn.stopDeconstructing() + } + + case AvatarAction.ObjectHeld(slot, _) + if filter.isSameTarget && slot > -1 => + sendResponse(ObjectHeldMessage(filter.resolvedPlayerGuid, slot, unk1=true)) + + case AvatarAction.ObjectHeld(_, _) + if filter.isSameTarget => () + + case AvatarAction.ObjectHeld(_, previousSlot) => + sendResponse(ObjectHeldMessage(filter.resolvedPlayerGuid, previousSlot, unk1=false)) + + case ChangeFireState_Start(weaponGuid) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { _.visible } => + sendResponse(ChangeFireStateMessage_Start(weaponGuid)) + val entry = ops.lastSeenStreamMessage(filter.resolvedPlayerGuid.guid) + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, entry.copy(shooting = Some(weaponGuid))) + + case ChangeFireState_Stop(weaponGuid) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { msg => msg.visible || msg.shooting.nonEmpty } => + sendResponse(ChangeFireStateMessage_Stop(weaponGuid)) + val entry = ops.lastSeenStreamMessage(filter.resolvedPlayerGuid.guid) + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, entry.copy(shooting = None)) + + case AvatarAction.LoadCreatedPlayer(pkt) if filter.isNotSameTarget => + sendResponse(pkt) + + case AvatarAction.EquipmentCreatedInHand(pkt) if filter.isNotSameTarget => + sendResponse(pkt) + + case AvatarAction.PlanetsideStringAttribute(attributeType, attributeValue) => + sendResponse(PlanetsideStringAttributeMessage(filter.resolvedPlayerGuid, attributeType, attributeValue)) + + case AvatarAction.Destroy(victim, killer, weapon, pos) => + // guid = victim // killer = killer + sendResponse(DestroyMessage(victim, killer, weapon, pos)) + + case AvatarAction.DestroyDisplay(killer, victim, method, unk) => + sendResponse(ops.destroyDisplayMessage(killer, victim, method, unk)) + + case AvatarAction.TerminalOrderResult(terminalGuid, action, result) + if result && (action == TransactionType.Buy || action == TransactionType.Loadout) => + sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) + sessionLogic.terminals.lastTerminalOrderFulfillment = true + AvatarActor.savePlayerData(player) + sessionLogic.general.renewCharSavedTimer( + Config.app.game.savedMsg.interruptedByAction.fixed, + Config.app.game.savedMsg.interruptedByAction.variable + ) + + case AvatarAction.TerminalOrderResult(terminalGuid, action, result) => + sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) + sessionLogic.terminals.lastTerminalOrderFulfillment = true + + case AvatarAction.ChangeExosuit( + target, + armor, + exosuit, + subtype, + _, + maxhand, + oldHolsters, + holsters, + oldInventory, + inventory, + drop, + delete + ) if filter.resolvedPlayerGuid == target => + sendResponse(ArmorChangedMessage(target, exosuit, subtype)) + sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) + //happening to this player + //cleanup + sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=false)) + (oldHolsters ++ oldInventory ++ delete).foreach { + case (_, dguid) => sendResponse(ObjectDeleteMessage(dguid, unk1=0)) + } + //functionally delete + if (delete.size > 1 || delete.nonEmpty && !delete.exists { + case (e: Tool, _) => GlobalDefinitions.isMaxArms(e.Definition) + case _ => false + }) { + /* + if going x -> max, you will have enough space in max inventory for any displaced holster equipment + for max -> max, don't care about the max weapon arm being deleted (allow for 1) + for any other x -> x, any deleted equipment will raise this comment + */ + sendResponse(ChatMsg(ChatMessageType.UNK_227, "@ItemsDeconstructed")) + } + delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) } + //redraw + if (maxhand) { + sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong)) + val maxArmDefinition = GlobalDefinitions.MAXArms(subtype, player.Faction) + TaskWorkflow.execute(HoldNewEquipmentUp(player)(Tool(maxArmDefinition), slot = 0)) + player.avatar.purchaseCooldown(maxArmDefinition) + .collect(a => a) + .getOrElse { + avatarActor ! AvatarActor.UpdatePurchaseTime(maxArmDefinition) + None } - } - - case AvatarAction.AvatarImplant(ImplantAction.Add, implant_slot, value) - if value == ImplantType.SecondWind.value => - sendResponse(AvatarImplantMessage(resolvedPlayerGuid, ImplantAction.Add, implant_slot, 7)) - //second wind does not normally load its icon into the shortcut hotbar - avatar - .shortcuts - .zipWithIndex - .find { case (s, _) => s.isEmpty} - .foreach { case (_, index) => - sendResponse(CreateShortcutMessage(resolvedPlayerGuid, index + 1, Some(ImplantType.SecondWind.shortcut))) - } - - case AvatarAction.AvatarImplant(ImplantAction.Remove, implant_slot, value) - if value == ImplantType.SecondWind.value => - sendResponse(AvatarImplantMessage(resolvedPlayerGuid, ImplantAction.Remove, implant_slot, value)) - //second wind does not normally unload its icon from the shortcut hotbar - val shortcut = { - val imp = ImplantType.SecondWind.shortcut - net.psforever.objects.avatar.Shortcut(imp.code, imp.tile) //case class - } - avatar - .shortcuts - .zipWithIndex - .find { case (s, _) => s.contains(shortcut) } - .foreach { case (_, index) => - sendResponse(CreateShortcutMessage(resolvedPlayerGuid, index + 1, None)) - } - - case AvatarAction.AvatarImplant(action, implant_slot, value) => - sendResponse(AvatarImplantMessage(resolvedPlayerGuid, action, implant_slot, value)) - - case AvatarAction.ObjectHeld(slot, _) - if isSameTarget && player.VisibleSlots.contains(slot) => - sendResponse(ObjectHeldMessage(guid, slot, unk1=true)) - //Stop using proximity terminals if player unholsters a weapon - continent.GUID(sessionLogic.terminals.usingMedicalTerminal).collect { - case term: Terminal with ProximityUnit => sessionLogic.terminals.StopUsingProximityUnit(term) - } - if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) { - sessionLogic.zoning.spawn.stopDeconstructing() - } - - case AvatarAction.ObjectHeld(slot, _) - if isSameTarget && slot > -1 => - sendResponse(ObjectHeldMessage(guid, slot, unk1=true)) - - case AvatarAction.ObjectHeld(_, _) - if isSameTarget => () - - case AvatarAction.ObjectHeld(_, previousSlot) => - sendResponse(ObjectHeldMessage(guid, previousSlot, unk1=false)) - - case ChangeFireState_Start(weaponGuid) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } => - sendResponse(ChangeFireStateMessage_Start(weaponGuid)) - val entry = ops.lastSeenStreamMessage(guid.guid) - ops.lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = Some(weaponGuid))) - - case ChangeFireState_Start(weaponGuid) - if isNotSameTarget => - sendResponse(ChangeFireStateMessage_Start(weaponGuid)) - - case ChangeFireState_Stop(weaponGuid) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { msg => msg.visible || msg.shooting.nonEmpty } => - sendResponse(ChangeFireStateMessage_Stop(weaponGuid)) - val entry = ops.lastSeenStreamMessage(guid.guid) - ops.lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = None)) - - case ChangeFireState_Stop(weaponGuid) - if isNotSameTarget => - sendResponse(ChangeFireStateMessage_Stop(weaponGuid)) - - case AvatarAction.LoadCreatedPlayer(pkt) if isNotSameTarget => - sendResponse(pkt) - - case AvatarAction.EquipmentCreatedInHand(pkt) if isNotSameTarget => - sendResponse(pkt) - - case PlanetsideAttribute(target_guid, attributeType, attributeValue) if isNotSameTarget => - sendResponse(PlanetsideAttributeMessage(target_guid, attributeType, attributeValue)) - - case AvatarAction.PlanetsideStringAttribute(attributeType, attributeValue) => - sendResponse(PlanetsideStringAttributeMessage(guid, attributeType, attributeValue)) - - case GenericObjectAction(objectGuid, actionCode) if isNotSameTarget => - sendResponse(GenericObjectActionMessage(objectGuid, actionCode)) - - case HintsAtAttacker(sourceGuid) if player.isAlive => - sendResponse(HitHint(sourceGuid, guid)) - sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg") - - case AvatarAction.Destroy(victim, killer, weapon, pos) => - // guid = victim // killer = killer - sendResponse(DestroyMessage(victim, killer, weapon, pos)) - - case AvatarAction.DestroyDisplay(killer, victim, method, unk) => - sendResponse(ops.destroyDisplayMessage(killer, victim, method, unk)) - - case AvatarAction.TerminalOrderResult(terminalGuid, action, result) - if result && (action == TransactionType.Buy || action == TransactionType.Loadout) => - sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) - sessionLogic.terminals.lastTerminalOrderFulfillment = true - AvatarActor.savePlayerData(player) - sessionLogic.general.renewCharSavedTimer( - Config.app.game.savedMsg.interruptedByAction.fixed, - Config.app.game.savedMsg.interruptedByAction.variable + } + //draw free hand + player.FreeHand.Equipment.foreach { obj => + val definition = obj.Definition + sendResponse( + ObjectCreateDetailedMessage( + definition.ObjectId, + obj.GUID, + ObjectCreateMessageParent(target, Player.FreeHandSlot), + definition.Packet.DetailedConstructorData(obj).get + ) ) - - case AvatarAction.TerminalOrderResult(terminalGuid, action, result) => - sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) - sessionLogic.terminals.lastTerminalOrderFulfillment = true - - case AvatarAction.ChangeExosuit( - target, - armor, - exosuit, - subtype, - _, - maxhand, - oldHolsters, - holsters, - oldInventory, - inventory, - drop, - delete - ) if resolvedPlayerGuid == target => - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) - //happening to this player - //cleanup - sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=false)) - (oldHolsters ++ oldInventory ++ delete).foreach { - case (_, dguid) => sendResponse(ObjectDeleteMessage(dguid, unk1=0)) - } - //functionally delete - if (delete.size > 1 || delete.nonEmpty && !delete.exists { - case (e: Tool, _) => GlobalDefinitions.isMaxArms(e.Definition) - case _ => false - }) { - /* - if going x -> max, you will have enough space in max inventory for any displaced holster equipment - for max -> max, don't care about the max weapon arm being deleted (allow for 1) - for any other x -> x, any deleted equipment will raise this comment - */ - sendResponse(ChatMsg(ChatMessageType.UNK_227, "@ItemsDeconstructed")) - } - delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) } - //redraw - if (maxhand) { - sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong)) - val maxArmDefinition = GlobalDefinitions.MAXArms(subtype, player.Faction) - TaskWorkflow.execute(HoldNewEquipmentUp(player)(Tool(maxArmDefinition), slot = 0)) - player.avatar.purchaseCooldown(maxArmDefinition) - .collect(a => a) - .getOrElse { - avatarActor ! AvatarActor.UpdatePurchaseTime(maxArmDefinition) - None - } - } - //draw free hand - player.FreeHand.Equipment.foreach { obj => + } + //draw holsters and inventory + (holsters ++ inventory).foreach { + case InventoryItem(obj, index) => val definition = obj.Definition sendResponse( ObjectCreateDetailedMessage( definition.ObjectId, obj.GUID, - ObjectCreateMessageParent(target, Player.FreeHandSlot), + ObjectCreateMessageParent(target, index), definition.Packet.DetailedConstructorData(obj).get ) ) - } - //draw holsters and inventory - (holsters ++ inventory).foreach { - case InventoryItem(obj, index) => - val definition = obj.Definition - sendResponse( - ObjectCreateDetailedMessage( - definition.ObjectId, - obj.GUID, - ObjectCreateMessageParent(target, index), - definition.Packet.DetailedConstructorData(obj).get - ) + } + DropLeftovers(player)(drop) + //deactivate non-passive implants + avatarActor ! AvatarActor.DeactivateActiveImplants + + case AvatarAction.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, drop, delete) => + sendResponse(ArmorChangedMessage(target, exosuit, subtype)) + sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) + //happening to some other player + sendResponse(ObjectHeldMessage(target, slot, unk1 = false)) + //cleanup + val dropPred = ContainableBehavior.DropPredicate(player) + val deleteFromDrop = drop.filterNot(dropPred) + (oldHolsters ++ delete ++ deleteFromDrop.map(f =>(f.obj, f.GUID))) + .distinctBy(_._2) + .foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } + //draw holsters + holsters.foreach { + case InventoryItem(obj, index) => + val definition = obj.Definition + sendResponse( + ObjectCreateMessage( + definition.ObjectId, + obj.GUID, + ObjectCreateMessageParent(target, index), + definition.Packet.ConstructorData(obj).get ) - } - DropLeftovers(player)(drop) - //deactivate non-passive implants - avatarActor ! AvatarActor.DeactivateActiveImplants - - case AvatarAction.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, drop, delete) => - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) - //happening to some other player - sendResponse(ObjectHeldMessage(target, slot, unk1 = false)) - //cleanup - val dropPred = ContainableBehavior.DropPredicate(player) - val deleteFromDrop = drop.filterNot(dropPred) - (oldHolsters ++ delete ++ deleteFromDrop.map(f =>(f.obj, f.GUID))) - .distinctBy(_._2) - .foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } - //draw holsters - holsters.foreach { - case InventoryItem(obj, index) => - val definition = obj.Definition - sendResponse( - ObjectCreateMessage( - definition.ObjectId, - obj.GUID, - ObjectCreateMessageParent(target, index), - definition.Packet.ConstructorData(obj).get - ) - ) - } - - case AvatarAction.ChangeLoadout( - target, - armor, - exosuit, - subtype, - _, - maxhand, - oldHolsters, - holsters, - oldInventory, - inventory, - drops - ) if resolvedPlayerGuid == target => - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) - //happening to this player - sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=true)) - //cleanup - (oldHolsters ++ oldInventory).foreach { - case (obj, objGuid) => - sendResponse(ObjectDeleteMessage(objGuid, unk1=0)) - TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) - } - drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0))) - //redraw - if (maxhand) { - val maxArmWeapon = GlobalDefinitions.MAXArms(subtype, player.Faction) - sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong)) - TaskWorkflow.execute(HoldNewEquipmentUp(player)(Tool(maxArmWeapon), slot = 0)) - player.avatar.purchaseCooldown(maxArmWeapon) - if (!oldHolsters.exists { case (e, _) => e.Definition == maxArmWeapon } && - player.avatar.purchaseCooldown(maxArmWeapon).isEmpty) { - avatarActor ! AvatarActor.UpdatePurchaseTime(maxArmWeapon) //switching for first time causes cooldown - } - } - sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory) - DropLeftovers(player)(drops) - //deactivate non-passive implants - avatarActor ! AvatarActor.DeactivateActiveImplants - - case AvatarAction.ChangeLoadout(target, armor, exosuit, subtype, slot, _, oldHolsters, _, _, _, _) => - //redraw handled by callbacks - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) - //happening to some other player - sendResponse(ObjectHeldMessage(target, slot, unk1=false)) - //cleanup - oldHolsters.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } - - case AvatarAction.UseKit(kguid, kObjId) => - sendResponse( - UseItemMessage( - resolvedPlayerGuid, - kguid, - resolvedPlayerGuid, - unk2 = 4294967295L, - unk3 = false, - unk4 = Vector3.Zero, - unk5 = Vector3.Zero, - unk6 = 126, - unk7 = 0, //sequence time? - unk8 = 137, - kObjId ) + } + + case AvatarAction.ChangeLoadout( + target, + armor, + exosuit, + subtype, + _, + maxhand, + oldHolsters, + holsters, + oldInventory, + inventory, + drops + ) if filter.resolvedPlayerGuid == target => + sendResponse(ArmorChangedMessage(target, exosuit, subtype)) + sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) + //happening to this player + sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=true)) + //cleanup + (oldHolsters ++ oldInventory).foreach { + case (obj, objGuid) => + sendResponse(ObjectDeleteMessage(objGuid, unk1=0)) + TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) + } + drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0))) + //redraw + if (maxhand) { + val maxArmWeapon = GlobalDefinitions.MAXArms(subtype, player.Faction) + sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong)) + TaskWorkflow.execute(HoldNewEquipmentUp(player)(Tool(maxArmWeapon), slot = 0)) + player.avatar.purchaseCooldown(maxArmWeapon) + if (!oldHolsters.exists { case (e, _) => e.Definition == maxArmWeapon } && + player.avatar.purchaseCooldown(maxArmWeapon).isEmpty) { + avatarActor ! AvatarActor.UpdatePurchaseTime(maxArmWeapon) //switching for first time causes cooldown + } + } + sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory) + DropLeftovers(player)(drops) + //deactivate non-passive implants + avatarActor ! AvatarActor.DeactivateActiveImplants + + case AvatarAction.ChangeLoadout(target, armor, exosuit, subtype, slot, _, oldHolsters, _, _, _, _) => + //redraw handled by callbacks + sendResponse(ArmorChangedMessage(target, exosuit, subtype)) + sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) + //happening to some other player + sendResponse(ObjectHeldMessage(target, slot, unk1=false)) + //cleanup + oldHolsters.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } + + case AvatarAction.UseKit(kguid, kObjId) => + sendResponse( + UseItemMessage( + filter.resolvedPlayerGuid, + kguid, + filter.resolvedPlayerGuid, + unk2 = 4294967295L, + unk3 = false, + unk4 = Vector3.Zero, + unk5 = Vector3.Zero, + unk6 = 126, + unk7 = 0, //sequence time? + unk8 = 137, + kObjId ) - sendResponse(ObjectDeleteMessage(kguid, unk1=0)) + ) + sendResponse(ObjectDeleteMessage(kguid, unk1=0)) - case AvatarAction.KitNotUsed(_, "") => - sessionLogic.general.kitToBeUsed = None + case AvatarAction.KitNotUsed(_, "") => + sessionLogic.general.kitToBeUsed = None - case AvatarAction.KitNotUsed(_, msg) => - sessionLogic.general.kitToBeUsed = None - sendResponse(ChatMsg(ChatMessageType.UNK_225, msg)) + case AvatarAction.KitNotUsed(_, msg) => + sessionLogic.general.kitToBeUsed = None + sendResponse(ChatMsg(ChatMessageType.UNK_225, msg)) - case AvatarAction.UpdateKillsDeathsAssists(_, kda) => - avatarActor ! AvatarActor.UpdateKillsDeathsAssists(kda) + case AvatarAction.UpdateKillsDeathsAssists(_, kda) => + avatarActor ! AvatarActor.UpdateKillsDeathsAssists(kda) - case AvatarAction.AwardBep(charId, bep, expType) => - //if the target player, always award (some) BEP - if (charId == player.CharId) { - avatarActor ! AvatarActor.AwardBep(bep, expType) - } + case AvatarAction.AwardBep(charId, bep, expType) => + //if the target player, always award (some) BEP + if (charId == player.CharId) { + avatarActor ! AvatarActor.AwardBep(bep, expType) + } - case AvatarAction.AwardCep(charId, cep) => - //if the target player, always award (some) CEP - if (charId == player.CharId) { - avatarActor ! AvatarActor.AwardCep(cep) - } + case AvatarAction.AwardCep(charId, cep) => + //if the target player, always award (some) CEP + if (charId == player.CharId) { + avatarActor ! AvatarActor.AwardCep(cep) + } - case AvatarAction.FacilityCaptureRewards(buildingId, zoneNumber, cep) => - ops.facilityCaptureRewards(buildingId, zoneNumber, cep) + case AvatarAction.FacilityCaptureRewards(buildingId, zoneNumber, cep) => + ops.facilityCaptureRewards(buildingId, zoneNumber, cep) - case AvatarAction.ShareKillExperienceWithSquad(killer, exp) => - ops.shareKillExperienceWithSquad(killer, exp) + case AvatarAction.ShareKillExperienceWithSquad(killer, exp) => + ops.shareKillExperienceWithSquad(killer, exp) - case AvatarAction.ShareAntExperienceWithSquad(owner, exp, vehicle) => - ops.shareAntExperienceWithSquad(owner, exp, vehicle) + case AvatarAction.ShareAntExperienceWithSquad(owner, exp, vehicle) => + ops.shareAntExperienceWithSquad(owner, exp, vehicle) - case AvatarAction.RemoveFromOutfitChat(outfit_id) => - ops.removeFromOutfitChat(outfit_id) + case AvatarAction.RemoveFromOutfitChat(outfit_id) => + ops.removeFromOutfitChat(outfit_id) - case SendResponse(msgs) => - msgs.foreach(sendResponse) + /* common messages (maybe once every respawn) */ + case ReloadTool(itemGuid) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { _.visible } => + sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0)) - /* common messages (maybe once every respawn) */ - case ReloadTool(itemGuid) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } => - sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0)) - - case AvatarAction.Killed(cause, mount) => - //log and chat messages - //destroy display - val zoneChannel = continent.id - val events = continent.AvatarEvents - val pentry = PlayerSource(player) - cause - .adversarial - .collect { case out @ Adversarial(attacker, _, _) if attacker != PlayerSource.Nobody => out } - .orElse { - player.LastDamage.collect { - case attack if System.currentTimeMillis() - attack.interaction.hitTime < (10 seconds).toMillis => - attack - .adversarial - .collect { case out @ Adversarial(attacker, _, _) if attacker != PlayerSource.Nobody => out } - }.flatten - } match { - case Some(adversarial) => - events ! MessageEnvelope( - zoneChannel, - AvatarAction.DestroyDisplay(adversarial.attacker, pentry, adversarial.implement) - ) - case _ => - events ! MessageEnvelope(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0)) - } - //events chat and log - val excuse = player.LastDamage.flatMap { damage => - val interaction = damage.interaction - val reason = interaction.cause - val adversarial = interaction.adversarial.map { _.attacker } - reason match { - case r: ExplodingEntityReason if r.entity.isInstanceOf[VehicleSpawnPad] => - //also, @SVCP_Killed_TooCloseToPadOnCreate^n~ or "... within n meters of pad ..." - sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SVCP_Killed_OnPadOnCreate")) - case _ => () - } - adversarial.map {_.Name }.orElse { Some(s"a ${reason.getClass.getSimpleName}") } - }.getOrElse { s"an unfortunate circumstance (probably ${player.Sex.pronounObject} own fault)" } - log.info(s"${player.Name} has died, killed by $excuse") - if (sessionLogic.shooting.shotsWhileDead > 0) { - log.warn( - s"SHOTS_WHILE_DEAD: client of ${avatar.name} fired ${sessionLogic.shooting.shotsWhileDead} rounds while character was dead on server" + case AvatarAction.Killed(cause, mount) => + //log and chat messages + //destroy display + val zoneChannel = continent.id + val events = continent.AvatarEvents + val pentry = PlayerSource(player) + cause + .adversarial + .collect { case out @ Adversarial(attacker, _, _) if attacker != PlayerSource.Nobody => out } + .orElse { + player.LastDamage.collect { + case attack if System.currentTimeMillis() - attack.interaction.hitTime < (10 seconds).toMillis => + attack + .adversarial + .collect { case out @ Adversarial(attacker, _, _) if attacker != PlayerSource.Nobody => out } + }.flatten + } match { + case Some(adversarial) => + events ! MessageEnvelope( + zoneChannel, + AvatarAction.DestroyDisplay(adversarial.attacker, pentry, adversarial.implement) ) - sessionLogic.shooting.shotsWhileDead = 0 + case _ => + events ! MessageEnvelope(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0)) + } + //events chat and log + val excuse = player.LastDamage.flatMap { damage => + val interaction = damage.interaction + val reason = interaction.cause + val adversarial = interaction.adversarial.map { _.attacker } + reason match { + case r: ExplodingEntityReason if r.entity.isInstanceOf[VehicleSpawnPad] => + //also, @SVCP_Killed_TooCloseToPadOnCreate^n~ or "... within n meters of pad ..." + sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SVCP_Killed_OnPadOnCreate")) + case _ => () } - //TODO other methods of death? - sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason(msg = "cancel") - sessionLogic.general.renewCharSavedTimer(fixedLen = 1800L, varLen = 0L) - continent.actor ! ZoneActor.RewardThisDeath(player) - - //player state changes - sessionLogic.zoning.spawn.avatarActive = false - AvatarActor.updateToolDischargeFor(avatar) - player.FreeHand.Equipment.foreach { item => - DropEquipmentFromInventory(player)(item) - } - sessionLogic.general.dropSpecialSlotItem() - sessionLogic.general.toggleMaxSpecialState(enable = false) - sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive - sessionLogic.zoning.zoningStatus = Zoning.Status.None - sessionLogic.zoning.spawn.deadState = DeadState.Dead - continent.GUID(mount).collect { - case obj: Vehicle => - killedWhileMounted(obj, resolvedPlayerGuid) - sessionLogic.vehicles.ConditionalDriverVehicleControl(obj) - sessionLogic.general.unaccessContainer(obj) - - case obj: PlanetSideGameObject with Mountable with Container => - killedWhileMounted(obj, resolvedPlayerGuid) - sessionLogic.general.unaccessContainer(obj) - - case obj: PlanetSideGameObject with Mountable => - killedWhileMounted(obj, resolvedPlayerGuid) - } - sessionLogic.actionsToCancel() - sessionLogic.terminals.CancelAllProximityUnits() - AvatarActor.savePlayerLocation(player) - sessionLogic.zoning.spawn.ShiftPosition = Some(player.Position) - - //respawn - val respawnTimer = 300000 //milliseconds - sendResponse(AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, player.Position, player.Faction, unk5=true)) - sessionLogic.zoning.spawn.reviveTimer.cancel() - sessionLogic.zoning.spawn.reviveTimer = Default.Cancellable - if (player.death_by == 0) { - sessionLogic.zoning.spawn.randomRespawn(300.seconds) - } else { - sessionLogic.zoning.spawn.HandleReleaseAvatar(player, continent) - } - - case AvatarAction.ReleasePlayer(tplayer) if isNotSameTarget => - sessionLogic.zoning.spawn.DepictPlayerAsCorpse(tplayer) - - case AvatarAction.Revive(revivalTargetGuid) - if resolvedPlayerGuid == revivalTargetGuid => - log.info(s"No time for rest, ${player.Name}. Back on your feet!") - ops.revive() - player.Actor ! Player.Revive - player.History - .findLast { _.isInstanceOf[RevivingActivity] } - .map { - case activity: RevivingActivity - if System.currentTimeMillis() - activity.time < 5000L => - val reviveMessage = s"@YouHaveBeenMessage^revived~^${activity.user.unique.name}~" - sendResponse(ChatMsg(ChatMessageType.UNK_227, reviveMessage)) - None - } - - /* uncommon messages (utility, or once in a while) */ - case ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } => - ops.changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data) - sendResponse(ChangeAmmoMessage(weapon_guid, 1)) - - case ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) - if isNotSameTarget => - ops.changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data) - - case AvatarAction.ChangeFireMode(itemGuid, mode) if isNotSameTarget => - sendResponse(ChangeFireModeMessage(itemGuid, mode)) - - case ConcealPlayer(_) => - sendResponse(GenericObjectActionMessage(guid, code=9)) - - case AvatarAction.EnvironmentalDamage(_, _, _) => - //TODO damage marker? - sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg") - - case AvatarAction.DropCreatedItem(pkt) if isNotSameTarget => - sendResponse(pkt) - - case ObjectDelete(itemGuid, unk) if isNotSameTarget => - sendResponse(ObjectDeleteMessage(itemGuid, unk)) - - /* rare messages */ - case SetEmpire(objectGuid, faction) if isNotSameTarget => - sendResponse(SetEmpireMessage(objectGuid, faction)) - - case AvatarAction.DropSpecialItem() => - sessionLogic.general.dropSpecialSlotItem() - - case AvatarAction.OxygenState(player, vehicle) => - sendResponse(OxygenStateMessage( - DrowningTarget(player.guid, player.progress, player.state), - vehicle.flatMap { vinfo => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state)) } - )) - - case AvatarAction.LoadCreatedProjectile(pkt) if isNotSameTarget => - sendResponse(pkt) - - case AvatarAction.ProjectileState(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid) if isNotSameTarget => - sendResponse(ProjectileStateMessage(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid)) - - case AvatarAction.ProjectileExplodes(projectileGuid, projectile) => - sendResponse( - ProjectileStateMessage( - projectileGuid, - projectile.Position, - shot_vel = Vector3.Zero, - projectile.Orientation, - sequence_num=0, - end=true, - hit_target_guid=PlanetSideGUID(0) - ) + adversarial.map {_.Name }.orElse { Some(s"a ${reason.getClass.getSimpleName}") } + }.getOrElse { s"an unfortunate circumstance (probably ${player.Sex.pronounObject} own fault)" } + log.info(s"${player.Name} has died, killed by $excuse") + if (sessionLogic.shooting.shotsWhileDead > 0) { + log.warn( + s"SHOTS_WHILE_DEAD: client of ${avatar.name} fired ${sessionLogic.shooting.shotsWhileDead} rounds while character was dead on server" ) - sendResponse(ObjectDeleteMessage(projectileGuid, unk1=2)) + sessionLogic.shooting.shotsWhileDead = 0 + } + //TODO other methods of death? + sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason(msg = "cancel") + sessionLogic.general.renewCharSavedTimer(fixedLen = 1800L, varLen = 0L) + continent.actor ! ZoneActor.RewardThisDeath(player) - case AvatarAction.ProjectileAutoLockAwareness(mode) => - sendResponse(GenericActionMessage(mode)) + //player state changes + sessionLogic.zoning.spawn.avatarActive = false + AvatarActor.updateToolDischargeFor(avatar) + player.FreeHand.Equipment.foreach { item => + DropEquipmentFromInventory(player)(item) + } + sessionLogic.general.dropSpecialSlotItem() + sessionLogic.general.toggleMaxSpecialState(enable = false) + sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive + sessionLogic.zoning.zoningStatus = Zoning.Status.None + sessionLogic.zoning.spawn.deadState = DeadState.Dead + continent.GUID(mount).collect { + case obj: Vehicle => + killedWhileMounted(obj, filter.resolvedPlayerGuid) + sessionLogic.vehicles.ConditionalDriverVehicleControl(obj) + sessionLogic.general.unaccessContainer(obj) - case AvatarAction.PutDownFDU(target) if isNotSameTarget => - sendResponse(GenericObjectActionMessage(target, code=53)) + case obj: PlanetSideGameObject with Mountable with Container => + killedWhileMounted(obj, filter.resolvedPlayerGuid) + sessionLogic.general.unaccessContainer(obj) - case AvatarAction.StowEquipment(target, slot, item) if isNotSameTarget => - val definition = item.Definition - sendResponse( - ObjectCreateDetailedMessage( - definition.ObjectId, - item.GUID, - ObjectCreateMessageParent(target, slot), - definition.Packet.DetailedConstructorData(item).get - ) - ) + case obj: PlanetSideGameObject with Mountable => + killedWhileMounted(obj, filter.resolvedPlayerGuid) + } + sessionLogic.actionsToCancel() + sessionLogic.terminals.CancelAllProximityUnits() + AvatarActor.savePlayerLocation(player) + sessionLogic.zoning.spawn.ShiftPosition = Some(player.Position) - case WeaponDryFire(weaponGuid) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } => - continent.GUID(weaponGuid).collect { - case tool: Tool if tool.Magazine == 0 => - // check that the magazine is still empty before sending WeaponDryFireMessage - // if it has been reloaded since then, other clients will not see it firing - sendResponse(WeaponDryFireMessage(weaponGuid)) + //respawn + val respawnTimer = 300000 //milliseconds + sendResponse(AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, player.Position, player.Faction, unk5=true)) + sessionLogic.zoning.spawn.reviveTimer.cancel() + sessionLogic.zoning.spawn.reviveTimer = Default.Cancellable + if (player.death_by == 0) { + sessionLogic.zoning.spawn.randomRespawn(300.seconds) + } else { + sessionLogic.zoning.spawn.HandleReleaseAvatar(player, continent) + } + + case AvatarAction.ReleasePlayer(tplayer) if filter.isNotSameTarget => + sessionLogic.zoning.spawn.DepictPlayerAsCorpse(tplayer) + + case AvatarAction.Revive(revivalTargetGuid) + if filter.resolvedPlayerGuid == revivalTargetGuid => + log.info(s"No time for rest, ${player.Name}. Back on your feet!") + ops.revive() + player.Actor ! Player.Revive + player.History + .findLast { _.isInstanceOf[RevivingActivity] } + .map { + case activity: RevivingActivity + if System.currentTimeMillis() - activity.time < 5000L => + val reviveMessage = s"@YouHaveBeenMessage^revived~^${activity.user.unique.name}~" + sendResponse(ChatMsg(ChatMessageType.UNK_227, reviveMessage)) + None } - case _ => () - } + /* uncommon messages (utility, or once in a while) */ + case ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { _.visible } => + ops.changeAmmoProcedure(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data) + sendResponse(ChangeAmmoMessage(weapon_guid, 1)) + + case AvatarAction.ChangeFireMode(itemGuid, mode) if filter.isNotSameTarget => + sendResponse(ChangeFireModeMessage(itemGuid, mode)) + + case AvatarAction.EnvironmentalDamage(_, _, _) => + //TODO damage marker? + sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg") + + case AvatarAction.DropCreatedItem(pkt) if filter.isNotSameTarget => + sendResponse(pkt) + + /* rare messages */ + case AvatarAction.DropSpecialItem() => + sessionLogic.general.dropSpecialSlotItem() + + case AvatarAction.OxygenState(player, vehicle) => + sendResponse(OxygenStateMessage( + DrowningTarget(player.guid, player.progress, player.state), + vehicle.flatMap { vinfo => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state)) } + )) + + case AvatarAction.LoadCreatedProjectile(pkt) if filter.isNotSameTarget => + sendResponse(pkt) + + case AvatarAction.ProjectileState(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid) if filter.isNotSameTarget => + sendResponse(ProjectileStateMessage(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid)) + + case AvatarAction.ProjectileExplodes(projectileGuid, projectile) => + sendResponse( + ProjectileStateMessage( + projectileGuid, + projectile.Position, + shot_vel = Vector3.Zero, + projectile.Orientation, + sequence_num=0, + end=true, + hit_target_guid=PlanetSideGUID(0) + ) + ) + sendResponse(ObjectDeleteMessage(projectileGuid, unk1=2)) + + case AvatarAction.ProjectileAutoLockAwareness(mode) => + sendResponse(GenericActionMessage(mode)) + + case AvatarAction.PutDownFDU(target) if filter.isNotSameTarget => + sendResponse(GenericObjectActionMessage(target, code=53)) + + case AvatarAction.StowEquipment(target, slot, item) if filter.isNotSameTarget => + val definition = item.Definition + sendResponse( + ObjectCreateDetailedMessage( + definition.ObjectId, + item.GUID, + ObjectCreateMessageParent(target, slot), + definition.Packet.DetailedConstructorData(item).get + ) + ) + + case WeaponDryFire(weaponGuid) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { _.visible } => + continent.GUID(weaponGuid).collect { + case tool: Tool if tool.Magazine == 0 => + // check that the magazine is still empty before sending WeaponDryFireMessage + // if it has been reloaded since then, other clients will not see it firing + sendResponse(WeaponDryFireMessage(weaponGuid)) + } } def killedWhileMounted(obj: PlanetSideGameObject with Mountable, playerGuid: PlanetSideGUID): Unit = { diff --git a/src/main/scala/net/psforever/actors/session/normal/GalaxyHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/GalaxyHandlerLogic.scala index 6e87ee521..273ffe3b7 100644 --- a/src/main/scala/net/psforever/actors/session/normal/GalaxyHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/GalaxyHandlerLogic.scala @@ -1,14 +1,15 @@ // Copyright (c) 2024 PSForever package net.psforever.actors.session.normal +import akka.actor.Actor.Receive import akka.actor.{ActorContext, ActorRef, typed} import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.support.{GalaxyHandlerFunctions, SessionData, SessionGalaxyHandlers} import net.psforever.packet.game.{BroadcastWarpgateUpdateMessage, FriendsResponse, HotSpotUpdateMessage, ZoneInfoMessage, ZonePopulationUpdateMessage, HotSpotInfo => PacketHotSpotInfo} import net.psforever.services.base.envelope.MessageEnvelope -import net.psforever.services.base.message.{EventResponse, SendResponse} +import net.psforever.services.base.message.EventResponse import net.psforever.services.galaxy.GalaxyAction -import net.psforever.types.{MemberAction, PlanetSideEmpire} +import net.psforever.types.{MemberAction, PlanetSideEmpire, PlanetSideGUID} object GalaxyHandlerLogic { def apply(ops: SessionGalaxyHandlers): GalaxyHandlerLogic = { @@ -34,62 +35,59 @@ class GalaxyHandlerLogic(val ops: SessionGalaxyHandlers, implicit val context: A /* response handlers */ - def handle(reply: EventResponse): Unit = { - reply match { - case GalaxyAction.HotSpotUpdate(zone_index, priority, hot_spot_info) => - sendResponse( - HotSpotUpdateMessage( - zone_index, - priority, - hot_spot_info.map { spot => PacketHotSpotInfo(spot.DisplayLocation.x, spot.DisplayLocation.y, 40) } - ) + override def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit = { + receive.apply(reply) + } + + def receive: Receive = { + case GalaxyAction.HotSpotUpdate(zone_index, priority, hot_spot_info) => + sendResponse( + HotSpotUpdateMessage( + zone_index, + priority, + hot_spot_info.map { spot => PacketHotSpotInfo(spot.DisplayLocation.x, spot.DisplayLocation.y, 40) } ) + ) - case GalaxyAction.MapUpdate(msg) => - sendResponse(msg) - import net.psforever.actors.zone.ZoneActor - import net.psforever.zones.Zones - Zones.zones.find(_.Number == msg.continent_id) match { - case Some(zone) => - zone.actor ! ZoneActor.BuildingInfoState(msg) - case None => - } + case GalaxyAction.MapUpdate(msg) => + sendResponse(msg) + import net.psforever.actors.zone.ZoneActor + import net.psforever.zones.Zones + Zones.zones.find(_.Number == msg.continent_id) match { + case Some(zone) => + zone.actor ! ZoneActor.BuildingInfoState(msg) + case None => + } - case GalaxyAction.UpdateBroadcastPrivileges(zoneId, gateMapId, fromFactions, toFactions) => - val faction = player.Faction - val from = fromFactions.contains(faction) - val to = toFactions.contains(faction) - if (from && !to) { - sendResponse(BroadcastWarpgateUpdateMessage(zoneId, gateMapId, PlanetSideEmpire.NEUTRAL)) - } else if (!from && to) { - sendResponse(BroadcastWarpgateUpdateMessage(zoneId, gateMapId, faction)) - } + case GalaxyAction.UpdateBroadcastPrivileges(zoneId, gateMapId, fromFactions, toFactions) => + val faction = player.Faction + val from = fromFactions.contains(faction) + val to = toFactions.contains(faction) + if (from && !to) { + sendResponse(BroadcastWarpgateUpdateMessage(zoneId, gateMapId, PlanetSideEmpire.NEUTRAL)) + } else if (!from && to) { + sendResponse(BroadcastWarpgateUpdateMessage(zoneId, gateMapId, faction)) + } - case GalaxyAction.FlagMapUpdate(msg) => - sendResponse(msg) + case GalaxyAction.FlagMapUpdate(msg) => + sendResponse(msg) - case GalaxyAction.TransferPassenger(_, temp_channel, vehicle, _, manifest) => - sessionLogic.zoning.handleTransferPassenger(temp_channel, vehicle, manifest) + case GalaxyAction.TransferPassenger(_, temp_channel, vehicle, _, manifest) => + sessionLogic.zoning.handleTransferPassenger(temp_channel, vehicle, manifest) - case GalaxyAction.LockedZoneUpdate(zone, time) => - sendResponse(ZoneInfoMessage(zone.Number, empire_status=false, lock_time=time)) + case GalaxyAction.LockedZoneUpdate(zone, time) => + sendResponse(ZoneInfoMessage(zone.Number, empire_status=false, lock_time=time)) - case GalaxyAction.UnlockedZoneUpdate(zone) => - sendResponse(ZoneInfoMessage(zone.Number, empire_status=true, lock_time=0L)) - val popBO = 0 - val pop = zone.LivePlayers.distinctBy(_.CharId) - val popTR = pop.count(_.Faction == PlanetSideEmpire.TR) - val popNC = pop.count(_.Faction == PlanetSideEmpire.NC) - val popVS = pop.count(_.Faction == PlanetSideEmpire.VS) - sendResponse(ZonePopulationUpdateMessage(zone.Number, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) + case GalaxyAction.UnlockedZoneUpdate(zone) => + sendResponse(ZoneInfoMessage(zone.Number, empire_status=true, lock_time=0L)) + val popBO = 0 + val pop = zone.LivePlayers.distinctBy(_.CharId) + val popTR = pop.count(_.Faction == PlanetSideEmpire.TR) + val popNC = pop.count(_.Faction == PlanetSideEmpire.NC) + val popVS = pop.count(_.Faction == PlanetSideEmpire.VS) + sendResponse(ZonePopulationUpdateMessage(zone.Number, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) - case GalaxyAction.LogStatusChange(name) if avatar.people.friend.exists(_.name.equals(name)) => - avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name) - - case SendResponse(msgs) => - msgs.foreach(sendResponse) - - case _ => () - } + case GalaxyAction.LogStatusChange(name) if avatar.people.friend.exists(_.name.equals(name)) => + avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name) } } diff --git a/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala index 23d34208f..f8bb508ee 100644 --- a/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala @@ -1,6 +1,7 @@ // Copyright (c) 2024 PSForever package net.psforever.actors.session.normal +import akka.actor.Actor.Receive import akka.actor.ActorContext import net.psforever.actors.session.support.SpawnOperations.ActivityQueuedTask import net.psforever.actors.session.support.{LocalHandlerFunctions, SessionData, SessionLocalHandlers, SpawnOperations} @@ -8,11 +9,11 @@ import net.psforever.objects.ce.Deployable import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.vehicles.MountableWeapons import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable, TelepadDeployable, Tool, TurretDeployable} -import net.psforever.packet.game.{ChatMsg, DeployableObjectsInfoMessage, GenericActionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HackMessage, HackState, HackState1, InventoryStateMessage, ObjectAttachMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, OrbitalShuttleTimeMsg, PadAndShuttlePair, PlanetsideAttributeMessage, ProximityTerminalUseMessage, SetEmpireMessage, TriggerEffectMessage, TriggerSoundMessage, TriggeredSound, VehicleStateMessage} -import net.psforever.services.base.message.{EventResponse, GenericObjectAction, ObjectDelete, PlanetsideAttribute, SendResponse, SetEmpire} +import net.psforever.packet.game.{ChatMsg, DeployableObjectsInfoMessage, GenericActionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HackMessage, HackState, HackState1, InventoryStateMessage, ObjectAttachMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, OrbitalShuttleTimeMsg, PadAndShuttlePair, PlanetsideAttributeMessage, ProximityTerminalUseMessage, TriggerEffectMessage, TriggerSoundMessage, TriggeredSound, VehicleStateMessage} +import net.psforever.services.base.message.{GenericObjectAction, PlanetsideAttribute, SendResponse} import net.psforever.services.{InterstellarClusterService, Service} import net.psforever.services.local.LocalAction -import net.psforever.types.{ChatMessageType, PlanetSideGUID, SpawnGroup} +import net.psforever.types.{ChatMessageType, SpawnGroup} object LocalHandlerLogic { def apply(ops: SessionLocalHandlers): LocalHandlerLogic = { @@ -35,238 +36,213 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act /* response handlers */ - /** - * na - * @param toChannel na - * @param guid na - * @param reply na - */ - def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit = { - val resolvedPlayerGuid = if (player.HasGUID) { - player.GUID - } else { - Service.defaultPlayerGUID - } - val isNotSameTarget = resolvedPlayerGuid != guid - reply match { - case LocalAction.DeployableMapIcon(behavior, deployInfo) if isNotSameTarget => - sendResponse(DeployableObjectsInfoMessage(behavior, deployInfo)) - - case LocalAction.DeployableUIFor(item) => - sessionLogic.general.updateDeployableUIElements(avatar.deployables.UpdateUIElement(item)) - - case LocalAction.Detonate(dguid, _: BoomerDeployable) => - sendResponse(TriggerEffectMessage(dguid, "detonate_boomer")) - sendResponse(PlanetsideAttributeMessage(dguid, attribute_type=29, attribute_value=1)) - sendResponse(ObjectDeleteMessage(dguid, unk1=0)) - - case LocalAction.Detonate(dguid, _: ExplosiveDeployable) => - sendResponse(GenericObjectActionMessage(dguid, code=19)) - sendResponse(PlanetsideAttributeMessage(dguid, attribute_type=29, attribute_value=1)) - sendResponse(ObjectDeleteMessage(dguid, unk1=0)) - - case LocalAction.Detonate(_, obj) => - log.warn(s"LocalAction.Detonate: ${obj.Definition.Name} not configured to explode correctly") - - case LocalAction.DoorOpens(_, door) if isNotSameTarget => - val doorGuid = door.GUID - val pos = player.Position.xy - val range = ops.doorLoadRange() - val foundDoor = continent - .blockMap - .sector(pos, range) - .amenityList - .collect { case door: Door => door } - .find(_.GUID == doorGuid) - val doorExistsInRange: Boolean = foundDoor.nonEmpty - if (doorExistsInRange) { - sendResponse(GenericObjectStateMsg(doorGuid, state=16)) - } - - case LocalAction.DoorCloses(doorGuid) => //door closes for everyone - sendResponse(GenericObjectStateMsg(doorGuid, state=17)) - - case LocalAction.EliminateDeployable(obj: TurretDeployable, dguid, _, _) if obj.Destroyed => - sendResponse(ObjectDeleteMessage(dguid, unk1=0)) - - case LocalAction.EliminateDeployable(obj: TurretDeployable, dguid, pos, _) => - obj.Destroyed = true - ops.DeconstructDeployable( - obj, - dguid, - pos, - obj.Orientation, - deletionType= if (obj.MountPoints.isEmpty) { 2 } else { 1 } - ) - - case LocalAction.EliminateDeployable(obj: ExplosiveDeployable, dguid, _, _) - if obj.Destroyed || obj.Jammed || obj.Health == 0 => - sendResponse(ObjectDeleteMessage(dguid, unk1=0)) - - case LocalAction.EliminateDeployable(obj: ExplosiveDeployable, dguid, pos, effect) => - obj.Destroyed = true - ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect) - - case LocalAction.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed => - //if active, deactivate - obj.Active = false - ops.deactivateTelpadDeployableMessages(dguid) - //standard deployable elimination behavior - sendResponse(ObjectDeleteMessage(dguid, unk1=0)) - - case LocalAction.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) if obj.Active => - //if active, deactivate - obj.Active = false - ops.deactivateTelpadDeployableMessages(dguid) - //standard deployable elimination behavior - obj.Destroyed = true - ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2) - - case LocalAction.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Destroyed => - //standard deployable elimination behavior - sendResponse(ObjectDeleteMessage(dguid, unk1=0)) - - case LocalAction.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) => - //standard deployable elimination behavior - obj.Destroyed = true - ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2) - - case LocalAction.EliminateDeployable(obj, dguid, _, _) if obj.Destroyed => - sendResponse(ObjectDeleteMessage(dguid, unk1=0)) - - case LocalAction.EliminateDeployable(obj, dguid, pos, effect) => - obj.Destroyed = true - ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect) - - case LocalAction.HackClear(targetGuid, unk1, unk2) => - sendResponse(HackMessage(HackState1.Unk0, targetGuid, guid, progress=0, unk1.toFloat, HackState.HackCleared, unk2)) - - case LocalAction.HackObject(targetGuid, unk1, unk2) => - sessionLogic.general.hackObject(targetGuid, unk1, unk2) - - case PlanetsideAttribute(targetGuid, attributeType, attributeValue) => - sessionLogic.general.sendPlanetsideAttributeMessage(targetGuid, attributeType, attributeValue) - - case GenericObjectAction(targetGuid, actionNumber) => - sendResponse(GenericObjectActionMessage(targetGuid, actionNumber)) - - case LocalAction.GenericActionMessage(actionNumber) => - sendResponse(GenericActionMessage(actionNumber)) - - case LocalAction.LluSpawned(llu) => - // Create LLU on client - sendResponse(ObjectCreateMessage( - llu.Definition.ObjectId, - llu.GUID, - llu.Definition.Packet.ConstructorData(llu).get - )) - sendResponse(TriggerSoundMessage(TriggeredSound.LLUMaterialize, llu.Position, unk=20, volume=0.8000001f)) - - case LocalAction.LluDespawned(lluGuid, position) => - sendResponse(TriggerSoundMessage(TriggeredSound.LLUDeconstruct, position, unk=20, volume=0.8000001f)) - sendResponse(ObjectDeleteMessage(lluGuid, unk1=0)) - // If the player was holding the LLU, remove it from their tracked special item slot - sessionLogic.general.specialItemSlotGuid.collect { case guid if guid == lluGuid => - sessionLogic.general.specialItemSlotGuid = None - player.Carrying = None - } - - case ObjectDelete(objectGuid, unk) if isNotSameTarget => - sendResponse(ObjectDeleteMessage(objectGuid, unk)) - - case LocalAction.ProximityTerminalEffect(object_guid, true) => - sendResponse(ProximityTerminalUseMessage(Service.defaultPlayerGUID, object_guid, unk=true)) - - case LocalAction.ProximityTerminalEffect(objectGuid, false) => - sendResponse(ProximityTerminalUseMessage(Service.defaultPlayerGUID, objectGuid, unk=false)) - sessionLogic.terminals.ForgetAllProximityTerminals(objectGuid) - - case LocalAction.RouterTelepadMessage(msg) => - sendResponse(ChatMsg(ChatMessageType.UNK_229, wideContents=false, recipient="", msg, note=None)) - - case LocalAction.RouterTelepadTransport(passengerGuid, srcGuid, destGuid) => - sessionLogic.general.useRouterTelepadEffect(passengerGuid, srcGuid, destGuid) - - case SendResponse(msgs) => - msgs.foreach { - case msg @ (m: GenericObjectActionMessage) - if (m.code == 58 || m.code == 60 || m.code == 61) && !sessionLogic.zoning.spawn.startEnqueueSquadMessages => - // delay building virus alert if player is dead/respawning - sessionLogic.zoning.spawn.enqueueNewActivity(ActivityQueuedTask( - SpawnOperations.delaySendGenericObjectActionMessage(msg), 1) - ) - case msg => - sendResponse(msg) - } - - case SetEmpire(objectGuid, empire) => - sendResponse(SetEmpireMessage(objectGuid, empire)) - - case LocalAction.ShuttleEvent(ev) => - val msg = OrbitalShuttleTimeMsg( - ev.u1, - ev.u2, - ev.t1, - ev.t2, - ev.t3, - pairs=ev.pairs.map { case ((a, b), c) => PadAndShuttlePair(a, b, c) } - ) - sendResponse(msg) - - case LocalAction.ShuttleDock(pguid, sguid, slot) => - sendResponse(ObjectAttachMessage(pguid, sguid, slot)) - - case LocalAction.ShuttleUndock(pguid, sguid, pos, orient) => - sendResponse(ObjectDetachMessage(pguid, sguid, pos, orient)) - - case LocalAction.ShuttleState(sguid, pos, orient, state) => - sendResponse(VehicleStateMessage(sguid, unk1=0, pos, orient, vel=None, Some(state), unk3=0, unk4=0, wheel_direction=15, is_decelerating=false, is_cloaked=false)) - - case LocalAction.ToggleTeleportSystem(router, systemPlan) => - sessionLogic.general.toggleTeleportSystem(router, systemPlan) - - case LocalAction.TriggerEffectAtLocation(targetGuid, effect, effectInfo, triggerLocation) => - sendResponse(TriggerEffectMessage(targetGuid, effect, effectInfo, triggerLocation)) - - case LocalAction.TriggerSound(sound, pos, unk, volume) => - sendResponse(TriggerSoundMessage(sound, pos, unk, volume)) - - case LocalAction.UpdateForceDomeStatus(buildingGuid, true) => - sendResponse(GenericObjectActionMessage(buildingGuid, 11)) - - case LocalAction.UpdateForceDomeStatus(buildingGuid, false) => - sendResponse(GenericObjectActionMessage(buildingGuid, 12)) - - case LocalAction.RechargeVehicleWeapon(vehicleGuid, weaponGuid) if resolvedPlayerGuid == guid => - continent.GUID(vehicleGuid) - .collect { case vehicle: MountableWeapons => (vehicle, vehicle.PassengerInSeat(player)) } - .collect { case (vehicle, Some(seat_num)) => vehicle.WeaponControlledFromSeat(seat_num) } - .getOrElse(Set.empty) - .collect { case weapon: Tool if weapon.GUID == weaponGuid => - sendResponse(InventoryStateMessage(weapon.AmmoSlot.Box.GUID, weapon.GUID, weapon.Magazine)) - } - - case LocalAction.ForceZoneChange(zone) => - //todo we might be able to piggyback this for squad recalls later - if(session.zone eq zone) { - sessionLogic.zoning.zoneReload = true - zone.AvatarEvents ! Service.LeaveAll - zone.LocalEvents ! Service.LeaveAll - zone.VehicleEvents ! Service.LeaveAll - zone.AvatarEvents ! Service.Join(player.Name) //must manually restore this subscriptions - sessionLogic.zoning.spawn.handleNewPlayerLoaded(player) //will restart subscriptions and dispatch a LoadMapMessage - } else { - import akka.actor.typed.scaladsl.adapter._ - sessionLogic.cluster ! InterstellarClusterService.GetRandomSpawnPoint( - zone.Number, - player.Faction, - Seq(SpawnGroup.Facility, SpawnGroup.Tower, SpawnGroup.AMS), - context.self + def receive: Receive = { + case SendResponse(msgs) => + msgs.foreach { + case msg @ (m: GenericObjectActionMessage) + if (m.code == 58 || m.code == 60 || m.code == 61) && !sessionLogic.zoning.spawn.startEnqueueSquadMessages => + // delay building virus alert if player is dead/respawning + sessionLogic.zoning.spawn.enqueueNewActivity(ActivityQueuedTask( + SpawnOperations.delaySendGenericObjectActionMessage(msg), 1) ) + case msg => + sendResponse(msg) + } + + case LocalAction.DeployableMapIcon(behavior, deployInfo) if filter.isNotSameTarget => + sendResponse(DeployableObjectsInfoMessage(behavior, deployInfo)) + + case LocalAction.DeployableUIFor(item) => + sessionLogic.general.updateDeployableUIElements(avatar.deployables.UpdateUIElement(item)) + + case LocalAction.Detonate(dguid, _: BoomerDeployable) => + sendResponse(TriggerEffectMessage(dguid, "detonate_boomer")) + sendResponse(PlanetsideAttributeMessage(dguid, attribute_type=29, attribute_value=1)) + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) + + case LocalAction.Detonate(dguid, _: ExplosiveDeployable) => + sendResponse(GenericObjectActionMessage(dguid, code=19)) + sendResponse(PlanetsideAttributeMessage(dguid, attribute_type=29, attribute_value=1)) + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) + + case LocalAction.Detonate(_, obj) => + log.warn(s"LocalAction.Detonate: ${obj.Definition.Name} not configured to explode correctly") + + case LocalAction.DoorOpens(_, door) if filter.isNotSameTarget => + val doorGuid = door.GUID + val pos = player.Position.xy + val range = ops.doorLoadRange() + val foundDoor = continent + .blockMap + .sector(pos, range) + .amenityList + .collect { case door: Door => door } + .find(_.GUID == doorGuid) + val doorExistsInRange: Boolean = foundDoor.nonEmpty + if (doorExistsInRange) { + sendResponse(GenericObjectStateMsg(doorGuid, state=16)) + } + + case LocalAction.DoorCloses(doorGuid) => //door closes for everyone + sendResponse(GenericObjectStateMsg(doorGuid, state=17)) + + case LocalAction.EliminateDeployable(obj: TurretDeployable, dguid, _, _) if obj.Destroyed => + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) + + case LocalAction.EliminateDeployable(obj: TurretDeployable, dguid, pos, _) => + obj.Destroyed = true + ops.DeconstructDeployable( + obj, + dguid, + pos, + obj.Orientation, + deletionType= if (obj.MountPoints.isEmpty) { 2 } else { 1 } + ) + + case LocalAction.EliminateDeployable(obj: ExplosiveDeployable, dguid, _, _) + if obj.Destroyed || obj.Jammed || obj.Health == 0 => + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) + + case LocalAction.EliminateDeployable(obj: ExplosiveDeployable, dguid, pos, effect) => + obj.Destroyed = true + ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect) + + case LocalAction.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed => + //if active, deactivate + obj.Active = false + ops.deactivateTelpadDeployableMessages(dguid) + //standard deployable elimination behavior + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) + + case LocalAction.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) if obj.Active => + //if active, deactivate + obj.Active = false + ops.deactivateTelpadDeployableMessages(dguid) + //standard deployable elimination behavior + obj.Destroyed = true + ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2) + + case LocalAction.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Destroyed => + //standard deployable elimination behavior + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) + + case LocalAction.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) => + //standard deployable elimination behavior + obj.Destroyed = true + ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2) + + case LocalAction.EliminateDeployable(obj, dguid, _, _) if obj.Destroyed => + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) + + case LocalAction.EliminateDeployable(obj, dguid, pos, effect) => + obj.Destroyed = true + ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect) + + case LocalAction.HackClear(targetGuid, unk1, unk2) => + sendResponse(HackMessage(HackState1.Unk0, targetGuid, filter.resolvedPlayerGuid, progress=0, unk1.toFloat, HackState.HackCleared, unk2)) + + case LocalAction.HackObject(targetGuid, unk1, unk2) => + sessionLogic.general.hackObject(targetGuid, unk1, unk2) + + case PlanetsideAttribute(targetGuid, attributeType, attributeValue) => + sessionLogic.general.sendPlanetsideAttributeMessage(targetGuid, attributeType, attributeValue) + + case LocalAction.GenericActionMessage(actionNumber) => + sendResponse(GenericActionMessage(actionNumber)) + + case LocalAction.LluSpawned(llu) => + // Create LLU on client + sendResponse(ObjectCreateMessage( + llu.Definition.ObjectId, + llu.GUID, + llu.Definition.Packet.ConstructorData(llu).get + )) + sendResponse(TriggerSoundMessage(TriggeredSound.LLUMaterialize, llu.Position, unk=20, volume=0.8000001f)) + + case LocalAction.LluDespawned(lluGuid, position) => + sendResponse(TriggerSoundMessage(TriggeredSound.LLUDeconstruct, position, unk=20, volume=0.8000001f)) + sendResponse(ObjectDeleteMessage(lluGuid, unk1=0)) + // If the player was holding the LLU, remove it from their tracked special item slot + sessionLogic.general.specialItemSlotGuid.collect { case guid if guid == lluGuid => + sessionLogic.general.specialItemSlotGuid = None + player.Carrying = None + } + + case LocalAction.ProximityTerminalEffect(object_guid, true) => + sendResponse(ProximityTerminalUseMessage(Service.defaultPlayerGUID, object_guid, unk=true)) + + case LocalAction.ProximityTerminalEffect(objectGuid, false) => + sendResponse(ProximityTerminalUseMessage(Service.defaultPlayerGUID, objectGuid, unk=false)) + sessionLogic.terminals.ForgetAllProximityTerminals(objectGuid) + + case LocalAction.RouterTelepadMessage(msg) => + sendResponse(ChatMsg(ChatMessageType.UNK_229, wideContents=false, recipient="", msg, note=None)) + + case LocalAction.RouterTelepadTransport(passengerGuid, srcGuid, destGuid) => + sessionLogic.general.useRouterTelepadEffect(passengerGuid, srcGuid, destGuid) + + case LocalAction.ShuttleEvent(ev) => + val msg = OrbitalShuttleTimeMsg( + ev.u1, + ev.u2, + ev.t1, + ev.t2, + ev.t3, + pairs=ev.pairs.map { case ((a, b), c) => PadAndShuttlePair(a, b, c) } + ) + sendResponse(msg) + + case LocalAction.ShuttleDock(pguid, sguid, slot) => + sendResponse(ObjectAttachMessage(pguid, sguid, slot)) + + case LocalAction.ShuttleUndock(pguid, sguid, pos, orient) => + sendResponse(ObjectDetachMessage(pguid, sguid, pos, orient)) + + case LocalAction.ShuttleState(sguid, pos, orient, state) => + sendResponse(VehicleStateMessage(sguid, unk1=0, pos, orient, vel=None, Some(state), unk3=0, unk4=0, wheel_direction=15, is_decelerating=false, is_cloaked=false)) + + case LocalAction.ToggleTeleportSystem(router, systemPlan) => + sessionLogic.general.toggleTeleportSystem(router, systemPlan) + + case LocalAction.TriggerEffectAtLocation(targetGuid, effect, effectInfo, triggerLocation) => + sendResponse(TriggerEffectMessage(targetGuid, effect, effectInfo, triggerLocation)) + + case LocalAction.TriggerSound(sound, pos, unk, volume) => + sendResponse(TriggerSoundMessage(sound, pos, unk, volume)) + + case LocalAction.UpdateForceDomeStatus(buildingGuid, true) => + sendResponse(GenericObjectActionMessage(buildingGuid, 11)) + + case LocalAction.UpdateForceDomeStatus(buildingGuid, false) => + sendResponse(GenericObjectActionMessage(buildingGuid, 12)) + + case LocalAction.RechargeVehicleWeapon(vehicleGuid, weaponGuid) if filter.isSameTarget /*resolvedPlayerGuid == guid*/ => + continent.GUID(vehicleGuid) + .collect { case vehicle: MountableWeapons => (vehicle, vehicle.PassengerInSeat(player)) } + .collect { case (vehicle, Some(seat_num)) => vehicle.WeaponControlledFromSeat(seat_num) } + .getOrElse(Set.empty) + .collect { case weapon: Tool if weapon.GUID == weaponGuid => + sendResponse(InventoryStateMessage(weapon.AmmoSlot.Box.GUID, weapon.GUID, weapon.Magazine)) } - case _ => () - } + case LocalAction.ForceZoneChange(zone) => + //todo we might be able to piggyback this for squad recalls later + if(session.zone eq zone) { + sessionLogic.zoning.zoneReload = true + zone.AvatarEvents ! Service.LeaveAll + zone.LocalEvents ! Service.LeaveAll + zone.VehicleEvents ! Service.LeaveAll + zone.AvatarEvents ! Service.Join(player.Name) //must manually restore this subscriptions + sessionLogic.zoning.spawn.handleNewPlayerLoaded(player) //will restart subscriptions and dispatch a LoadMapMessage + } else { + import akka.actor.typed.scaladsl.adapter._ + sessionLogic.cluster ! InterstellarClusterService.GetRandomSpawnPoint( + zone.Number, + player.Faction, + Seq(SpawnGroup.Facility, SpawnGroup.Tower, SpawnGroup.AMS), + context.self + ) + } } /* support functions */ diff --git a/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala index 62acd99c1..9758c53d3 100644 --- a/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala @@ -1,6 +1,7 @@ // Copyright (c) 2024 PSForever package net.psforever.actors.session.normal +import akka.actor.Actor.Receive import akka.actor.{ActorContext, ActorRef, typed} import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.support.{SessionData, SessionVehicleHandlers, VehicleHandlerFunctions} @@ -15,7 +16,7 @@ import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent import net.psforever.packet.game.{ChangeAmmoMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, ChildObjectStateMessage, DeadState, DeployRequestMessage, DismountVehicleMsg, FrameVehicleStateMessage, GenericObjectActionMessage, HitHint, InventoryStateMessage, ObjectAttachMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, PlanetsideAttributeMessage, ReloadMessage, ServerVehicleOverrideMsg, VehicleStateMessage, WeaponDryFireMessage} import net.psforever.services.Service import net.psforever.services.base.envelope.GenericResponseEnvelope -import net.psforever.services.base.message.{ChangeAmmo, ChangeFireState_Start, ChangeFireState_Stop, EventResponse, GenericObjectAction, HintsAtAttacker, ObjectDelete, PlanetsideAttribute, ReloadTool, SendResponse, WeaponDryFire} +import net.psforever.services.base.message.{ChangeAmmo, ChangeFireState_Start, ChangeFireState_Stop, GenericObjectAction, HintsAtAttacker, ObjectDelete, PlanetsideAttribute, ReloadTool, SendResponse, WeaponDryFire} import net.psforever.services.local.support.CaptureFlagManager import net.psforever.services.vehicle.{VehicleAction, VehicleStamp} import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3} @@ -33,367 +34,305 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: private val galaxyService: ActorRef = ops.galaxyService - /** - * na - * - * @param toChannel na - * @param guid na - * @param reply na - */ - def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit = { - val resolvedPlayerGuid = if (player.HasGUID) { - player.GUID - } else { - PlanetSideGUID(-1) - } - val isNotSameTarget = resolvedPlayerGuid != guid - reply match { - case VehicleAction.VehicleState( - vehicleGuid, - unk1, - pos, - orient, - vel, - unk2, - unk3, - unk4, - wheelDirection, - unk5, - unk6 - ) if isNotSameTarget && player.VehicleSeated.contains(vehicleGuid) => - //player who is also in the vehicle (not driver) - sendResponse(VehicleStateMessage(vehicleGuid, unk1, pos, orient, vel, unk2, unk3, unk4, wheelDirection, unk5, unk6)) - player.Position = pos - player.Orientation = orient - player.Velocity = vel - sessionLogic.updateLocalBlockMap(pos) - //llu destruction check - if (player.Carrying.contains(SpecialCarry.CaptureFlag)) { - continent - .GUID(player.VehicleSeated) - .collect { case vehicle: Vehicle => - CaptureFlagManager.ReasonToLoseFlagViolently(continent, sessionLogic.general.specialItemSlotGuid, vehicle) - } - } - - case VehicleAction.VehicleState( - vehicleGuid, - unk1, - pos, - ang, - vel, - unk2, - unk3, - unk4, - wheelDirection, - unk5, - unk6 - ) if isNotSameTarget => - //player who is watching the vehicle from the outside - sendResponse(VehicleStateMessage(vehicleGuid, unk1, pos, ang, vel, unk2, unk3, unk4, wheelDirection, unk5, unk6)) - - case VehicleAction.ChildObjectState(objectGuid, pitch, yaw) if isNotSameTarget => - sendResponse(ChildObjectStateMessage(objectGuid, pitch, yaw)) - - case VehicleAction.FrameVehicleState(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA) - if isNotSameTarget => - sendResponse(FrameVehicleStateMessage(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA)) - - case ChangeFireState_Start(weaponGuid) if isNotSameTarget => - sendResponse(ChangeFireStateMessage_Start(weaponGuid)) - - case ChangeFireState_Stop(weaponGuid) if isNotSameTarget => - sendResponse(ChangeFireStateMessage_Stop(weaponGuid)) - - case ReloadTool(itemGuid) if isNotSameTarget => - sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0)) - - case ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) if isNotSameTarget => - sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3.Zero, 0)) - //TODO? sendResponse(ObjectDeleteMessage(previousAmmoGuid, 0)) - sendResponse( - ObjectCreateMessage( - ammo_id, - ammo_guid, - ObjectCreateMessageParent(weapon_guid, weapon_slot), - ammo_data - ) - ) - sendResponse(ChangeAmmoMessage(weapon_guid, 1)) - - case WeaponDryFire(weaponGuid) if isNotSameTarget => - continent.GUID(weaponGuid).collect { - case tool: Tool if tool.Magazine == 0 => - // check that the magazine is still empty before sending WeaponDryFireMessage - // if it has been reloaded since then, other clients will not see it firing - sendResponse(WeaponDryFireMessage(weaponGuid)) - } - - case VehicleAction.DismountVehicle(bailType, wasKickedByDriver) if isNotSameTarget => - sendResponse(DismountVehicleMsg(guid, bailType, wasKickedByDriver)) - - case VehicleAction.MountVehicle(vehicleGuid, seat) if isNotSameTarget => - sendResponse(ObjectAttachMessage(vehicleGuid, guid, seat)) - - case VehicleAction.DeployRequest(objectGuid, state, unk1, unk2, pos) if isNotSameTarget => - sendResponse(DeployRequestMessage(guid, objectGuid, state, unk1, unk2, pos)) - - case SendResponse(msgs) => - msgs.foreach(sendResponse) - - case VehicleAction.EquipmentCreatedInSlot(pkt) if isNotSameTarget => - sendResponse(pkt) - - case GenericObjectAction(objectGuid, action) if isNotSameTarget => - sendResponse(GenericObjectActionMessage(objectGuid, action)) - - case HintsAtAttacker(sourceGuid) if player.isAlive => - sendResponse(HitHint(sourceGuid, player.GUID)) - - case VehicleAction.InventoryState(obj, parentGuid, start, conData) if isNotSameTarget => - //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? - val objGuid = obj.GUID - sendResponse(ObjectDeleteMessage(objGuid, unk1=0)) - sendResponse(ObjectCreateDetailedMessage( - obj.Definition.ObjectId, - objGuid, - ObjectCreateMessageParent(parentGuid, start), - conData - )) - - case VehicleAction.KickPassenger(_, wasKickedByDriver, vehicleGuid) if resolvedPlayerGuid == guid => - //seat number (first field) seems to be correct if passenger is kicked manually by driver - //but always seems to return 4 if user is kicked by mount permissions changing - sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver)) - val typeOfRide = continent.GUID(vehicleGuid) match { - case Some(obj: Vehicle) => - sessionLogic.general.unaccessContainer(obj) - s"the ${obj.Definition.Name}'s seat by ${obj.OwnerName.getOrElse("the pilot")}" - case _ => - s"${player.Sex.possessive} ride" - } - log.info(s"${player.Name} has been kicked from $typeOfRide!") - player.WhichSide = OutsideOf - - case VehicleAction.KickPassenger(_, wasKickedByDriver, _) => - //seat number (first field) seems to be correct if passenger is kicked manually by driver - //but always seems to return 4 if user is kicked by mount permissions changing - sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver)) - - case VehicleAction.InventoryState2(objGuid, parentGuid, value) if isNotSameTarget => - sendResponse(InventoryStateMessage(objGuid, unk=0, parentGuid, value)) - - case VehicleAction.LoadVehicle(vehicle, vtype, vguid, vdata) if isNotSameTarget => - //this is not be suitable for vehicles with people who are seated in it before it spawns (if that is possible) - sendResponse(ObjectCreateMessage(vtype, vguid, vdata)) - Vehicles.ReloadAccessPermissions(vehicle, player.Name) - - case ObjectDelete(itemGuid, _) if isNotSameTarget => - sendResponse(ObjectDeleteMessage(itemGuid, unk1=0)) - - case VehicleAction.Ownership(vehicleGuid) if resolvedPlayerGuid == guid => - //Only the player that owns this vehicle needs the ownership packet - avatarActor ! AvatarActor.SetVehicle(Some(vehicleGuid)) - sendResponse(PlanetsideAttributeMessage(resolvedPlayerGuid, attribute_type=21, vehicleGuid)) - - case VehicleAction.LoseOwnership(_, vehicleGuid) => - ops.announceAmsDecay(vehicleGuid,msg = "@ams_decaystarted") - - case PlanetsideAttribute(vehicleGuid, attributeType, attributeValue) if isNotSameTarget => - sendResponse(PlanetsideAttributeMessage(vehicleGuid, attributeType, attributeValue)) - - case VehicleAction.SeatPermissions(vehicleGuid, seatGroup, permission) if isNotSameTarget => - sendResponse(PlanetsideAttributeMessage(vehicleGuid, seatGroup, permission)) - - case VehicleAction.StowCreatedEquipment(vehicleGuid, slot, itemType, itemGuid, itemData) if isNotSameTarget => - //TODO prefer ObjectAttachMessage, but how to force ammo pools to update properly? - sendResponse(ObjectCreateDetailedMessage(itemType, itemGuid, ObjectCreateMessageParent(vehicleGuid, slot), itemData)) - - case VehicleAction.UnloadVehicle(_, vehicleGuid) => - sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=1)) - if (sessionLogic.zoning.spawn.prevSpawnPoint.map(_.Owner).exists { - case ams: Vehicle => - ams.GUID == vehicleGuid && - ams.OwnerGuid.isEmpty - case _ => - false - }) { - sessionLogic.zoning.spawn.prevSpawnPoint = None - sendResponse(ChatMsg(ChatMessageType.UNK_229, "@ams_decayed")) - } - - case VehicleAction.UnstowEquipment(itemGuid) if isNotSameTarget => - //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? - sendResponse(ObjectDeleteMessage(itemGuid, unk1=0)) - - case VehicleAction.UpdateAmsSpawnList(list) => - sessionLogic.zoning.spawn.amsSpawnPoints = list.filter(tube => tube.Faction == player.Faction) - sessionLogic.zoning.spawn.DrawCurrentAmsSpawnPoint() - - case VehicleAction.TransferPassengerChannel(oldChannel, tempChannel, vehicle, vehicleToDelete) if isNotSameTarget => - sessionLogic.zoning.interstellarFerry = Some(vehicle) - sessionLogic.zoning.interstellarFerryTopLevelGUID = Some(vehicleToDelete) - continent.VehicleEvents ! Service.Leave(oldChannel) //old vehicle-specific channel (was s"${vehicle.Actor}") - galaxyService ! Service.Join(tempChannel) //temporary vehicle-specific channel - log.debug(s"TransferPassengerChannel: ${player.Name} now subscribed to $tempChannel for vehicle gating") - - case VehicleAction.KickCargo(vehicle, speed, delay) - if player.VehicleSeated.nonEmpty && sessionLogic.zoning.spawn.deadState == DeadState.Alive && speed > 0 => - val strafe = 1 + Vehicles.CargoOrientation(vehicle) - val reverseSpeed = if (strafe > 1) { 0 } else { speed } - //strafe or reverse, not both - sessionLogic.vehicles.ServerVehicleOverrideWithPacket( - vehicle, - ServerVehicleOverrideMsg( - lock_accelerator=true, - lock_wheel=true, - reverse=true, - unk4=false, - lock_vthrust=0, - strafe, - reverseSpeed, - unk8=Some(0) - ) - ) - import scala.concurrent.ExecutionContext.Implicits.global - import scala.concurrent.duration._ - val resp = GenericResponseEnvelope( - VehicleStamp, - toChannel, - PlanetSideGUID(0), - VehicleAction.KickCargo(vehicle, speed=0, delay) - ) - context.system.scheduler.scheduleOnce(delay milliseconds, context.self, resp) - - case VehicleAction.KickCargo(cargo, _, _) - if player.VehicleSeated.nonEmpty && sessionLogic.zoning.spawn.deadState == DeadState.Alive => - sessionLogic.vehicles.TotalDriverVehicleControl(cargo) - - case VehicleAction.ChangeLoadout(target, oldWeapons, addedWeapons, oldInventory, newInventory) - if player.avatar.vehicle.contains(target) => - //TODO when vehicle weapons can be changed without visual glitches, rewrite this - continent.GUID(target).collect { case vehicle: Vehicle => - import net.psforever.login.WorldSession.boolToInt - //owner: must unregister old equipment, and register and install new equipment - (oldWeapons ++ oldInventory).foreach { - case (obj, eguid) => - sendResponse(ObjectDeleteMessage(eguid, unk1=0)) - TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) + def receive: Receive = { + case VehicleAction.VehicleState( + vehicleGuid, + unk1, + pos, + orient, + vel, + unk2, + unk3, + unk4, + wheelDirection, + unk5, + unk6 + ) if filter.isNotSameTarget && player.VehicleSeated.contains(vehicleGuid) => + //player who is also in the vehicle (not driver) + sendResponse(VehicleStateMessage(vehicleGuid, unk1, pos, orient, vel, unk2, unk3, unk4, wheelDirection, unk5, unk6)) + player.Position = pos + player.Orientation = orient + player.Velocity = vel + sessionLogic.updateLocalBlockMap(pos) + //llu destruction check + if (player.Carrying.contains(SpecialCarry.CaptureFlag)) { + continent + .GUID(player.VehicleSeated) + .collect { case vehicle: Vehicle => + CaptureFlagManager.ReasonToLoseFlagViolently(continent, sessionLogic.general.specialItemSlotGuid, vehicle) } - sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, vehicle, addedWeapons ++ newInventory) - //jammer or unjamm new weapons based on vehicle status - val vehicleJammered = vehicle.Jammed - addedWeapons - .map { _.obj } - .collect { - case jamItem: JammableUnit if jamItem.Jammed != vehicleJammered => - jamItem.Jammed = vehicleJammered - JammableMountedWeapons.JammedWeaponStatus(vehicle.Zone, jamItem, vehicleJammered) - } - changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory) - } + } - case VehicleAction.ChangeLoadout(target, oldWeapons, _, oldInventory, _) - if sessionLogic.general.accessedContainer.map(_.GUID).contains(target) => - //TODO when vehicle weapons can be changed without visual glitches, rewrite this - continent.GUID(target).collect { case vehicle: Vehicle => - //external participant: observe changes to equipment - (oldWeapons ++ oldInventory).foreach { case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0)) } - changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory) - } + case VehicleAction.VehicleState( + vehicleGuid, + unk1, + pos, + ang, + vel, + unk2, + unk3, + unk4, + wheelDirection, + unk5, + unk6 + ) if filter.isNotSameTarget => + //player who is watching the vehicle from the outside + sendResponse(VehicleStateMessage(vehicleGuid, unk1, pos, ang, vel, unk2, unk3, unk4, wheelDirection, unk5, unk6)) - case VehicleAction.ChangeLoadout(target, oldWeapons, _, oldInventory, _) => - //TODO when vehicle weapons can be changed without visual glitches, rewrite this - continent.GUID(target).collect { case vehicle: Vehicle => - changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory) - } + case VehicleAction.ChildObjectState(objectGuid, pitch, yaw) if filter.isNotSameTarget => + sendResponse(ChildObjectStateMessage(objectGuid, pitch, yaw)) - case VehicleSpawnPad.AttachToRails(vehicle, pad) => - sendResponse(ObjectAttachMessage(pad.GUID, vehicle.GUID, slot=3)) + case VehicleAction.FrameVehicleState(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA) + if filter.isNotSameTarget => + sendResponse(FrameVehicleStateMessage(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA)) - case VehicleSpawnPad.ConcealPlayer(playerGuid) => - sendResponse(GenericObjectActionMessage(playerGuid, code=9)) + case VehicleAction.DismountVehicle(bailType, wasKickedByDriver) if filter.isNotSameTarget => + sendResponse(DismountVehicleMsg(filter.resolvedPlayerGuid, bailType, wasKickedByDriver)) - case VehicleSpawnPad.DetachFromRails(vehicle, pad) => - val padDefinition = pad.Definition - sendResponse( - ObjectDetachMessage( - pad.GUID, - vehicle.GUID, - pad.Position + Vector3.z(padDefinition.VehicleCreationZOffset), - pad.Orientation.z + padDefinition.VehicleCreationZOrientOffset - ) + case VehicleAction.MountVehicle(vehicleGuid, seat) if filter.isNotSameTarget => + sendResponse(ObjectAttachMessage(vehicleGuid, filter.resolvedPlayerGuid, seat)) + + case VehicleAction.DeployRequest(objectGuid, state, unk1, unk2, pos) if filter.isNotSameTarget => + sendResponse(DeployRequestMessage(filter.resolvedPlayerGuid, objectGuid, state, unk1, unk2, pos)) + + case VehicleAction.EquipmentCreatedInSlot(pkt) if filter.isNotSameTarget => + sendResponse(pkt) + + case VehicleAction.InventoryState(obj, parentGuid, start, conData) if filter.isNotSameTarget => + //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? + val objGuid = obj.GUID + sendResponse(ObjectDeleteMessage(objGuid, unk1=0)) + sendResponse(ObjectCreateDetailedMessage( + obj.Definition.ObjectId, + objGuid, + ObjectCreateMessageParent(parentGuid, start), + conData + )) + + case VehicleAction.KickPassenger(_, wasKickedByDriver, vehicleGuid) if filter.isSameTarget /*resolvedPlayerGuid == guid*/ => + //seat number (first field) seems to be correct if passenger is kicked manually by driver + //but always seems to return 4 if user is kicked by mount permissions changing + sendResponse(DismountVehicleMsg(filter.resolvedPlayerGuid, BailType.Kicked, wasKickedByDriver)) + val typeOfRide = continent.GUID(vehicleGuid) match { + case Some(obj: Vehicle) => + sessionLogic.general.unaccessContainer(obj) + s"the ${obj.Definition.Name}'s seat by ${obj.OwnerName.getOrElse("the pilot")}" + case _ => + s"${player.Sex.possessive} ride" + } + log.info(s"${player.Name} has been kicked from $typeOfRide!") + player.WhichSide = OutsideOf + + case VehicleAction.KickPassenger(_, wasKickedByDriver, _) => + //seat number (first field) seems to be correct if passenger is kicked manually by driver + //but always seems to return 4 if user is kicked by mount permissions changing + sendResponse(DismountVehicleMsg(filter.resolvedPlayerGuid, BailType.Kicked, wasKickedByDriver)) + + case VehicleAction.InventoryState2(objGuid, parentGuid, value) if filter.isNotSameTarget => + sendResponse(InventoryStateMessage(objGuid, unk=0, parentGuid, value)) + + case VehicleAction.LoadVehicle(vehicle, vtype, vguid, vdata) if filter.isNotSameTarget => + //this is not be suitable for vehicles with people who are seated in it before it spawns (if that is possible) + sendResponse(ObjectCreateMessage(vtype, vguid, vdata)) + Vehicles.ReloadAccessPermissions(vehicle, player.Name) + + case VehicleAction.Ownership(vehicleGuid) if filter.isSameTarget /*resolvedPlayerGuid == guid*/ => + //Only the player that owns this vehicle needs the ownership packet + avatarActor ! AvatarActor.SetVehicle(Some(vehicleGuid)) + sendResponse(PlanetsideAttributeMessage(filter.resolvedPlayerGuid, attribute_type=21, vehicleGuid)) + + case VehicleAction.LoseOwnership(_, vehicleGuid) => + ops.announceAmsDecay(vehicleGuid,msg = "@ams_decaystarted") + + case VehicleAction.SeatPermissions(vehicleGuid, seatGroup, permission) if filter.isNotSameTarget => + sendResponse(PlanetsideAttributeMessage(vehicleGuid, seatGroup, permission)) + + case VehicleAction.StowCreatedEquipment(vehicleGuid, slot, itemType, itemGuid, itemData) if filter.isNotSameTarget => + //TODO prefer ObjectAttachMessage, but how to force ammo pools to update properly? + sendResponse(ObjectCreateDetailedMessage(itemType, itemGuid, ObjectCreateMessageParent(vehicleGuid, slot), itemData)) + + case VehicleAction.UnloadVehicle(_, vehicleGuid) => + sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=1)) + if (sessionLogic.zoning.spawn.prevSpawnPoint.map(_.Owner).exists { + case ams: Vehicle => + ams.GUID == vehicleGuid && + ams.OwnerGuid.isEmpty + case _ => + false + }) { + sessionLogic.zoning.spawn.prevSpawnPoint = None + sendResponse(ChatMsg(ChatMessageType.UNK_229, "@ams_decayed")) + } + + case VehicleAction.UnstowEquipment(itemGuid) if filter.isNotSameTarget => + //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? + sendResponse(ObjectDeleteMessage(itemGuid, unk1=0)) + + case VehicleAction.UpdateAmsSpawnList(list) => + sessionLogic.zoning.spawn.amsSpawnPoints = list.filter(tube => tube.Faction == player.Faction) + sessionLogic.zoning.spawn.DrawCurrentAmsSpawnPoint() + + case VehicleAction.TransferPassengerChannel(oldChannel, tempChannel, vehicle, vehicleToDelete) if filter.isNotSameTarget => + sessionLogic.zoning.interstellarFerry = Some(vehicle) + sessionLogic.zoning.interstellarFerryTopLevelGUID = Some(vehicleToDelete) + continent.VehicleEvents ! Service.Leave(oldChannel) //old vehicle-specific channel (was s"${vehicle.Actor}") + galaxyService ! Service.Join(tempChannel) //temporary vehicle-specific channel + log.debug(s"TransferPassengerChannel: ${player.Name} now subscribed to $tempChannel for vehicle gating") + + case VehicleAction.KickCargo(vehicle, speed, delay) + if player.VehicleSeated.nonEmpty && sessionLogic.zoning.spawn.deadState == DeadState.Alive && speed > 0 => + val strafe = 1 + Vehicles.CargoOrientation(vehicle) + val reverseSpeed = if (strafe > 1) { 0 } else { speed } + //strafe or reverse, not both + sessionLogic.vehicles.ServerVehicleOverrideWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=true, + lock_wheel=true, + reverse=true, + unk4=false, + lock_vthrust=0, + strafe, + reverseSpeed, + unk8=Some(0) ) + ) + import scala.concurrent.ExecutionContext.Implicits.global + import scala.concurrent.duration._ + val resp = GenericResponseEnvelope( + VehicleStamp, + "", + PlanetSideGUID(0), + VehicleAction.KickCargo(vehicle, speed=0, delay) + ) + context.system.scheduler.scheduleOnce(delay milliseconds, context.self, resp) - case VehicleSpawnPad.ResetSpawnPad(pad) => - sendResponse(GenericObjectActionMessage(pad.GUID, code=23)) + case VehicleAction.KickCargo(cargo, _, _) + if player.VehicleSeated.nonEmpty && sessionLogic.zoning.spawn.deadState == DeadState.Alive => + sessionLogic.vehicles.TotalDriverVehicleControl(cargo) - case VehicleSpawnPad.RevealPlayer(playerGuid) => - sendResponse(GenericObjectActionMessage(playerGuid, code=10)) - - case VehicleSpawnPad.StartPlayerSeatedInVehicle(vehicle, _) - if player.VisibleSlots.contains(player.DrawnSlot) => - player.DrawnSlot = Player.HandsDownSlot - startPlayerSeatedInVehicle(vehicle) - - case VehicleSpawnPad.StartPlayerSeatedInVehicle(vehicle, _) => - startPlayerSeatedInVehicle(vehicle) - - case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, _) => - Vehicles.ReloadAccessPermissions(vehicle, player.Name) - sessionLogic.vehicles.ServerVehicleOverrideWithPacket( - vehicle, - ServerVehicleOverrideMsg( - lock_accelerator=true, - lock_wheel=true, - reverse=true, - unk4=false, - lock_vthrust=1, - lock_strafe=0, - movement_speed=0, - unk8=Some(0) - ) - ) - sessionLogic.vehicles.serverVehicleControlVelocity = Some(0) - - case VehicleSpawnPad.ServerVehicleOverrideStart(vehicle, _) => - val vdef = vehicle.Definition - sessionLogic.vehicles.ServerVehicleOverrideWithPacket( - vehicle, - ServerVehicleOverrideMsg( - lock_accelerator=true, - lock_wheel=true, - reverse=false, - unk4=false, - lock_vthrust=if (GlobalDefinitions.isFlightVehicle(vdef)) { 1 } else { 0 }, - lock_strafe=0, - movement_speed=vdef.AutoPilotSpeed1, - unk8=Some(0) - ) - ) - - case VehicleSpawnPad.ServerVehicleOverrideEnd(vehicle, _) => - sessionLogic.vehicles.ServerVehicleOverrideStop(vehicle) - - case VehicleSpawnPad.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) => - val str = s"${data.getOrElse("The vehicle spawn pad where you placed your order is blocked.")}" - val msg = if (str.contains("@")) { - ChatMsg(ChatMessageType.UNK_229, str) - } else { - ChatMsg(ChatMessageType.CMT_OPEN, wideContents = true, recipient = "", str, note = None) + case VehicleAction.ChangeLoadout(target, oldWeapons, addedWeapons, oldInventory, newInventory) + if player.avatar.vehicle.contains(target) => + //TODO when vehicle weapons can be changed without visual glitches, rewrite this + continent.GUID(target).collect { case vehicle: Vehicle => + import net.psforever.login.WorldSession.boolToInt + //owner: must unregister old equipment, and register and install new equipment + (oldWeapons ++ oldInventory).foreach { + case (obj, eguid) => + sendResponse(ObjectDeleteMessage(eguid, unk1=0)) + TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) } - sendResponse(msg) + sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, vehicle, addedWeapons ++ newInventory) + //jammer or unjamm new weapons based on vehicle status + val vehicleJammered = vehicle.Jammed + addedWeapons + .map { _.obj } + .collect { + case jamItem: JammableUnit if jamItem.Jammed != vehicleJammered => + jamItem.Jammed = vehicleJammered + JammableMountedWeapons.JammedWeaponStatus(vehicle.Zone, jamItem, vehicleJammered) + } + changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory) + } - case VehicleSpawnPad.PeriodicReminder(_, data) => - val (isType, flag, msg): (ChatMessageType, Boolean, String) = data match { - case Some(msg: String) if msg.startsWith("@") => (ChatMessageType.UNK_227, false, msg) - case Some(msg: String) => (ChatMessageType.CMT_OPEN, true, msg) - case _ => (ChatMessageType.CMT_OPEN, true, "Your vehicle order has been cancelled.") - } - sendResponse(ChatMsg(isType, flag, recipient="", msg, None)) + case VehicleAction.ChangeLoadout(target, oldWeapons, _, oldInventory, _) + if sessionLogic.general.accessedContainer.map(_.GUID).contains(target) => + //TODO when vehicle weapons can be changed without visual glitches, rewrite this + continent.GUID(target).collect { case vehicle: Vehicle => + //external participant: observe changes to equipment + (oldWeapons ++ oldInventory).foreach { case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0)) } + changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory) + } - case _ => () - } + case VehicleAction.ChangeLoadout(target, oldWeapons, _, oldInventory, _) => + //TODO when vehicle weapons can be changed without visual glitches, rewrite this + continent.GUID(target).collect { case vehicle: Vehicle => + changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory) + } + + case VehicleSpawnPad.AttachToRails(vehicle, pad) => + sendResponse(ObjectAttachMessage(pad.GUID, vehicle.GUID, slot=3)) + + case VehicleSpawnPad.ConcealPlayer(playerGuid) => + sendResponse(GenericObjectActionMessage(playerGuid, code=9)) + + case VehicleSpawnPad.DetachFromRails(vehicle, pad) => + val padDefinition = pad.Definition + sendResponse( + ObjectDetachMessage( + pad.GUID, + vehicle.GUID, + pad.Position + Vector3.z(padDefinition.VehicleCreationZOffset), + pad.Orientation.z + padDefinition.VehicleCreationZOrientOffset + ) + ) + + case VehicleSpawnPad.ResetSpawnPad(pad) => + sendResponse(GenericObjectActionMessage(pad.GUID, code=23)) + + case VehicleSpawnPad.RevealPlayer(playerGuid) => + sendResponse(GenericObjectActionMessage(playerGuid, code=10)) + + case VehicleSpawnPad.StartPlayerSeatedInVehicle(vehicle, _) + if player.VisibleSlots.contains(player.DrawnSlot) => + player.DrawnSlot = Player.HandsDownSlot + startPlayerSeatedInVehicle(vehicle) + + case VehicleSpawnPad.StartPlayerSeatedInVehicle(vehicle, _) => + startPlayerSeatedInVehicle(vehicle) + + case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, _) => + Vehicles.ReloadAccessPermissions(vehicle, player.Name) + sessionLogic.vehicles.ServerVehicleOverrideWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=true, + lock_wheel=true, + reverse=true, + unk4=false, + lock_vthrust=1, + lock_strafe=0, + movement_speed=0, + unk8=Some(0) + ) + ) + sessionLogic.vehicles.serverVehicleControlVelocity = Some(0) + + case VehicleSpawnPad.ServerVehicleOverrideStart(vehicle, _) => + val vdef = vehicle.Definition + sessionLogic.vehicles.ServerVehicleOverrideWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=true, + lock_wheel=true, + reverse=false, + unk4=false, + lock_vthrust=if (GlobalDefinitions.isFlightVehicle(vdef)) { 1 } else { 0 }, + lock_strafe=0, + movement_speed=vdef.AutoPilotSpeed1, + unk8=Some(0) + ) + ) + + case VehicleSpawnPad.ServerVehicleOverrideEnd(vehicle, _) => + sessionLogic.vehicles.ServerVehicleOverrideStop(vehicle) + + case VehicleSpawnPad.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) => + val str = s"${data.getOrElse("The vehicle spawn pad where you placed your order is blocked.")}" + val msg = if (str.contains("@")) { + ChatMsg(ChatMessageType.UNK_229, str) + } else { + ChatMsg(ChatMessageType.CMT_OPEN, wideContents = true, recipient = "", str, note = None) + } + sendResponse(msg) + + case VehicleSpawnPad.PeriodicReminder(_, data) => + val (isType, flag, msg): (ChatMessageType, Boolean, String) = data match { + case Some(msg: String) if msg.startsWith("@") => (ChatMessageType.UNK_227, false, msg) + case Some(msg: String) => (ChatMessageType.CMT_OPEN, true, msg) + case _ => (ChatMessageType.CMT_OPEN, true, "Your vehicle order has been cancelled.") + } + sendResponse(ChatMsg(isType, flag, recipient="", msg, None)) } private def changeLoadoutDeleteOldEquipment( diff --git a/src/main/scala/net/psforever/actors/session/spectator/AvatarHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/AvatarHandlerLogic.scala index 6e4b94518..d072f0069 100644 --- a/src/main/scala/net/psforever/actors/session/spectator/AvatarHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/spectator/AvatarHandlerLogic.scala @@ -1,6 +1,7 @@ // Copyright (c) 2024 PSForever package net.psforever.actors.session.spectator +import akka.actor.Actor.Receive import akka.actor.{ActorContext, typed} import net.psforever.actors.session.support.AvatarHandlerFunctions import net.psforever.actors.zone.ZoneActor @@ -9,7 +10,7 @@ import net.psforever.objects.avatar.scoring.Kill import net.psforever.objects.sourcing.PlayerSource import net.psforever.packet.game.{AvatarImplantMessage, ImplantAction} import net.psforever.services.base.envelope.MessageEnvelope -import net.psforever.services.base.message.{ChangeAmmo, ChangeFireState_Start, ChangeFireState_Stop, ConcealPlayer, EventResponse, GenericObjectAction, HintsAtAttacker, ObjectDelete, PlanetsideAttribute, ReloadTool, SendResponse, SetEmpire, WeaponDryFire} +import net.psforever.services.base.message.{ChangeAmmo, ChangeFireState_Start, ChangeFireState_Stop, PlanetsideAttribute, ReloadTool, SendResponse, WeaponDryFire} import scala.concurrent.duration._ // @@ -24,9 +25,8 @@ import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal} import net.psforever.objects.vital.etc.ExplodingEntityReason import net.psforever.objects.zones.Zoning import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent -import net.psforever.packet.game.{ArmorChangedMessage, AvatarDeadStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, DeadState, DestroyMessage, DrowningTarget, GenericActionMessage, GenericObjectActionMessage, HitHint, ItemTransactionResultMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectHeldMessage, OxygenStateMessage, PlanetsideAttributeMessage, PlayerStateMessage, ProjectileStateMessage, ReloadMessage, SetEmpireMessage, UseItemMessage, WeaponDryFireMessage} +import net.psforever.packet.game.{ArmorChangedMessage, AvatarDeadStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, DeadState, DestroyMessage, DrowningTarget, GenericActionMessage, GenericObjectActionMessage, ItemTransactionResultMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectHeldMessage, OxygenStateMessage, PlanetsideAttributeMessage, PlayerStateMessage, ProjectileStateMessage, ReloadMessage, UseItemMessage, WeaponDryFireMessage} import net.psforever.services.avatar.AvatarAction -import net.psforever.services.Service import net.psforever.types.{ChatMessageType, PlanetSideGUID, TransactionType, Vector3} import net.psforever.util.Config @@ -41,539 +41,491 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor - /** - * na - * @param toChannel na - * @param guid na - * @param reply na - */ - def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit = { - val resolvedPlayerGuid = if (player != null && player.HasGUID) { - player.GUID - } else { - Service.defaultPlayerGUID - } - val isNotSameTarget = resolvedPlayerGuid != guid - val isSameTarget = !isNotSameTarget - reply match { - /* special messages */ - case AvatarAction.TeardownConnection() => - log.trace(s"ending ${player.Name}'s old session by event system request (relog)") - context.stop(context.self) + def receive: Receive = { + /* special messages */ + case AvatarAction.TeardownConnection => + log.trace(s"ending ${player.Name}'s old session by event system request (relog)") + context.stop(context.self) - /* really common messages (very frequently, every life) */ - case pstate @ AvatarAction.PlayerState( - pos, - vel, - yaw, - pitch, - yawUpper, - _, - isCrouching, - isJumping, - jumpThrust, - isCloaking, - isNotRendered, - canSeeReallyFar - ) if isNotSameTarget => - val pstateToSave = pstate.copy(timestamp = 0) - val (lastMsg, lastTime, lastPosition, wasVisible, wasShooting) = ops.lastSeenStreamMessage.get(guid.guid) match { - case Some(SessionAvatarHandlers.LastUpstream(Some(msg), visible, shooting, time)) => (Some(msg), time, msg.pos, visible, shooting) - case _ => (None, 0L, Vector3.Zero, false, None) - } - val drawConfig = Config.app.game.playerDraw //m - val maxRange = drawConfig.rangeMax * drawConfig.rangeMax //sq.m - val ourPosition = player.Position //xyz - val currentDistance = Vector3.DistanceSquared(ourPosition, pos) //sq.m - val inDrawableRange = currentDistance <= maxRange - val now = System.currentTimeMillis() //ms - if ( - sessionLogic.zoning.zoningStatus != Zoning.Status.Deconstructing && - !isNotRendered && inDrawableRange + /* really common messages (very frequently, every life) */ + case pstate @ AvatarAction.PlayerState( + pos, + vel, + yaw, + pitch, + yawUpper, + _, + isCrouching, + isJumping, + jumpThrust, + isCloaking, + isNotRendered, + canSeeReallyFar + ) if filter.isNotSameTarget => + val pstateToSave = pstate.copy(timestamp = 0) + val (lastMsg, lastTime, lastPosition, wasVisible, wasShooting) = ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid) match { + case Some(SessionAvatarHandlers.LastUpstream(Some(msg), visible, shooting, time)) => (Some(msg), time, msg.pos, visible, shooting) + case _ => (None, 0L, Vector3.Zero, false, None) + } + val drawConfig = Config.app.game.playerDraw //m + val maxRange = drawConfig.rangeMax * drawConfig.rangeMax //sq.m + val ourPosition = player.Position //xyz + val currentDistance = Vector3.DistanceSquared(ourPosition, pos) //sq.m + val inDrawableRange = currentDistance <= maxRange + val now = System.currentTimeMillis() //ms + if ( + sessionLogic.zoning.zoningStatus != Zoning.Status.Deconstructing && + !isNotRendered && inDrawableRange + ) { + //conditions where visibility is assured + val durationSince = now - lastTime //ms + lazy val previouslyInDrawableRange = Vector3.DistanceSquared(ourPosition, lastPosition) <= maxRange + lazy val targetDelay = { + val populationOver = math.max( + 0, + sessionLogic.localSector.livePlayerList.size - drawConfig.populationThreshold + ) + val distanceAdjustment = math.pow(populationOver / drawConfig.populationStep * drawConfig.rangeStep, 2) //sq.m + val adjustedDistance = currentDistance + distanceAdjustment //sq.m + drawConfig.ranges.lastIndexWhere { dist => adjustedDistance > dist * dist } match { + case -1 => 1 + case index => drawConfig.delays(index) + } + } //ms + if (!wasVisible || + !previouslyInDrawableRange || + durationSince > drawConfig.delayMax || + (!lastMsg.contains(pstateToSave) && + (canSeeReallyFar || + currentDistance < drawConfig.rangeMin * drawConfig.rangeMin || + sessionLogic.general.canSeeReallyFar || + durationSince > targetDelay + ) + ) ) { - //conditions where visibility is assured - val durationSince = now - lastTime //ms - lazy val previouslyInDrawableRange = Vector3.DistanceSquared(ourPosition, lastPosition) <= maxRange - lazy val targetDelay = { - val populationOver = math.max( - 0, - sessionLogic.localSector.livePlayerList.size - drawConfig.populationThreshold + //must draw + sendResponse( + PlayerStateMessage( + filter.resolvedPlayerGuid, + pos, + vel, + yaw, + pitch, + yawUpper, + timestamp = 0, //is this okay? + isCrouching, + isJumping, + jumpThrust, + isCloaking ) - val distanceAdjustment = math.pow(populationOver / drawConfig.populationStep * drawConfig.rangeStep, 2) //sq.m - val adjustedDistance = currentDistance + distanceAdjustment //sq.m - drawConfig.ranges.lastIndexWhere { dist => adjustedDistance > dist * dist } match { - case -1 => 1 - case index => drawConfig.delays(index) - } - } //ms - if (!wasVisible || - !previouslyInDrawableRange || - durationSince > drawConfig.delayMax || - (!lastMsg.contains(pstateToSave) && - (canSeeReallyFar || - currentDistance < drawConfig.rangeMin * drawConfig.rangeMin || - sessionLogic.general.canSeeReallyFar || - durationSince > targetDelay - ) - ) - ) { - //must draw - sendResponse( - PlayerStateMessage( - guid, - pos, - vel, - yaw, - pitch, - yawUpper, - timestamp = 0, //is this okay? - isCrouching, - isJumping, - jumpThrust, - isCloaking - ) - ) - ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, now)) - } else { - //is visible, but skip reinforcement - ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, lastTime)) - } + ) + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, now)) } else { - //conditions where the target is not currently visible - if (wasVisible) { - //the target was JUST PREVIOUSLY visible; one last draw to move target beyond a renderable distance - val lat = (1 + ops.hidingPlayerRandomizer.nextInt(continent.map.scale.height.toInt)).toFloat - sendResponse( - PlayerStateMessage( - guid, - Vector3(1f, lat, 1f), - vel=None, - facingYaw=0f, - facingPitch=0f, - facingYawUpper=0f, - timestamp=0, //is this okay? - is_cloaked = isCloaking - ) + //is visible, but skip reinforcement + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, lastTime)) + } + } else { + //conditions where the target is not currently visible + if (wasVisible) { + //the target was JUST PREVIOUSLY visible; one last draw to move target beyond a renderable distance + val lat = (1 + ops.hidingPlayerRandomizer.nextInt(continent.map.scale.height.toInt)).toFloat + sendResponse( + PlayerStateMessage( + filter.resolvedPlayerGuid, + Vector3(1f, lat, 1f), + vel=None, + facingYaw=0f, + facingPitch=0f, + facingYawUpper=0f, + timestamp=0, //is this okay? + is_cloaked = isCloaking ) - ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, now)) - } else { - //skip drawing altogether - ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, lastTime)) - } + ) + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, now)) + } else { + //skip drawing altogether + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, lastTime)) } + } - case AvatarAction.ObjectHeld(slot, _) - if isSameTarget && player.VisibleSlots.contains(slot) => - sendResponse(ObjectHeldMessage(guid, slot, unk1=true)) - //Stop using proximity terminals if player unholsters a weapon - continent.GUID(sessionLogic.terminals.usingMedicalTerminal).collect { - case term: Terminal with ProximityUnit => sessionLogic.terminals.StopUsingProximityUnit(term) - } - if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) { - sessionLogic.zoning.spawn.stopDeconstructing() - } + case AvatarAction.ObjectHeld(slot, _) + if filter.isSameTarget && player.VisibleSlots.contains(slot) => + sendResponse(ObjectHeldMessage(filter.resolvedPlayerGuid, slot, unk1=true)) + //Stop using proximity terminals if player unholsters a weapon + continent.GUID(sessionLogic.terminals.usingMedicalTerminal).collect { + case term: Terminal with ProximityUnit => sessionLogic.terminals.StopUsingProximityUnit(term) + } + if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) { + sessionLogic.zoning.spawn.stopDeconstructing() + } - case AvatarAction.ObjectHeld(slot, _) - if isSameTarget && slot > -1 => - sendResponse(ObjectHeldMessage(guid, slot, unk1=true)) + case AvatarAction.ObjectHeld(slot, _) + if filter.isSameTarget && slot > -1 => + sendResponse(ObjectHeldMessage(filter.resolvedPlayerGuid, slot, unk1=true)) - case AvatarAction.ObjectHeld(_, _) - if isSameTarget => () + case AvatarAction.ObjectHeld(_, _) + if filter.isSameTarget => () - case AvatarAction.ObjectHeld(_, previousSlot) => - sendResponse(ObjectHeldMessage(guid, previousSlot, unk1=false)) + case AvatarAction.ObjectHeld(_, previousSlot) => + sendResponse(ObjectHeldMessage(filter.resolvedPlayerGuid, previousSlot, unk1=false)) - case ChangeFireState_Start(weaponGuid) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } => - sendResponse(ChangeFireStateMessage_Start(weaponGuid)) - val entry = ops.lastSeenStreamMessage(guid.guid) - ops.lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = Some(weaponGuid))) + case ChangeFireState_Start(weaponGuid) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { _.visible } => + sendResponse(ChangeFireStateMessage_Start(weaponGuid)) + val entry = ops.lastSeenStreamMessage(filter.resolvedPlayerGuid.guid) + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, entry.copy(shooting = Some(weaponGuid))) - case ChangeFireState_Start(weaponGuid) - if isNotSameTarget => - sendResponse(ChangeFireStateMessage_Start(weaponGuid)) + case ChangeFireState_Stop(weaponGuid) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { msg => msg.visible || msg.shooting.nonEmpty } => + sendResponse(ChangeFireStateMessage_Stop(weaponGuid)) + val entry = ops.lastSeenStreamMessage(filter.resolvedPlayerGuid.guid) + ops.lastSeenStreamMessage.put(filter.resolvedPlayerGuid.guid, entry.copy(shooting = None)) - case ChangeFireState_Stop(weaponGuid) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { msg => msg.visible || msg.shooting.nonEmpty } => - sendResponse(ChangeFireStateMessage_Stop(weaponGuid)) - val entry = ops.lastSeenStreamMessage(guid.guid) - ops.lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = None)) + case AvatarAction.LoadCreatedPlayer(pkt) if filter.isNotSameTarget => + sendResponse(pkt) - case ChangeFireState_Stop(weaponGuid) - if isNotSameTarget => - sendResponse(ChangeFireStateMessage_Stop(weaponGuid)) + case AvatarAction.EquipmentCreatedInHand(pkt) if filter.isNotSameTarget => + sendResponse(pkt) - case AvatarAction.LoadCreatedPlayer(pkt) if isNotSameTarget => - sendResponse(pkt) + case AvatarAction.Destroy(victim, killer, weapon, pos) => + // guid = victim // killer = killer + sendResponse(DestroyMessage(victim, killer, weapon, pos)) - case AvatarAction.EquipmentCreatedInHand(pkt) if isNotSameTarget => - sendResponse(pkt) + case AvatarAction.DestroyDisplay(killer, victim, method, unk) => + sendResponse(ops.destroyDisplayMessage(killer, victim, method, unk)) - case PlanetsideAttribute(target_guid, attributeType, attributeValue) if isNotSameTarget => - sendResponse(PlanetsideAttributeMessage(target_guid, attributeType, attributeValue)) + case AvatarAction.TerminalOrderResult(terminalGuid, action, result) + if result && (action == TransactionType.Buy || action == TransactionType.Loadout) => + sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) + sessionLogic.terminals.lastTerminalOrderFulfillment = true + AvatarActor.savePlayerData(player) - case GenericObjectAction(objectGuid, actionCode) if isNotSameTarget => - sendResponse(GenericObjectActionMessage(objectGuid, actionCode)) + case AvatarAction.TerminalOrderResult(terminalGuid, action, result) => + sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) + sessionLogic.terminals.lastTerminalOrderFulfillment = true - case HintsAtAttacker(sourceGuid) if player.isAlive => - sendResponse(HitHint(sourceGuid, guid)) - sessionLogic.zoning.CancelZoningProcess() - - case AvatarAction.Destroy(victim, killer, weapon, pos) => - // guid = victim // killer = killer - sendResponse(DestroyMessage(victim, killer, weapon, pos)) - - case AvatarAction.DestroyDisplay(killer, victim, method, unk) => - sendResponse(ops.destroyDisplayMessage(killer, victim, method, unk)) - - case AvatarAction.TerminalOrderResult(terminalGuid, action, result) - if result && (action == TransactionType.Buy || action == TransactionType.Loadout) => - sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) - sessionLogic.terminals.lastTerminalOrderFulfillment = true - AvatarActor.savePlayerData(player) - - case AvatarAction.TerminalOrderResult(terminalGuid, action, result) => - sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) - sessionLogic.terminals.lastTerminalOrderFulfillment = true - - case AvatarAction.ChangeExosuit( - target, - armor, - exosuit, - subtype, - _, - maxhand, - oldHolsters, - holsters, - oldInventory, - inventory, - drop, - delete - ) if resolvedPlayerGuid == target => - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) - //happening to this player - //cleanup - sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=false)) - (oldHolsters ++ oldInventory ++ delete).foreach { - case (_, dguid) => sendResponse(ObjectDeleteMessage(dguid, unk1=0)) - } - //functionally delete - delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) } - //redraw - if (maxhand) { - TaskWorkflow.execute(HoldNewEquipmentUp(player)( - Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), - 0 - )) - } - //draw free hand - player.FreeHand.Equipment.foreach { obj => + case AvatarAction.ChangeExosuit( + target, + armor, + exosuit, + subtype, + _, + maxhand, + oldHolsters, + holsters, + oldInventory, + inventory, + drop, + delete + ) if filter.resolvedPlayerGuid == target => + sendResponse(ArmorChangedMessage(target, exosuit, subtype)) + sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) + //happening to this player + //cleanup + sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=false)) + (oldHolsters ++ oldInventory ++ delete).foreach { + case (_, dguid) => sendResponse(ObjectDeleteMessage(dguid, unk1=0)) + } + //functionally delete + delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) } + //redraw + if (maxhand) { + TaskWorkflow.execute(HoldNewEquipmentUp(player)( + Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), + 0 + )) + } + //draw free hand + player.FreeHand.Equipment.foreach { obj => + val definition = obj.Definition + sendResponse( + ObjectCreateDetailedMessage( + definition.ObjectId, + obj.GUID, + ObjectCreateMessageParent(target, Player.FreeHandSlot), + definition.Packet.DetailedConstructorData(obj).get + ) + ) + } + //draw holsters and inventory + (holsters ++ inventory).foreach { + case InventoryItem(obj, index) => val definition = obj.Definition sendResponse( ObjectCreateDetailedMessage( definition.ObjectId, obj.GUID, - ObjectCreateMessageParent(target, Player.FreeHandSlot), + ObjectCreateMessageParent(target, index), definition.Packet.DetailedConstructorData(obj).get ) ) - } - //draw holsters and inventory - (holsters ++ inventory).foreach { - case InventoryItem(obj, index) => - val definition = obj.Definition - sendResponse( - ObjectCreateDetailedMessage( - definition.ObjectId, - obj.GUID, - ObjectCreateMessageParent(target, index), - definition.Packet.DetailedConstructorData(obj).get - ) + } + DropLeftovers(player)(drop) + + case AvatarAction.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, _, delete) => + sendResponse(ArmorChangedMessage(target, exosuit, subtype)) + sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) + //happening to some other player + sendResponse(ObjectHeldMessage(target, slot, unk1 = false)) + //cleanup + (oldHolsters ++ delete).foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } + //draw holsters + holsters.foreach { + case InventoryItem(obj, index) => + val definition = obj.Definition + sendResponse( + ObjectCreateMessage( + definition.ObjectId, + obj.GUID, + ObjectCreateMessageParent(target, index), + definition.Packet.ConstructorData(obj).get ) - } - DropLeftovers(player)(drop) - - case AvatarAction.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, _, delete) => - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) - //happening to some other player - sendResponse(ObjectHeldMessage(target, slot, unk1 = false)) - //cleanup - (oldHolsters ++ delete).foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } - //draw holsters - holsters.foreach { - case InventoryItem(obj, index) => - val definition = obj.Definition - sendResponse( - ObjectCreateMessage( - definition.ObjectId, - obj.GUID, - ObjectCreateMessageParent(target, index), - definition.Packet.ConstructorData(obj).get - ) - ) - } - - case AvatarAction.ChangeLoadout( - target, - armor, - exosuit, - subtype, - _, - maxhand, - oldHolsters, - holsters, - oldInventory, - inventory, - drops - ) if resolvedPlayerGuid == target => - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, attribute_type = 4, armor)) - //happening to this player - sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=true)) - //cleanup - (oldHolsters ++ oldInventory).foreach { - case (obj, objGuid) => - sendResponse(ObjectDeleteMessage(objGuid, unk1=0)) - TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) - } - drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0))) - //redraw - if (maxhand) { - TaskWorkflow.execute(HoldNewEquipmentUp(player)( - Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), - slot = 0 - )) - } - sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory) - DropLeftovers(player)(drops) - - case AvatarAction.ChangeLoadout(target, armor, exosuit, subtype, slot, _, oldHolsters, _, _, _, _) => - //redraw handled by callbacks - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) - //happening to some other player - sendResponse(ObjectHeldMessage(target, slot, unk1=false)) - //cleanup - oldHolsters.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } - - case AvatarAction.UseKit(kguid, kObjId) => - sendResponse( - UseItemMessage( - resolvedPlayerGuid, - kguid, - resolvedPlayerGuid, - unk2 = 4294967295L, - unk3 = false, - unk4 = Vector3.Zero, - unk5 = Vector3.Zero, - unk6 = 126, - unk7 = 0, //sequence time? - unk8 = 137, - kObjId ) - ) - sendResponse(ObjectDeleteMessage(kguid, unk1=0)) + } - case AvatarAction.KitNotUsed(_, "") => - sessionLogic.general.kitToBeUsed = None - - case AvatarAction.KitNotUsed(_, msg) => - sessionLogic.general.kitToBeUsed = None - sendResponse(ChatMsg(ChatMessageType.UNK_225, msg)) - - case AvatarAction.UpdateKillsDeathsAssists(_, kda: Kill) if kda.experienceEarned > 0 => - continent.actor ! ZoneActor.RewardOurSupporters( - PlayerSource(player), - Players.produceContributionTranscriptFromKill(continent, player, kda), - kda, - kda.experienceEarned - ) - - case AvatarAction.AwardBep(charId, bep, expType) => - //if the target player, always award (some) BEP - if (charId == player.CharId) { - avatarActor ! AvatarActor.AwardBep(bep, expType) - } - - case AvatarAction.AwardCep(charId, cep) => - //if the target player, always award (some) CEP - if (charId == player.CharId) { - avatarActor ! AvatarActor.AwardCep(cep) - } - - case AvatarAction.FacilityCaptureRewards(buildingId, zoneNumber, cep) => - ops.facilityCaptureRewards(buildingId, zoneNumber, cep) - - case SendResponse(msgs) => - msgs.foreach { - case pkt: AvatarImplantMessage - if pkt.player_guid == player.GUID && pkt.action == ImplantAction.Initialization => - //special spectator implants stay initialized and do not deinitialize - () - case msg => - sendResponse(msg) - } - - /* common messages (maybe once every respawn) */ - case ReloadTool(itemGuid) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } => - sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0)) - - case AvatarAction.Killed(_, mount) => - //log and chat messages - val cause = player.LastDamage.flatMap { damage => - val interaction = damage.interaction - val reason = interaction.cause - val adversarial = interaction.adversarial.map { _.attacker } - reason match { - case r: ExplodingEntityReason if r.entity.isInstanceOf[VehicleSpawnPad] => - //also, @SVCP_Killed_TooCloseToPadOnCreate^n~ or "... within n meters of pad ..." - sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SVCP_Killed_OnPadOnCreate")) - case _ => () - } - adversarial.map {_.Name }.orElse { Some(s"a ${reason.getClass.getSimpleName}") } - }.getOrElse { s"an unfortunate circumstance (probably ${player.Sex.pronounObject} own fault)" } - log.info(s"${player.Name} has died, killed by $cause") - if (sessionLogic.shooting.shotsWhileDead > 0) { - log.warn( - s"SHOTS_WHILE_DEAD: client of ${avatar.name} fired ${sessionLogic.shooting.shotsWhileDead} rounds while character was dead on server" - ) - sessionLogic.shooting.shotsWhileDead = 0 - } - sessionLogic.zoning.CancelZoningProcess() - - //player state changes - sessionLogic.zoning.spawn.avatarActive = false - AvatarActor.updateToolDischargeFor(avatar) - player.FreeHand.Equipment.foreach { item => - DropEquipmentFromInventory(player)(item) - } - sessionLogic.general.dropSpecialSlotItem() - sessionLogic.general.toggleMaxSpecialState(enable = false) - sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive - sessionLogic.zoning.zoningStatus = Zoning.Status.None - sessionLogic.zoning.spawn.deadState = DeadState.Dead - continent.GUID(mount).collect { case obj: Vehicle => - sessionLogic.vehicles.ConditionalDriverVehicleControl(obj) - sessionLogic.general.unaccessContainer(obj) - } - sessionLogic.actionsToCancel() - sessionLogic.terminals.CancelAllProximityUnits() - AvatarActor.savePlayerLocation(player) - sessionLogic.zoning.spawn.ShiftPosition = Some(player.Position) - - //respawn - sessionLogic.zoning.spawn.reviveTimer.cancel() - if (player.death_by == 0) { - sessionLogic.zoning.spawn.randomRespawn(300.seconds) - } else { - sessionLogic.zoning.spawn.HandleReleaseAvatar(player, continent) - } - - case AvatarAction.ReleasePlayer(tplayer) if isNotSameTarget => - sessionLogic.zoning.spawn.DepictPlayerAsCorpse(tplayer) - - case AvatarAction.Revive(revivalTargetGuid) if resolvedPlayerGuid == revivalTargetGuid => - log.info(s"No time for rest, ${player.Name}. Back on your feet!") - sessionLogic.zoning.spawn.reviveTimer.cancel() - sessionLogic.zoning.spawn.deadState = DeadState.Alive - player.Revive - val health = player.Health - sendResponse(PlanetsideAttributeMessage(revivalTargetGuid, attribute_type=0, health)) - sendResponse(AvatarDeadStateMessage(DeadState.Alive, timer_max=0, timer=0, player.Position, player.Faction, unk5=true)) - continent.AvatarEvents ! MessageEnvelope( - continent.id, - PlanetsideAttribute(revivalTargetGuid, attribute_type=0, health) - ) - - /* uncommon messages (utility, or once in a while) */ - case ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } => - ops.changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data) - sendResponse(ChangeAmmoMessage(weapon_guid, 1)) - - case ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) - if isNotSameTarget => - ops.changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data) - - case AvatarAction.ChangeFireMode(itemGuid, mode) if isNotSameTarget => - sendResponse(ChangeFireModeMessage(itemGuid, mode)) - - case ConcealPlayer(_) => - sendResponse(GenericObjectActionMessage(guid, code=9)) - - case AvatarAction.EnvironmentalDamage(_, _, _) => - //TODO damage marker? - sessionLogic.zoning.CancelZoningProcess() - - case AvatarAction.DropCreatedItem(pkt) if isNotSameTarget => - sendResponse(pkt) - - case ObjectDelete(itemGuid, unk) if isNotSameTarget => - sendResponse(ObjectDeleteMessage(itemGuid, unk)) - - /* rare messages */ - case SetEmpire(objectGuid, faction) if isNotSameTarget => - sendResponse(SetEmpireMessage(objectGuid, faction)) - - case AvatarAction.DropSpecialItem() => - sessionLogic.general.dropSpecialSlotItem() - - case AvatarAction.OxygenState(player, vehicle) => - sendResponse(OxygenStateMessage( - DrowningTarget(player.guid, player.progress, player.state), - vehicle.flatMap { vinfo => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state)) } + case AvatarAction.ChangeLoadout( + target, + armor, + exosuit, + subtype, + _, + maxhand, + oldHolsters, + holsters, + oldInventory, + inventory, + drops + ) if filter.resolvedPlayerGuid == target => + sendResponse(ArmorChangedMessage(target, exosuit, subtype)) + sendResponse(PlanetsideAttributeMessage(target, attribute_type = 4, armor)) + //happening to this player + sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=true)) + //cleanup + (oldHolsters ++ oldInventory).foreach { + case (obj, objGuid) => + sendResponse(ObjectDeleteMessage(objGuid, unk1=0)) + TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) + } + drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0))) + //redraw + if (maxhand) { + TaskWorkflow.execute(HoldNewEquipmentUp(player)( + Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), + slot = 0 )) + } + sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory) + DropLeftovers(player)(drops) - case AvatarAction.LoadCreatedProjectile(pkt) if isNotSameTarget => - sendResponse(pkt) + case AvatarAction.ChangeLoadout(target, armor, exosuit, subtype, slot, _, oldHolsters, _, _, _, _) => + //redraw handled by callbacks + sendResponse(ArmorChangedMessage(target, exosuit, subtype)) + sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) + //happening to some other player + sendResponse(ObjectHeldMessage(target, slot, unk1=false)) + //cleanup + oldHolsters.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } - case AvatarAction.ProjectileState(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid) if isNotSameTarget => - sendResponse(ProjectileStateMessage(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid)) - - case AvatarAction.ProjectileExplodes(projectileGuid, projectile) => - sendResponse( - ProjectileStateMessage( - projectileGuid, - projectile.Position, - shot_vel = Vector3.Zero, - projectile.Orientation, - sequence_num=0, - end=true, - hit_target_guid=PlanetSideGUID(0) - ) + case AvatarAction.UseKit(kguid, kObjId) => + sendResponse( + UseItemMessage( + filter.resolvedPlayerGuid, + kguid, + filter.resolvedPlayerGuid, + unk2 = 4294967295L, + unk3 = false, + unk4 = Vector3.Zero, + unk5 = Vector3.Zero, + unk6 = 126, + unk7 = 0, //sequence time? + unk8 = 137, + kObjId ) - sendResponse(ObjectDeleteMessage(projectileGuid, unk1=2)) + ) + sendResponse(ObjectDeleteMessage(kguid, unk1=0)) - case AvatarAction.ProjectileAutoLockAwareness(mode) => - sendResponse(GenericActionMessage(mode)) + case AvatarAction.KitNotUsed(_, "") => + sessionLogic.general.kitToBeUsed = None - case AvatarAction.PutDownFDU(target) if isNotSameTarget => - sendResponse(GenericObjectActionMessage(target, code=53)) + case AvatarAction.KitNotUsed(_, msg) => + sessionLogic.general.kitToBeUsed = None + sendResponse(ChatMsg(ChatMessageType.UNK_225, msg)) - case AvatarAction.StowEquipment(target, slot, item) if isNotSameTarget => - val definition = item.Definition - sendResponse( - ObjectCreateDetailedMessage( - definition.ObjectId, - item.GUID, - ObjectCreateMessageParent(target, slot), - definition.Packet.DetailedConstructorData(item).get - ) - ) + case AvatarAction.UpdateKillsDeathsAssists(_, kda: Kill) if kda.experienceEarned > 0 => + continent.actor ! ZoneActor.RewardOurSupporters( + PlayerSource(player), + Players.produceContributionTranscriptFromKill(continent, player, kda), + kda, + kda.experienceEarned + ) - case WeaponDryFire(weaponGuid) - if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } => - continent.GUID(weaponGuid).collect { - case tool: Tool if tool.Magazine == 0 => - // check that the magazine is still empty before sending WeaponDryFireMessage - // if it has been reloaded since then, other clients will not see it firing - sendResponse(WeaponDryFireMessage(weaponGuid)) + case AvatarAction.AwardBep(charId, bep, expType) => + //if the target player, always award (some) BEP + if (charId == player.CharId) { + avatarActor ! AvatarActor.AwardBep(bep, expType) + } + + case AvatarAction.AwardCep(charId, cep) => + //if the target player, always award (some) CEP + if (charId == player.CharId) { + avatarActor ! AvatarActor.AwardCep(cep) + } + + case AvatarAction.FacilityCaptureRewards(buildingId, zoneNumber, cep) => + ops.facilityCaptureRewards(buildingId, zoneNumber, cep) + + case SendResponse(msgs) => + msgs.foreach { + case pkt: AvatarImplantMessage + if pkt.player_guid == player.GUID && pkt.action == ImplantAction.Initialization => + //special spectator implants stay initialized and do not deinitialize + () + case msg => + sendResponse(msg) + } + + /* common messages (maybe once every respawn) */ + case ReloadTool(itemGuid) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { _.visible } => + sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0)) + + case AvatarAction.Killed(_, mount) => + //log and chat messages + val cause = player.LastDamage.flatMap { damage => + val interaction = damage.interaction + val reason = interaction.cause + val adversarial = interaction.adversarial.map { _.attacker } + reason match { + case r: ExplodingEntityReason if r.entity.isInstanceOf[VehicleSpawnPad] => + //also, @SVCP_Killed_TooCloseToPadOnCreate^n~ or "... within n meters of pad ..." + sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SVCP_Killed_OnPadOnCreate")) + case _ => () } + adversarial.map {_.Name }.orElse { Some(s"a ${reason.getClass.getSimpleName}") } + }.getOrElse { s"an unfortunate circumstance (probably ${player.Sex.pronounObject} own fault)" } + log.info(s"${player.Name} has died, killed by $cause") + if (sessionLogic.shooting.shotsWhileDead > 0) { + log.warn( + s"SHOTS_WHILE_DEAD: client of ${avatar.name} fired ${sessionLogic.shooting.shotsWhileDead} rounds while character was dead on server" + ) + sessionLogic.shooting.shotsWhileDead = 0 + } + sessionLogic.zoning.CancelZoningProcess() - case _ => () - } + //player state changes + sessionLogic.zoning.spawn.avatarActive = false + AvatarActor.updateToolDischargeFor(avatar) + player.FreeHand.Equipment.foreach { item => + DropEquipmentFromInventory(player)(item) + } + sessionLogic.general.dropSpecialSlotItem() + sessionLogic.general.toggleMaxSpecialState(enable = false) + sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive + sessionLogic.zoning.zoningStatus = Zoning.Status.None + sessionLogic.zoning.spawn.deadState = DeadState.Dead + continent.GUID(mount).collect { case obj: Vehicle => + sessionLogic.vehicles.ConditionalDriverVehicleControl(obj) + sessionLogic.general.unaccessContainer(obj) + } + sessionLogic.actionsToCancel() + sessionLogic.terminals.CancelAllProximityUnits() + AvatarActor.savePlayerLocation(player) + sessionLogic.zoning.spawn.ShiftPosition = Some(player.Position) + + //respawn + sessionLogic.zoning.spawn.reviveTimer.cancel() + if (player.death_by == 0) { + sessionLogic.zoning.spawn.randomRespawn(300.seconds) + } else { + sessionLogic.zoning.spawn.HandleReleaseAvatar(player, continent) + } + + case AvatarAction.ReleasePlayer(tplayer) if filter.isNotSameTarget => + sessionLogic.zoning.spawn.DepictPlayerAsCorpse(tplayer) + + case AvatarAction.Revive(revivalTargetGuid) if filter.resolvedPlayerGuid == revivalTargetGuid => + log.info(s"No time for rest, ${player.Name}. Back on your feet!") + sessionLogic.zoning.spawn.reviveTimer.cancel() + sessionLogic.zoning.spawn.deadState = DeadState.Alive + player.Revive + val health = player.Health + sendResponse(PlanetsideAttributeMessage(revivalTargetGuid, attribute_type=0, health)) + sendResponse(AvatarDeadStateMessage(DeadState.Alive, timer_max=0, timer=0, player.Position, player.Faction, unk5=true)) + continent.AvatarEvents ! MessageEnvelope( + continent.id, + PlanetsideAttribute(revivalTargetGuid, attribute_type=0, health) + ) + + /* uncommon messages (utility, or once in a while) */ + case ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { _.visible } => + ops.changeAmmoProcedure(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data) + sendResponse(ChangeAmmoMessage(weapon_guid, 1)) + + case AvatarAction.ChangeFireMode(itemGuid, mode) if filter.isNotSameTarget => + sendResponse(ChangeFireModeMessage(itemGuid, mode)) + + case AvatarAction.EnvironmentalDamage(_, _, _) => + //TODO damage marker? + sessionLogic.zoning.CancelZoningProcess() + + case AvatarAction.DropCreatedItem(pkt) if filter.isNotSameTarget => + sendResponse(pkt) + + /* rare messages */ + case AvatarAction.DropSpecialItem() => + sessionLogic.general.dropSpecialSlotItem() + + case AvatarAction.OxygenState(player, vehicle) => + sendResponse(OxygenStateMessage( + DrowningTarget(player.guid, player.progress, player.state), + vehicle.flatMap { vinfo => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state)) } + )) + + case AvatarAction.LoadCreatedProjectile(pkt) if filter.isNotSameTarget => + sendResponse(pkt) + + case AvatarAction.ProjectileState(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid) if filter.isNotSameTarget => + sendResponse(ProjectileStateMessage(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid)) + + case AvatarAction.ProjectileExplodes(projectileGuid, projectile) => + sendResponse( + ProjectileStateMessage( + projectileGuid, + projectile.Position, + shot_vel = Vector3.Zero, + projectile.Orientation, + sequence_num=0, + end=true, + hit_target_guid=PlanetSideGUID(0) + ) + ) + sendResponse(ObjectDeleteMessage(projectileGuid, unk1=2)) + + case AvatarAction.ProjectileAutoLockAwareness(mode) => + sendResponse(GenericActionMessage(mode)) + + case AvatarAction.PutDownFDU(target) if filter.isNotSameTarget => + sendResponse(GenericObjectActionMessage(target, code=53)) + + case AvatarAction.StowEquipment(target, slot, item) if filter.isNotSameTarget => + val definition = item.Definition + sendResponse( + ObjectCreateDetailedMessage( + definition.ObjectId, + item.GUID, + ObjectCreateMessageParent(target, slot), + definition.Packet.DetailedConstructorData(item).get + ) + ) + + case WeaponDryFire(weaponGuid) + if filter.isNotSameTarget && ops.lastSeenStreamMessage.get(filter.resolvedPlayerGuid.guid).exists { _.visible } => + continent.GUID(weaponGuid).collect { + case tool: Tool if tool.Magazine == 0 => + // check that the magazine is still empty before sending WeaponDryFireMessage + // if it has been reloaded since then, other clients will not see it firing + sendResponse(WeaponDryFireMessage(weaponGuid)) + } } } diff --git a/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala index 182f32efe..3b6a1b96e 100644 --- a/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala @@ -1,6 +1,7 @@ // Copyright (c) 2024 PSForever package net.psforever.actors.session.spectator +import akka.actor.Actor.Receive import akka.actor.ActorContext import net.psforever.actors.session.support.{SessionData, SessionVehicleHandlers, VehicleHandlerFunctions} import net.psforever.objects.{Tool, Vehicle, Vehicles} @@ -9,7 +10,7 @@ import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent import net.psforever.packet.game.{ChangeAmmoMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChildObjectStateMessage, DeadState, DeployRequestMessage, DismountVehicleMsg, FrameVehicleStateMessage, GenericObjectActionMessage, InventoryStateMessage, ObjectAttachMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, PlanetsideAttributeMessage, ReloadMessage, ServerVehicleOverrideMsg, VehicleStateMessage, WeaponDryFireMessage} import net.psforever.services.base.envelope.GenericResponseEnvelope -import net.psforever.services.base.message.{ChangeAmmo, ChangeFireState_Start, ChangeFireState_Stop, EventResponse, GenericObjectAction, ObjectDelete, PlanetsideAttribute, ReloadTool, SendResponse, WeaponDryFire} +import net.psforever.services.base.message.{ChangeAmmo, ChangeFireState_Start, ChangeFireState_Stop, GenericObjectAction, ObjectDelete, PlanetsideAttribute, ReloadTool, SendResponse, WeaponDryFire} import net.psforever.services.vehicle.{VehicleAction, VehicleStamp} import net.psforever.types.{BailType, PlanetSideGUID, Vector3} @@ -26,232 +27,173 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: //private val galaxyService: ActorRef = ops.galaxyService - /** - * na - * - * @param toChannel na - * @param guid na - * @param reply na - */ - def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit = { - val resolvedPlayerGuid = if (player.HasGUID) { - player.GUID - } else { - PlanetSideGUID(-1) - } - val isNotSameTarget = resolvedPlayerGuid != guid - reply match { - case VehicleAction.VehicleState( - vehicleGuid, - unk1, - pos, - orient, - vel, - unk2, - unk3, - unk4, - wheelDirection, - unk5, - unk6 - ) if isNotSameTarget && player.VehicleSeated.contains(vehicleGuid) => - //player who is also in the vehicle (not driver) - sendResponse(VehicleStateMessage(vehicleGuid, unk1, pos, orient, vel, unk2, unk3, unk4, wheelDirection, unk5, unk6)) - player.Position = pos - player.Orientation = orient - player.Velocity = vel - sessionLogic.updateLocalBlockMap(pos) + def receive: Receive = { + case VehicleAction.VehicleState( + vehicleGuid, + unk1, + pos, + orient, + vel, + unk2, + unk3, + unk4, + wheelDirection, + unk5, + unk6 + ) if filter.isNotSameTarget && player.VehicleSeated.contains(vehicleGuid) => + //player who is also in the vehicle (not driver) + sendResponse(VehicleStateMessage(vehicleGuid, unk1, pos, orient, vel, unk2, unk3, unk4, wheelDirection, unk5, unk6)) + player.Position = pos + player.Orientation = orient + player.Velocity = vel + sessionLogic.updateLocalBlockMap(pos) - case VehicleAction.VehicleState( - vehicleGuid, - unk1, - pos, - ang, - vel, - unk2, - unk3, - unk4, - wheelDirection, - unk5, - unk6 - ) if isNotSameTarget => - //player who is watching the vehicle from the outside - sendResponse(VehicleStateMessage(vehicleGuid, unk1, pos, ang, vel, unk2, unk3, unk4, wheelDirection, unk5, unk6)) + case VehicleAction.VehicleState( + vehicleGuid, + unk1, + pos, + ang, + vel, + unk2, + unk3, + unk4, + wheelDirection, + unk5, + unk6 + ) if filter.isNotSameTarget => + //player who is watching the vehicle from the outside + sendResponse(VehicleStateMessage(vehicleGuid, unk1, pos, ang, vel, unk2, unk3, unk4, wheelDirection, unk5, unk6)) - case VehicleAction.ChildObjectState(objectGuid, pitch, yaw) if isNotSameTarget => - sendResponse(ChildObjectStateMessage(objectGuid, pitch, yaw)) + case VehicleAction.ChildObjectState(objectGuid, pitch, yaw) if filter.isNotSameTarget => + sendResponse(ChildObjectStateMessage(objectGuid, pitch, yaw)) - case VehicleAction.FrameVehicleState(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA) - if isNotSameTarget => - sendResponse(FrameVehicleStateMessage(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA)) + case VehicleAction.FrameVehicleState(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA) + if filter.isNotSameTarget => + sendResponse(FrameVehicleStateMessage(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA)) - case ChangeFireState_Start(weaponGuid) if isNotSameTarget => - sendResponse(ChangeFireStateMessage_Start(weaponGuid)) + case VehicleAction.DismountVehicle(bailType, wasKickedByDriver) if filter.isNotSameTarget => + sendResponse(DismountVehicleMsg(filter.resolvedPlayerGuid, bailType, wasKickedByDriver)) - case ChangeFireState_Stop(weaponGuid) if isNotSameTarget => - sendResponse(ChangeFireStateMessage_Stop(weaponGuid)) + case VehicleAction.MountVehicle(vehicleGuid, seat) if filter.isNotSameTarget => + sendResponse(ObjectAttachMessage(vehicleGuid, filter.resolvedPlayerGuid, seat)) - case ReloadTool(itemGuid) if isNotSameTarget => - sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0)) + case VehicleAction.DeployRequest(objectGuid, state, unk1, unk2, pos) if filter.isNotSameTarget => + sendResponse(DeployRequestMessage(filter.resolvedPlayerGuid, objectGuid, state, unk1, unk2, pos)) - case ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) if isNotSameTarget => - sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3.Zero, 0)) - //TODO? sendResponse(ObjectDeleteMessage(previousAmmoGuid, 0)) - sendResponse( - ObjectCreateMessage( - ammo_id, - ammo_guid, - ObjectCreateMessageParent(weapon_guid, weapon_slot), - ammo_data - ) + case VehicleAction.EquipmentCreatedInSlot(pkt) if filter.isNotSameTarget => + sendResponse(pkt) + + case VehicleAction.InventoryState(obj, parentGuid, start, conData) if filter.isNotSameTarget => + //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? + val objGuid = obj.GUID + sendResponse(ObjectDeleteMessage(objGuid, unk1=0)) + sendResponse(ObjectCreateDetailedMessage( + obj.Definition.ObjectId, + objGuid, + ObjectCreateMessageParent(parentGuid, start), + conData + )) + + case VehicleAction.KickPassenger(_, wasKickedByDriver, vehicleGuid) if filter.isSameTarget => + //seat number (first field) seems to be correct if passenger is kicked manually by driver + //but always seems to return 4 if user is kicked by mount permissions changing + sendResponse(DismountVehicleMsg(filter.resolvedPlayerGuid, BailType.Kicked, wasKickedByDriver)) + continent.GUID(vehicleGuid) match { + case Some(obj: Vehicle) => + sessionLogic.general.unaccessContainer(obj) + case _ => () + } + + case VehicleAction.KickPassenger(_, wasKickedByDriver, _) => + //seat number (first field) seems to be correct if passenger is kicked manually by driver + //but always seems to return 4 if user is kicked by mount permissions changing + sendResponse(DismountVehicleMsg(filter.resolvedPlayerGuid, BailType.Kicked, wasKickedByDriver)) + + case VehicleAction.InventoryState2(objGuid, parentGuid, value) if filter.isNotSameTarget => + sendResponse(InventoryStateMessage(objGuid, unk=0, parentGuid, value)) + + case VehicleAction.LoadVehicle(vehicle, vtype, vguid, vdata) if filter.isNotSameTarget => + //this is not be suitable for vehicles with people who are seated in it before it spawns (if that is possible) + sendResponse(ObjectCreateMessage(vtype, vguid, vdata)) + Vehicles.ReloadAccessPermissions(vehicle, player.Name) + + case VehicleAction.SeatPermissions(vehicleGuid, seatGroup, permission) if filter.isNotSameTarget => + sendResponse(PlanetsideAttributeMessage(vehicleGuid, seatGroup, permission)) + + case VehicleAction.UnloadVehicle(_, vehicleGuid) => + sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=1)) + + case VehicleAction.UnstowEquipment(itemGuid) if filter.isNotSameTarget => + //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? + sendResponse(ObjectDeleteMessage(itemGuid, unk1=0)) + + case VehicleAction.UpdateAmsSpawnList(list) => + sessionLogic.zoning.spawn.amsSpawnPoints = list.filter(tube => tube.Faction == player.Faction) + sessionLogic.zoning.spawn.DrawCurrentAmsSpawnPoint() + + case VehicleAction.KickCargo(vehicle, speed, delay) + if player.VehicleSeated.nonEmpty && sessionLogic.zoning.spawn.deadState == DeadState.Alive && speed > 0 => + val strafe = 1 + Vehicles.CargoOrientation(vehicle) + val reverseSpeed = if (strafe > 1) { 0 } else { speed } + //strafe or reverse, not both + sessionLogic.vehicles.ServerVehicleOverrideWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=true, + lock_wheel=true, + reverse=true, + unk4=false, + lock_vthrust=0, + strafe, + reverseSpeed, + unk8=Some(0) ) - sendResponse(ChangeAmmoMessage(weapon_guid, 1)) + ) + import scala.concurrent.ExecutionContext.Implicits.global + import scala.concurrent.duration._ + val resp = GenericResponseEnvelope( + VehicleStamp, + "", + PlanetSideGUID(0), + VehicleAction.KickCargo(vehicle, speed=0, delay) + ) + context.system.scheduler.scheduleOnce(delay milliseconds, context.self, resp) - case WeaponDryFire(weaponGuid) if isNotSameTarget => - continent.GUID(weaponGuid).collect { - case tool: Tool if tool.Magazine == 0 => - // check that the magazine is still empty before sending WeaponDryFireMessage - // if it has been reloaded since then, other clients will not see it firing - sendResponse(WeaponDryFireMessage(weaponGuid)) - } + case VehicleAction.KickCargo(cargo, _, _) + if player.VehicleSeated.nonEmpty && sessionLogic.zoning.spawn.deadState == DeadState.Alive => + sessionLogic.vehicles.TotalDriverVehicleControl(cargo) - case VehicleAction.DismountVehicle(bailType, wasKickedByDriver) if isNotSameTarget => - sendResponse(DismountVehicleMsg(guid, bailType, wasKickedByDriver)) + case VehicleSpawnPad.AttachToRails(vehicle, pad) => + sendResponse(ObjectAttachMessage(pad.GUID, vehicle.GUID, slot=3)) - case VehicleAction.MountVehicle(vehicleGuid, seat) if isNotSameTarget => - sendResponse(ObjectAttachMessage(vehicleGuid, guid, seat)) + case VehicleSpawnPad.ConcealPlayer(playerGuid) => + sendResponse(GenericObjectActionMessage(playerGuid, code=9)) - case VehicleAction.DeployRequest(objectGuid, state, unk1, unk2, pos) if isNotSameTarget => - sendResponse(DeployRequestMessage(guid, objectGuid, state, unk1, unk2, pos)) - - case SendResponse(msgs) => - msgs.foreach(sendResponse) - - case VehicleAction.EquipmentCreatedInSlot(pkt) if isNotSameTarget => - sendResponse(pkt) - - case GenericObjectAction(objectGuid, action) if isNotSameTarget => - sendResponse(GenericObjectActionMessage(objectGuid, action)) - - case VehicleAction.InventoryState(obj, parentGuid, start, conData) if isNotSameTarget => - //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? - val objGuid = obj.GUID - sendResponse(ObjectDeleteMessage(objGuid, unk1=0)) - sendResponse(ObjectCreateDetailedMessage( - obj.Definition.ObjectId, - objGuid, - ObjectCreateMessageParent(parentGuid, start), - conData - )) - - case VehicleAction.KickPassenger(_, wasKickedByDriver, vehicleGuid) if resolvedPlayerGuid == guid => - //seat number (first field) seems to be correct if passenger is kicked manually by driver - //but always seems to return 4 if user is kicked by mount permissions changing - sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver)) - continent.GUID(vehicleGuid) match { - case Some(obj: Vehicle) => - sessionLogic.general.unaccessContainer(obj) - case _ => () - } - - case VehicleAction.KickPassenger(_, wasKickedByDriver, _) => - //seat number (first field) seems to be correct if passenger is kicked manually by driver - //but always seems to return 4 if user is kicked by mount permissions changing - sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver)) - - case VehicleAction.InventoryState2(objGuid, parentGuid, value) if isNotSameTarget => - sendResponse(InventoryStateMessage(objGuid, unk=0, parentGuid, value)) - - case VehicleAction.LoadVehicle(vehicle, vtype, vguid, vdata) if isNotSameTarget => - //this is not be suitable for vehicles with people who are seated in it before it spawns (if that is possible) - sendResponse(ObjectCreateMessage(vtype, vguid, vdata)) - Vehicles.ReloadAccessPermissions(vehicle, player.Name) - - case ObjectDelete(itemGuid, _) if isNotSameTarget => - sendResponse(ObjectDeleteMessage(itemGuid, unk1=0)) - - case PlanetsideAttribute(vehicleGuid, attributeType, attributeValue) if isNotSameTarget => - sendResponse(PlanetsideAttributeMessage(vehicleGuid, attributeType, attributeValue)) - - case VehicleAction.SeatPermissions(vehicleGuid, seatGroup, permission) if isNotSameTarget => - sendResponse(PlanetsideAttributeMessage(vehicleGuid, seatGroup, permission)) - - case VehicleAction.UnloadVehicle(_, vehicleGuid) => - sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=1)) - - case VehicleAction.UnstowEquipment(itemGuid) if isNotSameTarget => - //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? - sendResponse(ObjectDeleteMessage(itemGuid, unk1=0)) - - case VehicleAction.UpdateAmsSpawnList(list) => - sessionLogic.zoning.spawn.amsSpawnPoints = list.filter(tube => tube.Faction == player.Faction) - sessionLogic.zoning.spawn.DrawCurrentAmsSpawnPoint() - - case VehicleAction.KickCargo(vehicle, speed, delay) - if player.VehicleSeated.nonEmpty && sessionLogic.zoning.spawn.deadState == DeadState.Alive && speed > 0 => - val strafe = 1 + Vehicles.CargoOrientation(vehicle) - val reverseSpeed = if (strafe > 1) { 0 } else { speed } - //strafe or reverse, not both - sessionLogic.vehicles.ServerVehicleOverrideWithPacket( - vehicle, - ServerVehicleOverrideMsg( - lock_accelerator=true, - lock_wheel=true, - reverse=true, - unk4=false, - lock_vthrust=0, - strafe, - reverseSpeed, - unk8=Some(0) - ) + case VehicleSpawnPad.DetachFromRails(vehicle, pad) => + val padDefinition = pad.Definition + sendResponse( + ObjectDetachMessage( + pad.GUID, + vehicle.GUID, + pad.Position + Vector3.z(padDefinition.VehicleCreationZOffset), + pad.Orientation.z + padDefinition.VehicleCreationZOrientOffset ) - import scala.concurrent.ExecutionContext.Implicits.global - import scala.concurrent.duration._ - val resp = GenericResponseEnvelope( - VehicleStamp, - toChannel, - PlanetSideGUID(0), - VehicleAction.KickCargo(vehicle, speed=0, delay) - ) - context.system.scheduler.scheduleOnce(delay milliseconds, context.self, resp) + ) - case VehicleAction.KickCargo(cargo, _, _) - if player.VehicleSeated.nonEmpty && sessionLogic.zoning.spawn.deadState == DeadState.Alive => - sessionLogic.vehicles.TotalDriverVehicleControl(cargo) + case VehicleSpawnPad.ResetSpawnPad(pad) => + sendResponse(GenericObjectActionMessage(pad.GUID, code=23)) - case VehicleSpawnPad.AttachToRails(vehicle, pad) => - sendResponse(ObjectAttachMessage(pad.GUID, vehicle.GUID, slot=3)) + case VehicleSpawnPad.RevealPlayer(playerGuid) => + sendResponse(GenericObjectActionMessage(playerGuid, code=10)) - case VehicleSpawnPad.ConcealPlayer(playerGuid) => - sendResponse(GenericObjectActionMessage(playerGuid, code=9)) + case VehicleSpawnPad.ServerVehicleOverrideEnd(vehicle, _) => + sessionLogic.vehicles.ServerVehicleOverrideStop(vehicle) - case VehicleSpawnPad.DetachFromRails(vehicle, pad) => - val padDefinition = pad.Definition - sendResponse( - ObjectDetachMessage( - pad.GUID, - vehicle.GUID, - pad.Position + Vector3.z(padDefinition.VehicleCreationZOffset), - pad.Orientation.z + padDefinition.VehicleCreationZOrientOffset - ) - ) - - case VehicleSpawnPad.ResetSpawnPad(pad) => - sendResponse(GenericObjectActionMessage(pad.GUID, code=23)) - - case VehicleSpawnPad.RevealPlayer(playerGuid) => - sendResponse(GenericObjectActionMessage(playerGuid, code=10)) - - case VehicleSpawnPad.ServerVehicleOverrideEnd(vehicle, _) => - sessionLogic.vehicles.ServerVehicleOverrideStop(vehicle) - - case VehicleAction.ChangeLoadout(target, oldWeapons, _, oldInventory, _) => - //TODO when vehicle weapons can be changed without visual glitches, rewrite this - continent.GUID(target).collect { case vehicle: Vehicle => - changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory) - } - - case _ => () - } + case VehicleAction.ChangeLoadout(target, oldWeapons, _, oldInventory, _) => + //TODO when vehicle weapons can be changed without visual glitches, rewrite this + continent.GUID(target).collect { case vehicle: Vehicle => + changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory) + } } private def changeLoadoutDeleteOldEquipment( diff --git a/src/main/scala/net/psforever/actors/session/support/CommonHandlerFunctions.scala b/src/main/scala/net/psforever/actors/session/support/CommonHandlerFunctions.scala new file mode 100644 index 000000000..4dc165152 --- /dev/null +++ b/src/main/scala/net/psforever/actors/session/support/CommonHandlerFunctions.scala @@ -0,0 +1,105 @@ +// Copyright (c) 2026 PSForever +package net.psforever.actors.session.support + +import akka.actor.Actor.Receive +import net.psforever.objects.Player +import net.psforever.services.Service +import net.psforever.services.base.message.EventResponse +import net.psforever.types.PlanetSideGUID + +trait HandlerFilter { + def resolvedPlayerGuid: PlanetSideGUID + def isNotSameTarget: Boolean + def isSameTarget: Boolean = !isNotSameTarget +} + +class HandlerFilterClass(guid1: PlanetSideGUID, guid2: PlanetSideGUID) extends HandlerFilter { + val resolvedPlayerGuid: PlanetSideGUID = guid2 + val isNotSameTarget: Boolean = resolvedPlayerGuid != guid1 +} + +object HandlerFilter { + def apply(guid1: PlanetSideGUID, guid2: PlanetSideGUID): HandlerFilter = { + new HandlerFilterClass(guid1, guid2) + } + + def apply(guid: PlanetSideGUID, player: Player): HandlerFilter = { + this(guid, if (player != null && player.HasGUID) { + player.GUID + } else { + Service.defaultPlayerGUID + }) + } + + final val NeverAllow: HandlerFilter = new HandlerFilter { + def resolvedPlayerGuid: PlanetSideGUID = PlanetSideGUID(-1) + def isNotSameTarget: Boolean = false + override def isSameTarget: Boolean = false + } + + final def Allow(guid: PlanetSideGUID): HandlerFilter = new HandlerFilter { + def resolvedPlayerGuid: PlanetSideGUID = guid + def isNotSameTarget: Boolean = true + override def isSameTarget: Boolean = true + } +} + +trait CommonHandlerFunctionsBase { + /** + * na + * @param toChannel na + * @param guid na + * @param reply na + */ + def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit + + def handleWith(guid: PlanetSideGUID): Receive + + def handleWith(filter: HandlerFilter): Receive + + def receive: Receive +} + +trait CommonHandlerFunctions extends CommonHandlerFunctionsBase { + _: CommonSessionInterfacingFunctionality => + protected var filter: HandlerFilter = HandlerFilter.NeverAllow + + /** + * na + * @param toChannel na + * @param guid na + * @param reply na + */ + def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit = { + filter = HandlerFilter(guid, player) + receive.apply(reply) + } + + def handleWith(guid: PlanetSideGUID): Receive = { + filter = HandlerFilter(guid, player) + receive + } + + def handleWith(giveFilter: HandlerFilter): Receive = { + filter = giveFilter + receive + } + + def receive: Receive +} + +object CommonHandlerFunctions { + val HandleNothing: CommonHandlerFunctionsBase = new CommonHandlerFunctionsBase { + def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit = { } + def handleWith(guid: PlanetSideGUID): Receive = receive + def handleWith(filter: HandlerFilter): Receive = receive + def receive: Receive = { case _: CommonHandlerFunctions => () } + } + + val HandleAnything: CommonHandlerFunctionsBase = new CommonHandlerFunctionsBase { + def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit = receive.apply(reply) + def handleWith(guid: PlanetSideGUID): Receive = receive + def handleWith(filter: HandlerFilter): Receive = receive + def receive: Receive = { case _ => () } + } +} diff --git a/src/main/scala/net/psforever/actors/session/support/CommonHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/support/CommonHandlerLogic.scala new file mode 100644 index 000000000..e66a4e84c --- /dev/null +++ b/src/main/scala/net/psforever/actors/session/support/CommonHandlerLogic.scala @@ -0,0 +1,67 @@ +// Copyright (c) 2026 PSForever +package net.psforever.actors.session.support + +import akka.actor.Actor.Receive +import akka.actor.ActorContext +import net.psforever.objects.Tool +import net.psforever.packet.game.{ChangeAmmoMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, GenericObjectActionMessage, HitHint, ObjectDeleteMessage, PlanetsideAttributeMessage, ReloadMessage, SetEmpireMessage, WeaponDryFireMessage} +import net.psforever.services.base.message.{ChangeAmmo, ChangeFireState_Start, ChangeFireState_Stop, ConcealPlayer, GenericObjectAction, HintsAtAttacker, ObjectDelete, PlanetsideAttribute, ReloadTool, SendResponse, SetEmpire, WeaponDryFire} + +class CommonHandlerLogic(val sessionLogic: SessionData, implicit val context: ActorContext) + extends CommonSessionInterfacingFunctionality with CommonHandlerFunctions { + + def receive: Receive = { + case PlanetsideAttribute(target_guid, attributeType, attributeValue) + if filter.isNotSameTarget => + sendResponse(PlanetsideAttributeMessage(target_guid, attributeType, attributeValue)) + + case GenericObjectAction(objectGuid, actionCode) + if filter.isNotSameTarget => + sendResponse(GenericObjectActionMessage(objectGuid, actionCode)) + + case ObjectDelete(itemGuid, unk) + if filter.isNotSameTarget => + sendResponse(ObjectDeleteMessage(itemGuid, unk)) + + case ChangeFireState_Start(weaponGuid) + if filter.isNotSameTarget => + sendResponse(ChangeFireStateMessage_Start(weaponGuid)) + + case ChangeFireState_Stop(weaponGuid) + if filter.isNotSameTarget => + sendResponse(ChangeFireStateMessage_Stop(weaponGuid)) + + case ReloadTool(itemGuid) + if filter.isNotSameTarget => + sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0)) + + case ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) + if filter.isNotSameTarget => + sessionLogic.avatarResponse.changeAmmoProcedure(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data) + sendResponse(ChangeAmmoMessage(weapon_guid, 1)) + + case WeaponDryFire(weaponGuid) + if filter.isNotSameTarget => + continent.GUID(weaponGuid).collect { + case tool: Tool if tool.Magazine == 0 => + // check that the magazine is still empty before sending WeaponDryFireMessage + // if it has been reloaded since then, other clients will not see it firing + sendResponse(WeaponDryFireMessage(weaponGuid)) + } + + case HintsAtAttacker(sourceGuid) + if player.isAlive => + sendResponse(HitHint(sourceGuid, filter.resolvedPlayerGuid)) + sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg") + + case SetEmpire(objectGuid, faction) + if filter.isNotSameTarget => + sendResponse(SetEmpireMessage(objectGuid, faction)) + + case ConcealPlayer(_) => + sendResponse(GenericObjectActionMessage(filter.resolvedPlayerGuid, code=9)) + + case SendResponse(msgs) => + msgs.foreach(sendResponse) + } +} diff --git a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala index 2e613704b..bf792a866 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala @@ -10,7 +10,7 @@ import net.psforever.objects.zones.exp import net.psforever.services.Service import net.psforever.services.avatar.{AvatarAction, AvatarStamp} import net.psforever.services.base.envelope.{GenericResponseEnvelope, MessageEnvelope} -import net.psforever.services.base.message.{EventResponse, SendResponse} +import net.psforever.services.base.message.SendResponse import net.psforever.services.chat.OutfitChannel import scala.collection.mutable @@ -21,10 +21,8 @@ import net.psforever.packet.game._ import net.psforever.types._ import net.psforever.util.Config -trait AvatarHandlerFunctions extends CommonSessionInterfacingFunctionality { +trait AvatarHandlerFunctions extends CommonSessionInterfacingFunctionality with CommonHandlerFunctions { val ops: SessionAvatarHandlers - - def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit } class SessionAvatarHandlers( @@ -37,7 +35,7 @@ class SessionAvatarHandlers( mutable.LongMap[SessionAvatarHandlers.LastUpstream]() private[session] val hidingPlayerRandomizer = new scala.util.Random - def changeAmmoProcedures( + def changeAmmoProcedure( weaponGuid: PlanetSideGUID, previousAmmoGuid: PlanetSideGUID, ammoTypeId: Int, @@ -46,7 +44,7 @@ class SessionAvatarHandlers( ammoData: ConstructorData ): Unit = { sendResponse(ObjectDetachMessage(weaponGuid, previousAmmoGuid, Vector3.Zero, 0)) - //TODO? sendResponse(ObjectDeleteMessage(previousAmmoGuid, 0)) + sendResponse(ObjectDeleteMessage(previousAmmoGuid, 0)) sendResponse( ObjectCreateMessage( ammoTypeId, diff --git a/src/main/scala/net/psforever/actors/session/support/SessionGalaxyHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionGalaxyHandlers.scala index adc4d742c..419c74c0d 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionGalaxyHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionGalaxyHandlers.scala @@ -3,18 +3,13 @@ package net.psforever.actors.session.support import akka.actor.{ActorContext, ActorRef, typed} import net.psforever.packet.game.FriendsResponse -import net.psforever.services.base.message.EventResponse -import scala.annotation.unused -// import net.psforever.actors.session.AvatarActor -trait GalaxyHandlerFunctions extends CommonSessionInterfacingFunctionality { +trait GalaxyHandlerFunctions extends CommonSessionInterfacingFunctionality with CommonHandlerFunctions { def ops: SessionGalaxyHandlers def handleUpdateIgnoredPlayers(pkt: FriendsResponse): Unit - - def handle(@unused reply: EventResponse): Unit } class SessionGalaxyHandlers( diff --git a/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala index 527636d03..583a83f87 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala @@ -7,17 +7,14 @@ import net.psforever.objects.ce.Deployable import net.psforever.objects.guid.{GUIDTask, TaskWorkflow} import net.psforever.objects.serverobject.interior.Sidedness import net.psforever.packet.game.{GenericObjectActionMessage, ObjectDeleteMessage, PlanetsideAttributeMessage, TriggerEffectMessage} -import net.psforever.services.base.message.EventResponse import net.psforever.types.{PlanetSideGUID, Vector3} -trait LocalHandlerFunctions extends CommonSessionInterfacingFunctionality { +trait LocalHandlerFunctions extends CommonSessionInterfacingFunctionality with CommonHandlerFunctions { def ops: SessionLocalHandlers def handleTurretDeployableIsDismissed(obj: TurretDeployable): Unit def handleDeployableIsDismissed(obj: Deployable): Unit - - def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit } class SessionLocalHandlers( diff --git a/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala index a3d1d6cad..3b46b874f 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala @@ -5,13 +5,10 @@ import akka.actor.{ActorContext, ActorRef, typed} import net.psforever.actors.session.AvatarActor import net.psforever.objects.Vehicle import net.psforever.packet.game.ChatMsg -import net.psforever.services.base.message.EventResponse import net.psforever.types.{ChatMessageType, DriveState, PlanetSideGUID} -trait VehicleHandlerFunctions extends CommonSessionInterfacingFunctionality { +trait VehicleHandlerFunctions extends CommonSessionInterfacingFunctionality with CommonHandlerFunctions { def ops: SessionVehicleHandlers - - def handle(toChannel: String, guid: PlanetSideGUID, reply: EventResponse): Unit } class SessionVehicleHandlers( diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala index 900d88600..4161addcd 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -2195,7 +2195,7 @@ class ZoningOperations( log.info(s"RestoreInfo: player $name is already logged in zone ${inZone.id}; rejoining that character") sessionLogic.persistFunc = UpdatePersistence(from) //tell the old WorldSessionActor to kill itself by using its own subscriptions against itself - inZone.AvatarEvents ! MessageEnvelope(name, AvatarAction.TeardownConnection()) + inZone.AvatarEvents ! MessageEnvelope(name, AvatarAction.TeardownConnection) spawn.switchAvatarStatisticsFieldToRefreshAfterRespawn() //find and reload previous player ( diff --git a/src/main/scala/net/psforever/objects/Vehicles.scala b/src/main/scala/net/psforever/objects/Vehicles.scala index 8b0a43f6b..5729a8e7c 100644 --- a/src/main/scala/net/psforever/objects/Vehicles.scala +++ b/src/main/scala/net/psforever/objects/Vehicles.scala @@ -279,7 +279,6 @@ object Vehicles { if (GlobalDefinitions.isBattleFrameVehicle(target.Definition) && target.Seat(0).isDefined) { zone.LocalEvents ! MessageEnvelope( zoneid, - PlanetSideGUID(-1), GenericObjectAction(target.GUID, GenericObjectActionEnum.BFRShieldsDown.id) ) zone.LocalEvents ! MessageEnvelope( diff --git a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala index 5b8f7d5b2..8ba360041 100644 --- a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala @@ -109,7 +109,6 @@ class ResourceSiloControl(resourceSilo: ResourceSilo) val zone = building.Zone building.Zone.AvatarEvents ! MessageEnvelope( zone.id, - building.GUID, PlanetsideAttribute(building.GUID, 47, if (resourceSilo.LowNtuWarningOn) 1 else 0) ) } @@ -133,7 +132,6 @@ class ResourceSiloControl(resourceSilo: ResourceSilo) ) zone.AvatarEvents ! MessageEnvelope( zone.id, - resourceSilo.GUID, PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay) ) building.Actor ! BuildingActor.MapUpdate() diff --git a/src/main/scala/net/psforever/services/avatar/AvatarAction.scala b/src/main/scala/net/psforever/services/avatar/AvatarAction.scala index 22e223e91..593fd920d 100644 --- a/src/main/scala/net/psforever/services/avatar/AvatarAction.scala +++ b/src/main/scala/net/psforever/services/avatar/AvatarAction.scala @@ -201,5 +201,5 @@ object AvatarAction { final case class RemoveFromOutfitChat(outfit_id: Long) extends SelfRespondingEvent - final case class TeardownConnection() extends SelfRespondingEvent + final case object TeardownConnection extends SelfRespondingEvent } diff --git a/src/main/scala/net/psforever/services/avatar/support/CorpseRemovalActor.scala b/src/main/scala/net/psforever/services/avatar/support/CorpseRemovalActor.scala index 842ccdab4..1f23e6022 100644 --- a/src/main/scala/net/psforever/services/avatar/support/CorpseRemovalActor.scala +++ b/src/main/scala/net/psforever/services/avatar/support/CorpseRemovalActor.scala @@ -5,7 +5,6 @@ import akka.actor.{ActorContext, ActorRef, Props} import net.psforever.objects.guid.{GUIDTask, TaskBundle} import net.psforever.objects.Player import net.psforever.types.{ExoSuitType, PlanetSideGUID} -import net.psforever.services.Service import net.psforever.services.avatar.AvatarAction.Release import net.psforever.services.base.envelope.MessageEnvelope import net.psforever.services.base.{EventServiceSupport, GenericSupportEnvelope, GenericSupportEnvelopeOnly} @@ -37,7 +36,7 @@ final case class ReleaseEnvelope( object ReleaseEnvelope { def apply(channel: String, actionMessage: Release): ReleaseEnvelope = - ReleaseEnvelope(channel, Service.defaultPlayerGUID, actionMessage) + ReleaseEnvelope(channel, actionMessage.player.GUID, actionMessage) } final case class CorpseEnvelope(supportMessage: Any) diff --git a/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala b/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala index 231ca3104..38616a0ea 100644 --- a/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala +++ b/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala @@ -63,7 +63,6 @@ class CaptureFlagManager(zone: Zone) extends Actor { // Override CC message when looked at zone.LocalEvents ! MessageEnvelope( zone.id, - PlanetSideGUID(-1), GenericObjectAction( capture_terminal.GUID, GenericObjectActionEnum.FlagSpawned.id diff --git a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala index 06c606f30..f0af48a72 100644 --- a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala +++ b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala @@ -246,7 +246,7 @@ class HackCaptureActor extends Actor { // Notify all clients that CC has had its hack state changed terminal.Zone.LocalEvents ! MessageEnvelope( terminal.Zone.id, - PlanetSideGUID(-1), + PlanetSideGUID(-1), /*what is this?, says the person who wrote it*/ PlanetsideAttribute( terminal.GUID, PlanetsideAttributeEnum.ControlConsoleHackUpdate.id,