From 39ad0fa997ce87957b34a24e97fb9829645a8c61 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 13 Mar 2023 17:17:44 -0400 Subject: [PATCH] further changes to session actor handler classes --- .../actors/session/SessionActor.scala | 4 +- .../support/SessionAvatarHandlers.scala | 454 ++++++++++-------- .../actors/session/support/SessionData.scala | 24 +- .../support/SessionLocalHandlers.scala | 25 +- .../support/SessionMountHandlers.scala | 138 ++++-- .../support/SessionTerminalHandlers.scala | 95 ++-- .../support/SessionVehicleHandlers.scala | 34 +- .../session/support/ZoningOperations.scala | 7 +- .../services/local/LocalService.scala | 8 +- .../services/local/LocalServiceResponse.scala | 8 +- 10 files changed, 467 insertions(+), 330 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index ddbcaf165..82e2d2d8e 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -283,8 +283,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con sessionFuncs.vehicles.handleCanNotChangeDeployment(obj, state, reason) /* rare messages */ - case ProximityUnit.StopAction(term, target) => - sessionFuncs.terminals.LocalStopUsingProximityUnit(term, target) + case ProximityUnit.StopAction(term, _) => + sessionFuncs.terminals.LocalStopUsingProximityUnit(term) case SessionActor.Suicide() => sessionFuncs.suicide(sessionFuncs.player) 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 2b43bf630..5f6d03ce0 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala @@ -32,7 +32,10 @@ class SessionAvatarHandlers( implicit val context: ActorContext ) extends CommonSessionInterfacingFunctionality { //TODO player characters only exist within a certain range of GUIDs for a given zone; this is overkill - private[support] var lastSeenStreamMessage: Array[Long] = Array.fill[Long](65535)(elem = 0L) + private[support] var lastSeenStreamMessage: Array[SessionAvatarHandlers.LastUpstream] = { + SessionAvatarHandlers.blankUpstreamMessages(65535) + } + private[this] val hidingPlayerRandomizer = new scala.util.Random /** * na @@ -47,12 +50,15 @@ class SessionAvatarHandlers( Service.defaultPlayerGUID } val isNotSameTarget = resolvedPlayerGuid != guid + val isSameTarget = !isNotSameTarget reply match { + /* special messages */ case AvatarResponse.TeardownConnection() => log.trace(s"ending ${player.Name}'s old session by event system request (relog)") context.stop(context.self) - case AvatarResponse.PlayerState( + /* really common messages (very frequently, every life) */ + case pstate @ AvatarResponse.PlayerState( pos, vel, yaw, @@ -68,12 +74,10 @@ class SessionAvatarHandlers( ) if isNotSameTarget => val now = System.currentTimeMillis() val (location, time, distanceSq): (Vector3, Long, Float) = if (spectating) { - val r = new scala.util.Random - val r1 = 2 + r.nextInt(30).toFloat - val r2 = 2 + r.nextInt(4000).toFloat - (Vector3(r2, r2, r1), 0L, 0f) + val r2 = 2 + hidingPlayerRandomizer.nextInt(4000).toFloat + (Vector3(r2, r2, 1f), 0L, 0f) } else { - val before = lastSeenStreamMessage(guid.guid) + val before = lastSeenStreamMessage(guid.guid).time val dist = Vector3.DistanceSquared(player.Position, pos) (pos, now - before, dist) } @@ -86,157 +90,45 @@ class SessionAvatarHandlers( yaw, pitch, yawUpper, - timestamp=0, + timestamp = 0, isCrouching, isJumping, jumpThrust, isCloaking ) ) - lastSeenStreamMessage(guid.guid) = now + lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstate), now) } - case AvatarResponse.SendResponse(msg) => - sendResponse(msg) - - case AvatarResponse.SendResponseTargeted(targetGuid, msg) if resolvedPlayerGuid == targetGuid => - sendResponse(msg) - - case AvatarResponse.Revive(revivalTargetGuid) if resolvedPlayerGuid == revivalTargetGuid => - log.info(s"No time for rest, ${player.Name}. Back on your feet!") - sessionData.zoning.spawn.reviveTimer.cancel() - sessionData.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 ! AvatarServiceMessage( - continent.id, - AvatarAction.PlanetsideAttributeToAll(revivalTargetGuid, attribute_type=0, health) - ) - - case AvatarResponse.ConcealPlayer() => - sendResponse(GenericObjectActionMessage(guid, code=9)) - - case AvatarResponse.EnvironmentalDamage(_, _, _) => - //TODO damage marker? - sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg") - - case AvatarResponse.Destroy(victim, killer, weapon, pos) => - // guid = victim // killer = killer - sendResponse(DestroyMessage(victim, killer, weapon, pos)) - - case AvatarResponse.DestroyDisplay(killer, victim, method, unk) => - sendResponse(sessionData.destroyDisplayMessage(killer, victim, method, unk)) - // TODO Temporary thing that should go somewhere else and use proper xp values - if (killer.CharId == avatar.id && killer.Faction != victim.Faction) { - avatarActor ! AvatarActor.AwardBep((1000 * Config.app.game.bepRate).toLong, ExperienceType.Normal) - avatarActor ! AvatarActor.AwardCep((100 * Config.app.game.cepRate).toLong) - } - - case AvatarResponse.DropItem(pkt) if isNotSameTarget => - sendResponse(pkt) - - case AvatarResponse.EquipmentInHand(pkt) if isNotSameTarget => - sendResponse(pkt) - - case AvatarResponse.GenericObjectAction(objectGuid, actionCode) if isNotSameTarget => - sendResponse(GenericObjectActionMessage(objectGuid, actionCode)) - - case AvatarResponse.HitHint(source_guid) if player.isAlive => - sendResponse(HitHint(source_guid, guid)) - sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg") - - case AvatarResponse.DropSpecialItem() => - sessionData.dropSpecialSlotItem() - - case AvatarResponse.Killed(mount) => - val cause = (player.LastDamage match { - case Some(reason) => (Some(reason), reason.adversarial) - case None => (None, None) - }) match { - case (_, Some(adversarial)) => adversarial.attacker.Name - case (Some(reason), None) => s"a ${reason.interaction.cause.getClass.getSimpleName}" - case _ => s"an unfortunate circumstance (probably ${player.Sex.pronounObject} own fault)" - } - log.info(s"${player.Name} has died, killed by $cause") - val respawnTimer = 300.seconds - //drop free hand item - player.FreeHand.Equipment.foreach { item => - DropEquipmentFromInventory(player)(item) - } - sessionData.dropSpecialSlotItem() - sessionData.toggleMaxSpecialState(enable = false) - - if (player.LastDamage.flatMap { damage => - Some(damage.interaction.cause match { - case cause: ExplodingEntityReason => cause.entity.isInstanceOf[VehicleSpawnPad] - case _ => false - }) }.contains(true) - ) { - //also, @SVCP_Killed_TooCloseToPadOnCreate^n~ or "... within n meters of pad ..." - sendResponse(ChatMsg(ChatMessageType.UNK_227, wideContents = false, "", "@SVCP_Killed_OnPadOnCreate", None)) - } - sessionData.keepAliveFunc = sessionData.zoning.NormalKeepAlive - sessionData.zoning.zoningStatus = Zoning.Status.None - sessionData.zoning.spawn.deadState = DeadState.Dead - continent.GUID(mount).collect { case obj: Vehicle => - sessionData.vehicles.ConditionalDriverVehicleControl(obj) - sessionData.vehicles.serverVehicleControlVelocity = None - sessionData.unaccessContainer(obj) - } - sessionData.playerActionsToCancel() - sessionData.terminals.CancelAllProximityUnits() - sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel") - if (sessionData.shooting.shotsWhileDead > 0) { - log.warn( - s"KillPlayer/SHOTS_WHILE_DEAD: client of ${avatar.name} fired ${sessionData.shooting.shotsWhileDead} rounds while character was dead on server" - ) - sessionData.shooting.shotsWhileDead = 0 - } - sessionData.zoning.spawn.reviveTimer.cancel() - if (player.death_by == 0) { - sessionData.zoning.spawn.reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer) { - sessionData.cluster ! ICS.GetRandomSpawnPoint( - Zones.sanctuaryZoneNumber(player.Faction), - player.Faction, - Seq(SpawnGroup.Sanctuary), - context.self - ) - } - } else { - sessionData.zoning.spawn.HandleReleaseAvatar(player, continent) - } - AvatarActor.savePlayerLocation(player) - sessionData.renewCharSavedTimer(fixedLen = 1800L, varLen = 0L) - - case AvatarResponse.LoadPlayer(pkt) if isNotSameTarget => - sendResponse(pkt) - - case AvatarResponse.LoadProjectile(pkt) if isNotSameTarget => - sendResponse(pkt) - - case AvatarResponse.ObjectDelete(itemGuid, unk) if isNotSameTarget => - sendResponse(ObjectDeleteMessage(itemGuid, unk)) - - case AvatarResponse.ObjectHeld(slot, _) if resolvedPlayerGuid == guid && slot > -1 => + case AvatarResponse.ObjectHeld(slot, _) + if isSameTarget && player.VisibleSlots.contains(slot) => sendResponse(ObjectHeldMessage(guid, slot, unk1=true)) //Stop using proximity terminals if player unholsters a weapon - if (player.VisibleSlots.contains(slot)) { - continent.GUID(sessionData.terminals.usingMedicalTerminal).collect { - case term: Terminal with ProximityUnit => - sessionData.terminals.StopUsingProximityUnit(term) - } + continent.GUID(sessionData.terminals.usingMedicalTerminal).collect { + case term: Terminal with ProximityUnit => sessionData.terminals.StopUsingProximityUnit(term) } + case AvatarResponse.ObjectHeld(slot, _) + if isSameTarget && slot > -1 => + sendResponse(ObjectHeldMessage(guid, slot, unk1=true)) + + case AvatarResponse.ObjectHeld(_, _) + if isSameTarget => () + case AvatarResponse.ObjectHeld(_, previousSlot) => sendResponse(ObjectHeldMessage(guid, previousSlot, unk1=false)) - case AvatarResponse.OxygenState(player, vehicle) => - sendResponse(OxygenStateMessage( - DrowningTarget(player.guid, player.progress, player.state), - vehicle.flatMap { vinfo => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state)) } - )) + case AvatarResponse.ChangeFireState_Start(weaponGuid) if isNotSameTarget => + sendResponse(ChangeFireStateMessage_Start(weaponGuid)) + + case AvatarResponse.ChangeFireState_Stop(weaponGuid) if isNotSameTarget => + sendResponse(ChangeFireStateMessage_Stop(weaponGuid)) + + case AvatarResponse.LoadPlayer(pkt) if isNotSameTarget => + sendResponse(pkt) + + case AvatarResponse.EquipmentInHand(pkt) if isNotSameTarget => + sendResponse(pkt) case AvatarResponse.PlanetsideAttribute(attributeType, attributeValue) if isNotSameTarget => sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue)) @@ -244,73 +136,43 @@ class SessionAvatarHandlers( case AvatarResponse.PlanetsideAttributeToAll(attributeType, attributeValue) => sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue)) - case AvatarResponse.PlanetsideAttributeSelf(attributeType, attributeValue) if resolvedPlayerGuid == guid => + case AvatarResponse.PlanetsideAttributeSelf(attributeType, attributeValue) if isSameTarget => sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue)) - case AvatarResponse.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 AvatarResponse.GenericObjectAction(objectGuid, actionCode) if isNotSameTarget => + sendResponse(GenericObjectActionMessage(objectGuid, actionCode)) + + case AvatarResponse.HitHint(sourceGuid) if player.isAlive => + sendResponse(HitHint(sourceGuid, guid)) + sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg") + + case AvatarResponse.DestroyDisplay(killer, victim, method, unk) + if killer.CharId == avatar.id && killer.Faction != victim.Faction => + // TODO Temporary thing that should go somewhere else and use proper xp values + sendResponse(sessionData.destroyDisplayMessage(killer, victim, method, unk)) + avatarActor ! AvatarActor.AwardBep((1000 * Config.app.game.bepRate).toLong, ExperienceType.Normal) + avatarActor ! AvatarActor.AwardCep((100 * Config.app.game.cepRate).toLong) + + case AvatarResponse.Destroy(victim, killer, weapon, pos) => + // guid = victim // killer = killer + sendResponse(DestroyMessage(victim, killer, weapon, pos)) + + case AvatarResponse.DestroyDisplay(killer, victim, method, unk) => + sendResponse(sessionData.destroyDisplayMessage(killer, victim, method, unk)) + + case AvatarResponse.TerminalOrderResult(terminalGuid, action, result) + if result && (action == TransactionType.Buy || action == TransactionType.Loadout) => + sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) + sessionData.terminals.lastTerminalOrderFulfillment = true + AvatarActor.savePlayerData(player) + sessionData.renewCharSavedTimer( + Config.app.game.savedMsg.interruptedByAction.fixed, + Config.app.game.savedMsg.interruptedByAction.variable ) - sendResponse(ObjectDeleteMessage(projectileGuid, unk1=2)) - - case AvatarResponse.ProjectileAutoLockAwareness(mode) => - sendResponse(GenericActionMessage(mode)) - - case AvatarResponse.ProjectileState(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid) if isNotSameTarget => - sendResponse(ProjectileStateMessage(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid)) - - case AvatarResponse.PutDownFDU(target) if isNotSameTarget => - sendResponse(GenericObjectActionMessage(target, code=53)) - - case AvatarResponse.Release(tplayer) if isNotSameTarget => - sessionData.zoning.spawn.DepictPlayerAsCorpse(tplayer) - - case AvatarResponse.Reload(itemGuid) if isNotSameTarget => - sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0)) - - case AvatarResponse.SetEmpire(objectGuid, faction) if isNotSameTarget => - sendResponse(SetEmpireMessage(objectGuid, faction)) - - case AvatarResponse.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 AvatarResponse.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 not see it firing - sendResponse(WeaponDryFireMessage(weaponGuid)) - case _ => - sendResponse(WeaponDryFireMessage(weaponGuid)) - } case AvatarResponse.TerminalOrderResult(terminalGuid, action, result) => sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) sessionData.terminals.lastTerminalOrderFulfillment = true - if (result && - (action == TransactionType.Buy || action == TransactionType.Loadout)) { - AvatarActor.savePlayerData(player) - sessionData.renewCharSavedTimer( - Config.app.game.savedMsg.interruptedByAction.fixed, - Config.app.game.savedMsg.interruptedByAction.variable - ) - } case AvatarResponse.ChangeExosuit( target, @@ -425,13 +287,13 @@ class SessionAvatarHandlers( DropLeftovers(player)(drops) case AvatarResponse.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)) } - //redraw handled by callback case AvatarResponse.UseKit(kguid, kObjId) => sendResponse( @@ -456,9 +318,191 @@ class SessionAvatarHandlers( case AvatarResponse.KitNotUsed(_, msg) => sessionData.kitToBeUsed = None - sendResponse(ChatMsg(ChatMessageType.UNK_225, wideContents=false, recipient="", msg, note=None)) + sendResponse(ChatMsg(ChatMessageType.UNK_225, msg)) + + case AvatarResponse.SendResponse(msg) => + sendResponse(msg) + + case AvatarResponse.SendResponseTargeted(targetGuid, msg) if resolvedPlayerGuid == targetGuid => + sendResponse(msg) + + /* common messages (maybe once every respawn) */ + case AvatarResponse.Reload(itemGuid) if isNotSameTarget => + sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0)) + + case AvatarResponse.Killed(mount) => + //log and chat messages + val cause = player.LastDamage.flatMap { damage => + damage.interaction.cause match { + case cause: ExplodingEntityReason if cause.entity.isInstanceOf[VehicleSpawnPad] => + //also, @SVCP_Killed_TooCloseToPadOnCreate^n~ or "... within n meters of pad ..." + sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SVCP_Killed_OnPadOnCreate")) + case _ => () + } + damage match { + case damage if damage.adversarial.nonEmpty => Some(damage.adversarial.get.attacker.Name) + case damage => Some(s"a ${damage.interaction.cause.getClass.getSimpleName}") + } + }.getOrElse { s"an unfortunate circumstance (probably ${player.Sex.pronounObject} own fault)" } + log.info(s"${player.Name} has died, killed by $cause") + if (sessionData.shooting.shotsWhileDead > 0) { + log.warn( + s"SHOTS_WHILE_DEAD: client of ${avatar.name} fired ${sessionData.shooting.shotsWhileDead} rounds while character was dead on server" + ) + sessionData.shooting.shotsWhileDead = 0 + } + sessionData.zoning.CancelZoningProcessWithDescriptiveReason(msg = "cancel") + sessionData.renewCharSavedTimer(fixedLen = 1800L, varLen = 0L) + + //player state changes + player.FreeHand.Equipment.foreach { item => + DropEquipmentFromInventory(player)(item) + } + sessionData.dropSpecialSlotItem() + sessionData.toggleMaxSpecialState(enable = false) + sessionData.keepAliveFunc = sessionData.zoning.NormalKeepAlive + sessionData.zoning.zoningStatus = Zoning.Status.None + sessionData.zoning.spawn.deadState = DeadState.Dead + continent.GUID(mount).collect { case obj: Vehicle => + sessionData.vehicles.ConditionalDriverVehicleControl(obj) + sessionData.vehicles.serverVehicleControlVelocity = None + sessionData.unaccessContainer(obj) + } + sessionData.playerActionsToCancel() + sessionData.terminals.CancelAllProximityUnits() + AvatarActor.savePlayerLocation(player) + + //respawn + val respawnTimer = 300.seconds + sessionData.zoning.spawn.reviveTimer.cancel() + if (player.death_by == 0) { + sessionData.zoning.spawn.reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer) { + sessionData.cluster ! ICS.GetRandomSpawnPoint( + Zones.sanctuaryZoneNumber(player.Faction), + player.Faction, + Seq(SpawnGroup.Sanctuary), + context.self + ) + } + } else { + sessionData.zoning.spawn.HandleReleaseAvatar(player, continent) + } + + case AvatarResponse.Release(tplayer) if isNotSameTarget => + sessionData.zoning.spawn.DepictPlayerAsCorpse(tplayer) + + case AvatarResponse.Revive(revivalTargetGuid) if resolvedPlayerGuid == revivalTargetGuid => + log.info(s"No time for rest, ${player.Name}. Back on your feet!") + sessionData.zoning.spawn.reviveTimer.cancel() + sessionData.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 ! AvatarServiceMessage( + continent.id, + AvatarAction.PlanetsideAttributeToAll(revivalTargetGuid, attribute_type=0, health) + ) + + /* uncommon messages (utility, or once in a while) */ + case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) + if isNotSameTarget => + sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3.Zero, 0)) + sendResponse( + ObjectCreateMessage( + ammo_id, + ammo_guid, + ObjectCreateMessageParent(weapon_guid, weapon_slot), + ammo_data + ) + ) + sendResponse(ChangeAmmoMessage(weapon_guid, 1)) + + case AvatarResponse.ChangeFireMode(itemGuid, mode) if isNotSameTarget => + sendResponse(ChangeFireModeMessage(itemGuid, mode)) + + case AvatarResponse.ConcealPlayer() => + sendResponse(GenericObjectActionMessage(guid, code=9)) + + case AvatarResponse.EnvironmentalDamage(_, _, _) => + //TODO damage marker? + sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg") + + case AvatarResponse.DropItem(pkt) if isNotSameTarget => + sendResponse(pkt) + + case AvatarResponse.ObjectDelete(itemGuid, unk) if isNotSameTarget => + sendResponse(ObjectDeleteMessage(itemGuid, unk)) + + /* rare messages */ + case AvatarResponse.SetEmpire(objectGuid, faction) if isNotSameTarget => + sendResponse(SetEmpireMessage(objectGuid, faction)) + + case AvatarResponse.DropSpecialItem() => + sessionData.dropSpecialSlotItem() + + case AvatarResponse.OxygenState(player, vehicle) => + sendResponse(OxygenStateMessage( + DrowningTarget(player.guid, player.progress, player.state), + vehicle.flatMap { vinfo => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state)) } + )) + + case AvatarResponse.LoadProjectile(pkt) if isNotSameTarget => + sendResponse(pkt) + + case AvatarResponse.ProjectileState(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid) if isNotSameTarget => + sendResponse(ProjectileStateMessage(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid)) + + case AvatarResponse.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 AvatarResponse.ProjectileAutoLockAwareness(mode) => + sendResponse(GenericActionMessage(mode)) + + case AvatarResponse.PutDownFDU(target) if isNotSameTarget => + sendResponse(GenericObjectActionMessage(target, code=53)) + + case AvatarResponse.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 AvatarResponse.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 not see it firing + sendResponse(WeaponDryFireMessage(weaponGuid)) + case _ => + sendResponse(WeaponDryFireMessage(weaponGuid)) + } case _ => () } } } + +object SessionAvatarHandlers { + private[support] case class LastUpstream(msg: Option[AvatarResponse.PlayerState], time: Long) + + private[support] def blankUpstreamMessages(n: Int): Array[LastUpstream] = { + Array.fill[SessionAvatarHandlers.LastUpstream](n)(elem = LastUpstream(None, 0L)) + } +} diff --git a/src/main/scala/net/psforever/actors/session/support/SessionData.scala b/src/main/scala/net/psforever/actors/session/support/SessionData.scala index c0fdaf8a2..37913f07d 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionData.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionData.scala @@ -257,10 +257,7 @@ class SessionData( } case None => () } - val wepInHand: Boolean = player.Slot(player.DrawnSlot).Equipment match { - case Some(item) => item.Definition == GlobalDefinitions.bolt_driver - case _ => false - } + val eagleEye: Boolean = canSeeReallyFar continent.AvatarEvents ! AvatarServiceMessage( continent.id, AvatarAction.PlayerState( @@ -276,7 +273,7 @@ class SessionData( jumpThrust, isCloaking, player.spectator, - wepInHand + eagleEye ) ) squad.updateSquad() @@ -2910,6 +2907,23 @@ class SessionData( heightLast = zHeight } + def canSeeReallyFar: Boolean = { + findEquipment().exists { + case weapon: Tool + if weapon.Size == EquipmentSize.Rifle && + (weapon.Projectile ne GlobalDefinitions.no_projectile) && + player.Crouching && + player.avatar + .implants + .exists { p => + p.collect { implant => implant.definition.implantType == ImplantType.RangeMagnifier && implant.initialized }.nonEmpty + } => + true + case item => + item.Definition == GlobalDefinitions.bolt_driver || item.Definition == GlobalDefinitions.heavy_sniper + } + } + def displayCharSavedMsgThenRenewTimer(fixedLen: Long, varLen: Long): Unit = { charSaved() renewCharSavedTimer(fixedLen, varLen) 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 5cf54218b..6d78bd875 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala @@ -114,16 +114,16 @@ class SessionLocalHandlers( case LocalResponse.HackObject(targetGuid, unk1, unk2) => HackObject(targetGuid, unk1, unk2) - case LocalResponse.SendPlanetsideAttributeMessage(targetGuid, attributeType, attributeValue) => + case LocalResponse.PlanetsideAttribute(targetGuid, attributeType, attributeValue) => SendPlanetsideAttributeMessage(targetGuid, attributeType, attributeValue) - case LocalResponse.SendGenericObjectActionMessage(targetGuid, actionNumber) => + case LocalResponse.GenericObjectAction(targetGuid, actionNumber) => sendResponse(GenericObjectActionMessage(targetGuid, actionNumber)) - case LocalResponse.SendGenericActionMessage(actionNumber) => + case LocalResponse.GenericActionMessage(actionNumber) => sendResponse(GenericActionMessage(actionNumber)) - case LocalResponse.SendChatMsg(msg) => + case LocalResponse.ChatMessage(msg) => sendResponse(msg) case LocalResponse.SendPacket(packet) => @@ -142,7 +142,7 @@ class SessionLocalHandlers( 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 - sessionData.specialItemSlotGuid.foreach { case guid if guid == lluGuid => + sessionData.specialItemSlotGuid.collect { case guid if guid == lluGuid => sessionData.specialItemSlotGuid = None player.Carrying = None } @@ -198,20 +198,19 @@ class SessionLocalHandlers( case LocalResponse.TriggerSound(sound, pos, unk, volume) => sendResponse(TriggerSoundMessage(sound, pos, unk, volume)) - case LocalResponse.UpdateForceDomeStatus(buildingGuid, activated) if activated => + case LocalResponse.UpdateForceDomeStatus(buildingGuid, true) => sendResponse(GenericObjectActionMessage(buildingGuid, 11)) - case LocalResponse.UpdateForceDomeStatus(buildingGuid, _) => + case LocalResponse.UpdateForceDomeStatus(buildingGuid, false) => sendResponse(GenericObjectActionMessage(buildingGuid, 12)) case LocalResponse.RechargeVehicleWeapon(vehicleGuid, weaponGuid) if resolvedPlayerGuid == guid => - continent.GUID(vehicleGuid).collect { case vehicle: MountableWeapons => - vehicle.PassengerInSeat(player).collect { seat_num => - vehicle.WeaponControlledFromSeat(seat_num).collect { case weapon: Tool if weapon.GUID == weaponGuid => - sendResponse(InventoryStateMessage(weapon.AmmoSlot.Box.GUID, weapon.GUID, weapon.Magazine)) - } + continent.GUID(vehicleGuid) + .collect { case vehicle: MountableWeapons => (vehicle, vehicle.PassengerInSeat(player)) } + .collect { case (vehicle, Some(seat_num)) => vehicle.WeaponControlledFromSeat(seat_num) } + .collect { case weapon: Tool if weapon.GUID == weaponGuid => + sendResponse(InventoryStateMessage(weapon.AmmoSlot.Box.GUID, weapon.GUID, weapon.Magazine)) } - } case _ => () } diff --git a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala index e33fb97bb..23d18afb9 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala @@ -46,54 +46,118 @@ class SessionMountHandlers( MountingAction(tplayer, obj, seatNumber) sessionData.keepAliveFunc = sessionData.keepAlivePersistence - case Mountable.CanMount(obj: Vehicle, seatNumber, _) => + case Mountable.CanMount(obj: Vehicle, seatNumber, _) + if obj.Definition == GlobalDefinitions.ant => sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") - log.info(s"${player.Name} mounts the ${obj.Definition.Name} in ${ - obj.SeatPermissionGroup(seatNumber) match { - case Some(AccessPermissionGroup.Driver) => "the driver seat" - case Some(seatType) => s"a $seatType seat (#$seatNumber)" - case None => "a seat" - } - }") + log.info(s"${player.Name} mounts the driver seat of the ${obj.Definition.Name}") val obj_guid: PlanetSideGUID = obj.GUID sessionData.terminals.CancelAllProximityUnits() sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health)) sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields)) - if (obj.Definition == GlobalDefinitions.ant) { - sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=45, obj.NtuCapacitorScaled)) - } - if (obj.Definition.MaxCapacitor > 0) { - sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=113, obj.Capacitor)) - } - if (seatNumber == 0) { - if (obj.Definition == GlobalDefinitions.quadstealth) { - //wraith cloak state matches the cloak state of the driver - //phantasm doesn't uncloak if the driver is uncloaked and no other vehicle cloaks - obj.Cloaked = tplayer.Cloaked - } - sendResponse(GenericObjectActionMessage(obj_guid, code=11)) - } else if (obj.WeaponControlledFromSeat(seatNumber).isEmpty) { - sessionData.keepAliveFunc = sessionData.keepAlivePersistence - } + sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=45, obj.NtuCapacitorScaled)) + sendResponse(GenericObjectActionMessage(obj_guid, code=11)) + MountingAction(tplayer, obj, seatNumber) + + case Mountable.CanMount(obj: Vehicle, seatNumber, _) + if obj.Definition == GlobalDefinitions.quadstealth => + sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") + log.info(s"${player.Name} mounts the driver seat of the ${obj.Definition.Name}") + val obj_guid: PlanetSideGUID = obj.GUID + sessionData.terminals.CancelAllProximityUnits() + sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health)) + sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields)) + //exclusive to the wraith, cloak state matches the cloak state of the driver + //phantasm doesn't uncloak if the driver is uncloaked and no other vehicle cloaks + obj.Cloaked = tplayer.Cloaked + sendResponse(GenericObjectActionMessage(obj_guid, code=11)) + sessionData.accessContainer(obj) + MountingAction(tplayer, obj, seatNumber) + + case Mountable.CanMount(obj: Vehicle, seatNumber, _) + if seatNumber == 0 && obj.Definition.MaxCapacitor > 0 => + sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") + log.info(s"${player.Name} mounts the driver seat of the ${obj.Definition.Name}") + val obj_guid: PlanetSideGUID = obj.GUID + sessionData.terminals.CancelAllProximityUnits() + sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health)) + sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields)) + sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=113, obj.Capacitor)) + sendResponse(GenericObjectActionMessage(obj_guid, code=11)) sessionData.accessContainer(obj) sessionData.updateWeaponAtSeatPosition(obj, seatNumber) MountingAction(tplayer, obj, seatNumber) - case Mountable.CanMount(obj: FacilityTurret, seatNumber, _) => + case Mountable.CanMount(obj: Vehicle, seatNumber, _) + if seatNumber == 0 => sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") - if (!obj.isUpgrading) { - log.info(s"${player.Name} mounts the ${obj.Definition.Name}") - if (obj.Definition == GlobalDefinitions.vanu_sentry_turret) { - obj.Zone.LocalEvents ! LocalServiceMessage(obj.Zone.id, LocalAction.SetEmpire(obj.GUID, player.Faction)) + log.info(s"${player.Name} mounts the driver seat of the ${obj.Definition.Name}") + val obj_guid: PlanetSideGUID = obj.GUID + sessionData.terminals.CancelAllProximityUnits() + sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health)) + sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields)) + sendResponse(GenericObjectActionMessage(obj_guid, code=11)) + sessionData.accessContainer(obj) + sessionData.updateWeaponAtSeatPosition(obj, seatNumber) + MountingAction(tplayer, obj, seatNumber) + + case Mountable.CanMount(obj: Vehicle, seatNumber, _) + if obj.Definition.MaxCapacitor > 0 => + sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") + log.info(s"${player.Name} mounts ${ + obj.SeatPermissionGroup(seatNumber) match { + case Some(seatType) => s"a $seatType seat (#$seatNumber)" + case None => "a seat" } - sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health)) - sessionData.updateWeaponAtSeatPosition(obj, seatNumber) - MountingAction(tplayer, obj, seatNumber) - } else { - log.warn( - s"MountVehicleMsg: ${tplayer.Name} wants to mount turret ${obj.GUID.guid}, but needs to wait until it finishes updating" - ) - } + } of the ${obj.Definition.Name}") + val obj_guid: PlanetSideGUID = obj.GUID + sessionData.terminals.CancelAllProximityUnits() + sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health)) + sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields)) + sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=113, obj.Capacitor)) + sessionData.accessContainer(obj) + sessionData.updateWeaponAtSeatPosition(obj, seatNumber) + sessionData.keepAliveFunc = sessionData.keepAlivePersistence + MountingAction(tplayer, obj, seatNumber) + + case Mountable.CanMount(obj: Vehicle, seatNumber, _) => + sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") + log.info(s"${player.Name} mounts the ${ + obj.SeatPermissionGroup(seatNumber) match { + case Some(seatType) => s"a $seatType seat (#$seatNumber)" + case None => "a seat" + } + } of the ${obj.Definition.Name}") + val obj_guid: PlanetSideGUID = obj.GUID + sessionData.terminals.CancelAllProximityUnits() + sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health)) + sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields)) + sessionData.accessContainer(obj) + sessionData.updateWeaponAtSeatPosition(obj, seatNumber) + sessionData.keepAliveFunc = sessionData.keepAlivePersistence + MountingAction(tplayer, obj, seatNumber) + + case Mountable.CanMount(obj: FacilityTurret, seatNumber, _) + if obj.Definition == GlobalDefinitions.vanu_sentry_turret => + sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") + log.info(s"${player.Name} mounts the ${obj.Definition.Name}") + obj.Zone.LocalEvents ! LocalServiceMessage(obj.Zone.id, LocalAction.SetEmpire(obj.GUID, player.Faction)) + sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health)) + sessionData.updateWeaponAtSeatPosition(obj, seatNumber) + MountingAction(tplayer, obj, seatNumber) + + case Mountable.CanMount(obj: FacilityTurret, seatNumber, _) + if !obj.isUpgrading => + sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") + log.info(s"${player.Name} mounts the ${obj.Definition.Name}") + sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health)) + sessionData.updateWeaponAtSeatPosition(obj, seatNumber) + MountingAction(tplayer, obj, seatNumber) + + case Mountable.CanMount(obj: FacilityTurret, _, _) => + sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") + log.warn( + s"MountVehicleMsg: ${tplayer.Name} wants to mount turret ${obj.GUID.guid}, but needs to wait until it finishes updating" + ) case Mountable.CanMount(obj: PlanetSideGameObject with WeaponTurret, seatNumber, _) => sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") diff --git a/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala index 1bac4329a..de2845420 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala @@ -28,19 +28,19 @@ class SessionTerminalHandlers( /* packets */ def handleItemTransaction(pkt: ItemTransactionMessage): Unit = { - val ItemTransactionMessage(terminalGuid, _, _, _, _, _) = pkt + val ItemTransactionMessage(terminalGuid, transactionType, _, itemName, _, _) = pkt continent.GUID(terminalGuid) match { - case Some(term: Terminal) => - if (lastTerminalOrderFulfillment) { - log.trace(s"ItemTransactionMessage: ${player.Name} is submitting an order") - lastTerminalOrderFulfillment = false - sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use") - term.Actor ! Terminal.Request(player, pkt) - } - case Some(obj: PlanetSideGameObject) => - log.error(s"ItemTransaction: $obj is not a terminal, ${player.Name}") + case Some(term: Terminal) if lastTerminalOrderFulfillment => + log.info(s"${player.Name} is submitting an order - $transactionType of $itemName") + lastTerminalOrderFulfillment = false + sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use") + term.Actor ! Terminal.Request(player, pkt) + case Some(_: Terminal) => + log.warn(s"Please Wait until your previous order has been fulfilled, ${player.Name}") + case Some(obj) => + log.error(s"ItemTransaction: ${obj.Definition.Name} is not a terminal, ${player.Name}") case _ => - log.error(s"ItemTransaction: $terminalGuid does not exist, ${player.Name}") + log.error(s"ItemTransaction: entity with guid=${terminalGuid.guid} does not exist, ${player.Name}") } } @@ -50,9 +50,9 @@ class SessionTerminalHandlers( case Some(obj: Terminal with ProximityUnit) => HandleProximityTerminalUse(obj) case Some(obj) => - log.warn(s"ProximityTerminalUse: $obj does not have proximity effects for ${player.Name}") + log.warn(s"ProximityTerminalUse: ${obj.Definition.Name} guid=${objectGuid.guid} is not ready to implement proximity effects") case None => - log.error(s"ProximityTerminalUse: ${player.Name} can not find an object with guid $objectGuid") + log.error(s"ProximityTerminalUse: ${player.Name} can not find an object with guid ${objectGuid.guid}") } } @@ -66,7 +66,8 @@ class SessionTerminalHandlers( */ def handle(tplayer: Player, msg: ItemTransactionMessage, order: Terminal.Exchange): Unit = { order match { - case Terminal.BuyEquipment(item) if tplayer.avatar.purchaseCooldown(item.Definition).nonEmpty => + case Terminal.BuyEquipment(item) + if tplayer.avatar.purchaseCooldown(item.Definition).nonEmpty => lastTerminalOrderFulfillment = true sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false)) @@ -100,19 +101,16 @@ class SessionTerminalHandlers( avatarActor ! AvatarActor.SellImplant(msg.terminal_guid, implant) lastTerminalOrderFulfillment = true - case Terminal.BuyVehicle(vehicle, _, _) if tplayer.avatar.purchaseCooldown(vehicle.Definition).nonEmpty => + case Terminal.BuyVehicle(vehicle, _, _) + if tplayer.avatar.purchaseCooldown(vehicle.Definition).nonEmpty => sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false)) lastTerminalOrderFulfillment = true case Terminal.BuyVehicle(vehicle, weapons, trunk) => continent.map.terminalToSpawnPad .find { case (termid, _) => termid == msg.terminal_guid.guid } - .map { - case (a: Int, b: Int) => (continent.GUID(a), continent.GUID(b)) - case _ => (None, None) - } - .get match { - case (Some(term: Terminal), Some(pad: VehicleSpawnPad)) => + .map { case (a: Int, b: Int) => (continent.GUID(a), continent.GUID(b)) } + .collect { case (Some(term: Terminal), Some(pad: VehicleSpawnPad)) => avatarActor ! AvatarActor.UpdatePurchaseTime(vehicle.Definition) vehicle.Faction = tplayer.Faction vehicle.Position = pad.Position @@ -143,27 +141,30 @@ class SessionTerminalHandlers( if (GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition)) { sendResponse(UnuseItemMessage(player.GUID, msg.terminal_guid)) } - case _ => - log.error( - s"${tplayer.Name} wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it" - ) - sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false)) + }.orElse { + log.error( + s"${tplayer.Name} wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it" + ) + sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false)) + None } - case Terminal.NoDeal() => - val order: String = if (msg == null) { - "missing order" - } else { - s"${msg.transaction_type} order" - } - log.warn(s"NoDeal: ${tplayer.Name} made a request but the terminal rejected the $order") - sendResponse(ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, success = false)) + case Terminal.NoDeal() if msg != null => + val transaction = msg.transaction_type + log.warn(s"NoDeal: ${tplayer.Name} made a request but the terminal rejected the ${transaction.toString} order") + sendResponse(ItemTransactionResultMessage(msg.terminal_guid, transaction, success = false)) lastTerminalOrderFulfillment = true case _ => - val transaction = msg.transaction_type - log.warn(s"n/a: ${tplayer.Name} made a $transaction request but terminal#${msg.terminal_guid.guid} is missing or wrong") - sendResponse(ItemTransactionResultMessage(msg.terminal_guid, transaction, success = false)) + val terminal = msg.terminal_guid.guid + continent.GUID(terminal) match { + case Some(term: Terminal) => + log.warn(s"NoDeal?: ${tplayer.Name} made a request but the ${term.Definition.Name}#$terminal rejected the missing order") + case Some(_) => + log.warn(s"NoDeal?: ${tplayer.Name} made a request to a non-terminal entity#$terminal") + case None => + log.warn(s"NoDeal?: ${tplayer.Name} made a request to a missing entity#$terminal") + } lastTerminalOrderFulfillment = true } } @@ -239,7 +240,6 @@ class SessionTerminalHandlers( */ def StartUsingProximityUnit(terminal: Terminal with ProximityUnit, target: PlanetSideGameObject): Unit = { val term_guid = terminal.GUID - //log.trace(s"StartUsingProximityUnit: ${player.Name} wants to use ${terminal.Definition.Name}@${term_guid.guid} on $target") if (player.isAlive) { target match { case _: Player => @@ -267,7 +267,7 @@ class SessionTerminalHandlers( */ def StopUsingProximityUnit(terminal: Terminal with ProximityUnit): Unit = { FindProximityUnitTargetsInScope(terminal).foreach { target => - LocalStopUsingProximityUnit(terminal, target) + LocalStopUsingProximityUnit(terminal) terminal.Actor ! CommonMessages.Unuse(player, Some(target)) } } @@ -280,7 +280,7 @@ class SessionTerminalHandlers( * Other sorts of proximity-based units are put on a timer. * @param terminal the proximity-based unit */ - def LocalStopUsingProximityUnit(terminal: Terminal with ProximityUnit, target: PlanetSideGameObject): Unit = { + def LocalStopUsingProximityUnit(terminal: Terminal with ProximityUnit): Unit = { ForgetAllProximityTerminals(terminal.GUID) } @@ -291,12 +291,23 @@ class SessionTerminalHandlers( * @see `postStop` */ def CancelAllProximityUnits(): Unit = { - continent.GUID(usingMedicalTerminal).collect { + usingMedicalTerminal.foreach { CancelAllProximityUnits } + } + + /** + * Cease all current interactions with proximity-based units. + * Pair with `PlayerActionsToCancel`, except when logging out (stopping). + * This operations may invoke callback messages. + * @param guid globally unique identifier for a proximity terminal + * @see `postStop` + */ + def CancelAllProximityUnits(guid: PlanetSideGUID): Unit = { + continent.GUID(guid).collect { case terminal: Terminal with ProximityUnit => FindProximityUnitTargetsInScope(terminal).foreach(target => terminal.Actor ! CommonMessages.Unuse(player, Some(target)) ) - ForgetAllProximityTerminals(usingMedicalTerminal.get) + ForgetAllProximityTerminals(guid) } } 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 5f411b5ea..504399c98 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala @@ -163,10 +163,8 @@ class SessionVehicleHandlers( avatarActor ! AvatarActor.SetVehicle(Some(vehicleGuid)) sendResponse(PlanetsideAttributeMessage(resolvedPlayerGuid, attribute_type=21, vehicleGuid)) - case VehicleResponse.PlanetsideAttribute(vehicleGuid, attributeType, attributeValue) => - if (isNotSameTarget) { - sendResponse(PlanetsideAttributeMessage(vehicleGuid, attributeType, attributeValue)) - } + case VehicleResponse.PlanetsideAttribute(vehicleGuid, attributeType, attributeValue) if isNotSameTarget => + sendResponse(PlanetsideAttributeMessage(vehicleGuid, attributeType, attributeValue)) case VehicleResponse.ResetSpawnPad(padGuid) => sendResponse(GenericObjectActionMessage(padGuid, code=23)) @@ -201,7 +199,7 @@ class SessionVehicleHandlers( case VehicleResponse.KickCargo(vehicle, speed, delay) if player.VehicleSeated.nonEmpty && sessionData.zoning.spawn.deadState == DeadState.Alive && speed > 0 => - val strafe = if (Vehicles.CargoOrientation(vehicle) == 1) { 2 } else { 1 } + val strafe = 1 + Vehicles.CargoOrientation(vehicle) val reverseSpeed = if (strafe > 1) { 0 } else { speed } //strafe or reverse, not both sessionData.vehicles.serverVehicleControlVelocity = Some(reverseSpeed) @@ -236,19 +234,29 @@ class SessionVehicleHandlers( unk8=None )) + case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, _) + if player.VisibleSlots.contains(player.DrawnSlot) => + val vehicle_guid = vehicle.GUID + sessionData.playerActionsToCancel() + sessionData.vehicles.serverVehicleControlVelocity = Some(0) + sessionData.terminals.CancelAllProximityUnits() + player.DrawnSlot = Player.HandsDownSlot + sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, unk1=true)) + continent.AvatarEvents ! AvatarServiceMessage( + continent.id, + AvatarAction.SendResponse(player.GUID, ObjectHeldMessage(player.GUID, player.LastDrawnSlot, unk1=false)) + ) + sendResponse(PlanetsideAttributeMessage(vehicle_guid, attribute_type=22, attribute_value=1L)) //mount points off + sendResponse(PlanetsideAttributeMessage(player.GUID, attribute_type=21, vehicle_guid)) //ownership + vehicle.MountPoints.find { case (_, mp) => mp.seatIndex == 0 }.collect { + case (mountPoint, _) => vehicle.Actor ! Mountable.TryMount(player, mountPoint) + } + case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, _) => val vehicle_guid = vehicle.GUID sessionData.playerActionsToCancel() sessionData.vehicles.serverVehicleControlVelocity = Some(0) sessionData.terminals.CancelAllProximityUnits() - if (player.VisibleSlots.contains(player.DrawnSlot)) { - player.DrawnSlot = Player.HandsDownSlot - sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, unk1=true)) - continent.AvatarEvents ! AvatarServiceMessage( - continent.id, - AvatarAction.SendResponse(player.GUID, ObjectHeldMessage(player.GUID, player.LastDrawnSlot, unk1=false)) - ) - } sendResponse(PlanetsideAttributeMessage(vehicle_guid, attribute_type=22, attribute_value=1L)) //mount points off sendResponse(PlanetsideAttributeMessage(player.GUID, attribute_type=21, vehicle_guid)) //ownership vehicle.MountPoints.find { case (_, mp) => mp.seatIndex == 0 }.collect { 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 7318e6be7..ac0685b87 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -1446,14 +1446,11 @@ class ZoningOperations( Deployables.Disown(continent, avatar, context.self) spawn.drawDeloyableIcon = spawn.RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone sessionData.squad.squadSetup = sessionData.squad.ZoneChangeSquadSetup - val lastSeen = sessionData.avatarResponse.lastSeenStreamMessage - lastSeen.indices.foreach { index => - lastSeen(index) = 0 - } + sessionData.avatarResponse.lastSeenStreamMessage = SessionAvatarHandlers.blankUpstreamMessages(65535) } /** - * Attempt to tranfer to the player's faction-specific sanctuary continent. + * Attempt to transfer to the player's faction-specific sanctuary continent. * If the server thinks the player is already on his sanctuary continent, and dead, * it will disconnect the player under the assumption that an error has occurred. * Eventually, this functionality should support better error-handling before it jumps to the conclusion: diff --git a/src/main/scala/net/psforever/services/local/LocalService.scala b/src/main/scala/net/psforever/services/local/LocalService.scala index ce247b826..e1e18d511 100644 --- a/src/main/scala/net/psforever/services/local/LocalService.scala +++ b/src/main/scala/net/psforever/services/local/LocalService.scala @@ -145,7 +145,7 @@ class LocalService(zone: Zone) extends Actor { LocalServiceResponse( s"/$forChannel/Local", player_guid, - LocalResponse.SendPlanetsideAttributeMessage(target_guid, attribute_number, attribute_value) + LocalResponse.PlanetsideAttribute(target_guid, attribute_number, attribute_value) ) ) case LocalAction.SendGenericObjectActionMessage(player_guid, target_guid, action_number) => @@ -153,7 +153,7 @@ class LocalService(zone: Zone) extends Actor { LocalServiceResponse( s"/$forChannel/Local", player_guid, - LocalResponse.SendGenericObjectActionMessage(target_guid, action_number) + LocalResponse.GenericObjectAction(target_guid, action_number) ) ) @@ -162,7 +162,7 @@ class LocalService(zone: Zone) extends Actor { LocalServiceResponse( s"/$forChannel/Local", player_guid, - LocalResponse.SendChatMsg(msg) + LocalResponse.ChatMessage(msg) ) ) @@ -171,7 +171,7 @@ class LocalService(zone: Zone) extends Actor { LocalServiceResponse( s"/$forChannel/Local", player_guid, - LocalResponse.SendGenericActionMessage(action_number) + LocalResponse.GenericActionMessage(action_number) ) ) case LocalAction.RouterTelepadMessage(msg) => diff --git a/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala b/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala index 2e050ac61..277dddccd 100644 --- a/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala +++ b/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala @@ -38,12 +38,12 @@ object LocalResponse { final case class HackObject(target_guid: PlanetSideGUID, unk1: Long, unk2: Long) extends Response final case class SendPacket(packet: PlanetSideGamePacket) extends Response - final case class SendPlanetsideAttributeMessage(target_guid: PlanetSideGUID, attribute_number: PlanetsideAttributeEnum, attribute_value: Long) + final case class PlanetsideAttribute(target_guid: PlanetSideGUID, attribute_number: PlanetsideAttributeEnum, attribute_value: Long) extends Response - final case class SendGenericObjectActionMessage(target_guid: PlanetSideGUID, action_number: GenericObjectActionEnum) + final case class GenericObjectAction(target_guid: PlanetSideGUID, action_number: GenericObjectActionEnum) extends Response - final case class SendChatMsg(msg: ChatMsg) extends Response - final case class SendGenericActionMessage(action_num: GenericAction) extends Response + final case class ChatMessage(msg: ChatMsg) extends Response + final case class GenericActionMessage(action_num: GenericAction) extends Response final case class LluSpawned(llu: CaptureFlag) extends Response final case class LluDespawned(guid: PlanetSideGUID, position: Vector3) extends Response