From 53e3f9a08d9dc453435d7d1f8bd05a44afa673cd Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Thu, 16 Mar 2023 14:05:21 -0400 Subject: [PATCH] Code Style Improvements 2 (#1050) * changes to the session actor handler classes * further changes to session actor handler classes * extending the range of voice emote penetration * rollback of changes to SessionGalaxyHandlers to preserve passenger zoning behavior --- .../psforever/actors/session/ChatActor.scala | 22 +- .../actors/session/SessionActor.scala | 4 +- .../support/SessionAvatarHandlers.scala | 817 +++++++++--------- .../actors/session/support/SessionData.scala | 24 +- .../support/SessionGalaxyHandlers.scala | 108 +++ .../support/SessionLocalHandlers.scala | 254 +++--- .../support/SessionMountHandlers.scala | 335 ++++--- .../support/SessionTerminalHandlers.scala | 217 ++--- .../support/SessionVehicleHandlers.scala | 514 +++++------ .../session/support/ZoningOperations.scala | 7 +- .../services/local/LocalService.scala | 8 +- .../services/local/LocalServiceResponse.scala | 8 +- 12 files changed, 1239 insertions(+), 1079 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/ChatActor.scala b/src/main/scala/net/psforever/actors/session/ChatActor.scala index ce9f46f25..e15ec2861 100644 --- a/src/main/scala/net/psforever/actors/session/ChatActor.scala +++ b/src/main/scala/net/psforever/actors/session/ChatActor.scala @@ -5,13 +5,6 @@ import akka.actor.typed.{ActorRef, Behavior, PostStop, SupervisorStrategy} import akka.actor.typed.receptionist.Receptionist import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} import akka.actor.typed.scaladsl.adapter._ -import net.psforever.objects.avatar.{Shortcut => AvatarShortcut} -import net.psforever.objects.definition.ImplantDefinition -import net.psforever.packet.game.{CreateShortcutMessage, Shortcut} -import net.psforever.packet.game.objectcreate.DrawnSlot -import net.psforever.types.ChatMessageType.{CMT_GMOPEN, UNK_227} -import net.psforever.types.{ExperienceType, ImplantType} - import scala.collection.mutable import scala.concurrent.ExecutionContextExecutor import scala.concurrent.duration._ @@ -19,18 +12,20 @@ import scala.concurrent.duration._ import net.psforever.actors.zone.BuildingActor import net.psforever.login.WorldSession import net.psforever.objects.{Default, Player, Session} -import net.psforever.objects.avatar.{BattleRank, Certification, CommandRank} +import net.psforever.objects.avatar.{BattleRank, Certification, CommandRank, Shortcut => AvatarShortcut} +import net.psforever.objects.definition.ImplantDefinition import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.structures.{Amenity, Building} import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurrets} import net.psforever.objects.zones.Zoning -import net.psforever.packet.game.{ChatMsg, DeadState, RequestDestroyMessage, ZonePopulationUpdateMessage} +import net.psforever.packet.game.objectcreate.DrawnSlot +import net.psforever.packet.game.{ChatMsg, CreateShortcutMessage, DeadState, RequestDestroyMessage, Shortcut, ZonePopulationUpdateMessage} import net.psforever.services.{CavernRotationService, InterstellarClusterService} import net.psforever.services.chat.ChatService import net.psforever.services.chat.ChatService.ChatChannel -import net.psforever.types.ChatMessageType.UNK_229 -import net.psforever.types.{ChatMessageType, Cosmetic, PlanetSideEmpire, PlanetSideGUID, Vector3} +import net.psforever.types.ChatMessageType.{CMT_GMOPEN, UNK_227, UNK_229} +import net.psforever.types.{ChatMessageType, Cosmetic, ExperienceType, ImplantType, PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.util.{Config, PointOfInterest} import net.psforever.zones.Zones @@ -998,9 +993,8 @@ class ChatActor( sessionActor ! SessionActor.SendResponse(message) case CMT_VOICE => if ( - session.zone == fromSession.zone && - Vector3.DistanceSquared(session.player.Position, fromSession.player.Position) < 625 || - message.contents.startsWith("SH") // tactical squad voice macro + (session.zone == fromSession.zone || message.contents.startsWith("SH")) && /*tactical squad voice macro*/ + Vector3.DistanceSquared(session.player.Position, fromSession.player.Position) < 1600 ) { val name = fromSession.avatar.name if (!session.avatar.people.ignored.exists { f => f.name.equals(name) } || 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 2c6d534c6..5f6d03ce0 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala @@ -3,6 +3,8 @@ package net.psforever.actors.session.support import akka.actor.typed.scaladsl.adapter._ import akka.actor.{ActorContext, typed} +import net.psforever.services.Service + import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ // @@ -29,172 +31,349 @@ class SessionAvatarHandlers( chatActor: typed.ActorRef[ChatActor.Command], implicit val context: ActorContext ) extends CommonSessionInterfacingFunctionality { - private[support] var lastSeenStreamMessage: Array[Long] = Array.fill[Long](65535)(elem=0L) + //TODO player characters only exist within a certain range of GUIDs for a given zone; this is overkill + private[support] var lastSeenStreamMessage: Array[SessionAvatarHandlers.LastUpstream] = { + SessionAvatarHandlers.blankUpstreamMessages(65535) + } + private[this] val hidingPlayerRandomizer = new scala.util.Random /** * na - * * @param toChannel na * @param guid na * @param reply na */ def handle(toChannel: String, guid: PlanetSideGUID, reply: AvatarResponse.Response): Unit = { - val tplayer_guid = - if (player != null && player.HasGUID) player.GUID - else PlanetSideGUID(0) + val resolvedPlayerGuid = if (player != null && player.HasGUID) { + player.GUID + } else { + 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.SendResponse(msg) => - sendResponse(msg) - - case AvatarResponse.SendResponseTargeted(target_guid, msg) => - if (tplayer_guid == target_guid) { - sendResponse(msg) + /* really common messages (very frequently, every life) */ + case pstate @ AvatarResponse.PlayerState( + pos, + vel, + yaw, + pitch, + yawUpper, + _, + isCrouching, + isJumping, + jumpThrust, + isCloaking, + spectating, + _ + ) if isNotSameTarget => + val now = System.currentTimeMillis() + val (location, time, distanceSq): (Vector3, Long, Float) = if (spectating) { + val r2 = 2 + hidingPlayerRandomizer.nextInt(4000).toFloat + (Vector3(r2, r2, 1f), 0L, 0f) + } else { + val before = lastSeenStreamMessage(guid.guid).time + val dist = Vector3.DistanceSquared(player.Position, pos) + (pos, now - before, dist) } - - case AvatarResponse.Revive(target_guid) => - if (tplayer_guid == target_guid) { - 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(target_guid, 0, health)) - sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0, 0, player.Position, player.Faction, unk5=true)) - continent.AvatarEvents ! AvatarServiceMessage( - continent.id, - AvatarAction.PlanetsideAttributeToAll(target_guid, 0, health) - ) - } - - case AvatarResponse.ArmorChanged(suit, subtype) => - if (tplayer_guid != guid) { - sendResponse(ArmorChangedMessage(guid, suit, subtype)) - } - - case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) => - if (tplayer_guid != guid) { - sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3.Zero, 0)) + if (distanceSq < 302500 || time > 5000) { // Render distance seems to be approx 525m. Reduce update rate at ~550m to be safe sendResponse( - ObjectCreateMessage( - ammo_id, - ammo_guid, - ObjectCreateMessageParent(weapon_guid, weapon_slot), - ammo_data + PlayerStateMessage( + guid, + location, + vel, + yaw, + pitch, + yawUpper, + timestamp = 0, + isCrouching, + isJumping, + jumpThrust, + isCloaking ) ) - sendResponse(ChangeAmmoMessage(weapon_guid, 1)) + lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstate), now) } - case AvatarResponse.ChangeFireMode(item_guid, mode) => - if (tplayer_guid != guid) { - sendResponse(ChangeFireModeMessage(item_guid, mode)) + 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 + continent.GUID(sessionData.terminals.usingMedicalTerminal).collect { + case term: Terminal with ProximityUnit => sessionData.terminals.StopUsingProximityUnit(term) } - case AvatarResponse.ChangeFireState_Start(weapon_guid) => - if (tplayer_guid != guid) { - sendResponse(ChangeFireStateMessage_Start(weapon_guid)) - } + case AvatarResponse.ObjectHeld(slot, _) + if isSameTarget && slot > -1 => + sendResponse(ObjectHeldMessage(guid, slot, unk1=true)) - case AvatarResponse.ChangeFireState_Stop(weapon_guid) => - if (tplayer_guid != guid) { - sendResponse(ChangeFireStateMessage_Stop(weapon_guid)) - } + case AvatarResponse.ObjectHeld(_, _) + if isSameTarget => () - case AvatarResponse.ConcealPlayer() => - sendResponse(GenericObjectActionMessage(guid, 9)) + case AvatarResponse.ObjectHeld(_, previousSlot) => + sendResponse(ObjectHeldMessage(guid, previousSlot, unk1=false)) - case AvatarResponse.EnvironmentalDamage(_, _, _) => + 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)) + + case AvatarResponse.PlanetsideAttributeToAll(attributeType, attributeValue) => + sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue)) + + case AvatarResponse.PlanetsideAttributeSelf(attributeType, attributeValue) if isSameTarget => + sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue)) + + 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") - //TODO damage marker? + + 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 ;) + // 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.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 + ) + + case AvatarResponse.TerminalOrderResult(terminalGuid, action, result) => + sendResponse(ItemTransactionResultMessage(terminalGuid, action, result)) + sessionData.terminals.lastTerminalOrderFulfillment = true + + case AvatarResponse.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 => + 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, index), + definition.Packet.DetailedConstructorData(obj).get + ) + ) + } + DropLeftovers(player)(drop) + + case AvatarResponse.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 AvatarResponse.DropItem(pkt) => - if (tplayer_guid != guid) { - sendResponse(pkt) + case AvatarResponse.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)) } - - case AvatarResponse.EquipmentInHand(pkt) => - if (tplayer_guid != guid) { - sendResponse(pkt) + //redraw + if (maxhand) { + TaskWorkflow.execute(HoldNewEquipmentUp(player)( + Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), + slot = 0 + )) } + sessionData.applyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory) + DropLeftovers(player)(drops) - case AvatarResponse.GenericObjectAction(object_guid, action_code) => - if (tplayer_guid != guid) { - sendResponse(GenericObjectActionMessage(object_guid, action_code)) - } + 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)) } - case AvatarResponse.HitHint(source_guid) => - if (player.isAlive) { - sendResponse(HitHint(source_guid, guid)) - sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg") - } + case AvatarResponse.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 AvatarResponse.DropSpecialItem() => - sessionData.dropSpecialSlotItem() + case AvatarResponse.KitNotUsed(_, "") => + sessionData.kitToBeUsed = None + + case AvatarResponse.KitNotUsed(_, msg) => + sessionData.kitToBeUsed = 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) => - 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 match { - case Some(item) => - DropEquipmentFromInventory(player)(item) - case None => ; - } - sessionData.dropSpecialSlotItem() - sessionData.toggleMaxSpecialState(enable = false) - if (player.LastDamage match { - case Some(damage) => damage.interaction.cause match { - case cause: ExplodingEntityReason => cause.entity.isInstanceOf[VehicleSpawnPad] - case _ => false + //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 _ => () } - case None => false - }) { - //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) match { - case Some(obj: Vehicle) => - sessionData.vehicles.ConditionalDriverVehicleControl(obj) - sessionData.vehicles.serverVehicleControlVelocity = None - sessionData.unaccessContainer(obj) - case _ => ; - } - sessionData.playerActionsToCancel() - sessionData.terminals.CancelAllProximityUnits() - sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel") + 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"KillPlayer/SHOTS_WHILE_DEAD: client of ${avatar.name} fired ${sessionData.shooting.shotsWhileDead} rounds while character was dead on server" + 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) { @@ -208,340 +387,122 @@ class SessionAvatarHandlers( } else { sessionData.zoning.spawn.HandleReleaseAvatar(player, continent) } - AvatarActor.savePlayerLocation(player) - sessionData.renewCharSavedTimer(fixedLen = 1800L, varLen = 0L) - case AvatarResponse.LoadPlayer(pkt) => - if (tplayer_guid != guid) { - sendResponse(pkt) - } + case AvatarResponse.Release(tplayer) if isNotSameTarget => + sessionData.zoning.spawn.DepictPlayerAsCorpse(tplayer) - case AvatarResponse.LoadProjectile(pkt) => - if (tplayer_guid != guid) { - sendResponse(pkt) - } + 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.ObjectDelete(item_guid, unk) => - if (tplayer_guid != guid) { - sendResponse(ObjectDeleteMessage(item_guid, unk)) - } + /* 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.ObjectHeld(slot, previousSlot) => - if (tplayer_guid == guid) { - if (slot > -1) { - 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) match { - case Some(term: Terminal with ProximityUnit) => - sessionData.terminals.StopUsingProximityUnit(term) - case _ => ; - } - } - } - } else { - sendResponse(ObjectHeldMessage(guid, previousSlot, unk1 = false)) - } + 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 match { - case Some(vinfo) => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state)) - case None => None - } - ) - ) + sendResponse(OxygenStateMessage( + DrowningTarget(player.guid, player.progress, player.state), + vehicle.flatMap { vinfo => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state)) } + )) - case AvatarResponse.PlanetsideAttribute(attribute_type, attribute_value) => - if (tplayer_guid != guid) { - sendResponse(PlanetsideAttributeMessage(guid, attribute_type, attribute_value)) - } + case AvatarResponse.LoadProjectile(pkt) if isNotSameTarget => + sendResponse(pkt) - case AvatarResponse.PlanetsideAttributeToAll(attribute_type, attribute_value) => - sendResponse(PlanetsideAttributeMessage(guid, attribute_type, attribute_value)) + case AvatarResponse.ProjectileState(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid) if isNotSameTarget => + sendResponse(ProjectileStateMessage(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid)) - case AvatarResponse.PlanetsideAttributeSelf(attribute_type, attribute_value) => - if (tplayer_guid == guid) { - sendResponse(PlanetsideAttributeMessage(guid, attribute_type, attribute_value)) - } - - case AvatarResponse.PlayerState( - pos, - vel, - yaw, - pitch, - yaw_upper, - _, - is_crouching, - is_jumping, - jump_thrust, - is_cloaking, - spectating, - _ - ) => - if (tplayer_guid != guid) { - 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) - } else { - val before = lastSeenStreamMessage(guid.guid) - val dist = Vector3.DistanceSquared(player.Position, pos) - (pos, now - before, dist) - } - if (distanceSq < 302500 || time > 5000) { // Render distance seems to be approx 525m. Reduce update rate at ~550m to be safe - sendResponse( - PlayerStateMessage( - guid, - location, - vel, - yaw, - pitch, - yaw_upper, - timestamp = 0, - is_crouching, - is_jumping, - jump_thrust, - is_cloaking - ) - ) - lastSeenStreamMessage(guid.guid) = now - } - } - - case AvatarResponse.ProjectileExplodes(projectile_guid, projectile) => + case AvatarResponse.ProjectileExplodes(projectileGuid, projectile) => sendResponse( ProjectileStateMessage( - projectile_guid, + projectileGuid, projectile.Position, - Vector3.Zero, + shot_vel = Vector3.Zero, projectile.Orientation, - 0, + sequence_num=0, end=true, - PlanetSideGUID(0) + hit_target_guid=PlanetSideGUID(0) ) ) - sendResponse(ObjectDeleteMessage(projectile_guid, 2)) + sendResponse(ObjectDeleteMessage(projectileGuid, unk1=2)) case AvatarResponse.ProjectileAutoLockAwareness(mode) => sendResponse(GenericActionMessage(mode)) - case AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) => - if (tplayer_guid != guid) { - sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid)) - } + case AvatarResponse.PutDownFDU(target) if isNotSameTarget => + sendResponse(GenericObjectActionMessage(target, code=53)) - case AvatarResponse.PutDownFDU(target) => - if (tplayer_guid != guid) { - sendResponse(GenericObjectActionMessage(target, 53)) - } - - case AvatarResponse.Release(tplayer) => - if (tplayer_guid != guid) { - sessionData.zoning.spawn.DepictPlayerAsCorpse(tplayer) - } - - case AvatarResponse.Reload(item_guid) => - if (tplayer_guid != guid) { - sendResponse(ReloadMessage(item_guid, 1, 0)) - } - - case AvatarResponse.SetEmpire(object_guid, faction) => - if (tplayer_guid != guid) { - sendResponse(SetEmpireMessage(object_guid, faction)) - } - - case AvatarResponse.StowEquipment(target, slot, item) => - if (tplayer_guid != guid) { - val definition = item.Definition - sendResponse( - ObjectCreateDetailedMessage( - definition.ObjectId, - item.GUID, - ObjectCreateMessageParent(target, slot), - definition.Packet.DetailedConstructorData(item).get - ) - ) - } - - case AvatarResponse.WeaponDryFire(weapon_guid) => - if (tplayer_guid != guid) { - continent.GUID(weapon_guid) match { - case Some(tool: Tool) => - // check that the magazine is still empty before sending WeaponDryFireMessage - // if it has been reloaded since then, other clients not see it firing - if (tool.Magazine == 0) { - sendResponse(WeaponDryFireMessage(weapon_guid)) - } - case Some(_) => - sendResponse(WeaponDryFireMessage(weapon_guid)) - case None => ; - } - } - - case AvatarResponse.TerminalOrderResult(terminal_guid, action, result) => - sendResponse(ItemTransactionResultMessage(terminal_guid, 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, - armor, - exosuit, - subtype, - slot, - maxhand, - old_holsters, - holsters, - old_inventory, - inventory, - drop, - delete - ) => - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, 4, armor)) - if (tplayer_guid == target) { - //happening to this player - //cleanup - sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=false)) - (old_holsters ++ old_inventory ++ delete).foreach { - case (_, dguid) => sendResponse(ObjectDeleteMessage(dguid, 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 match { - case Some(obj) => - val definition = obj.Definition - sendResponse( - ObjectCreateDetailedMessage( - definition.ObjectId, - obj.GUID, - ObjectCreateMessageParent(target, Player.FreeHandSlot), - definition.Packet.DetailedConstructorData(obj).get - ) - ) - case None => ; - } - //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) - } else { - //happening to some other player - sendResponse(ObjectHeldMessage(target, slot, unk1=false)) - //cleanup - (old_holsters ++ delete).foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, 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 AvatarResponse.ChangeLoadout( - target, - armor, - exosuit, - subtype, - slot, - maxhand, - old_holsters, - holsters, - old_inventory, - inventory, - drops - ) => - sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, 4, armor)) - if (tplayer_guid == target) { - //happening to this player - sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=true)) - //cleanup - (old_holsters ++ old_inventory).foreach { - case (obj, objGuid) => - sendResponse(ObjectDeleteMessage(objGuid, 0)) - TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) - } - //redraw - if (maxhand) { - TaskWorkflow.execute(HoldNewEquipmentUp(player)( - Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), - slot = 0 - )) - } - sessionData.applyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory) - DropLeftovers(player)(drops) - } else { - //happening to some other player - sendResponse(ObjectHeldMessage(target, slot, unk1=false)) - //cleanup - old_holsters.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, 0)) } - //redraw handled by callback - } - - case AvatarResponse.UseKit(kguid, kObjId) => + case AvatarResponse.StowEquipment(target, slot, item) if isNotSameTarget => + val definition = item.Definition sendResponse( - UseItemMessage( - tplayer_guid, - kguid, - tplayer_guid, - 4294967295L, - unk3=false, - Vector3.Zero, - Vector3.Zero, - 126, - 0, //sequence time? - 137, - kObjId + ObjectCreateDetailedMessage( + definition.ObjectId, + item.GUID, + ObjectCreateMessageParent(target, slot), + definition.Packet.DetailedConstructorData(item).get ) ) - sendResponse(ObjectDeleteMessage(kguid, 0)) - case AvatarResponse.KitNotUsed(_, "") => - sessionData.kitToBeUsed = None + 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.KitNotUsed(_, msg) => - sessionData.kitToBeUsed = None - sendResponse(ChatMsg(ChatMessageType.UNK_225, wideContents=false, "", msg, None)) - - case _ => ; + 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/SessionGalaxyHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionGalaxyHandlers.scala index f9fb7ee72..dc9507092 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionGalaxyHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionGalaxyHandlers.scala @@ -113,3 +113,111 @@ class SessionGalaxyHandlers( } } } + +/*package net.psforever.actors.session.support + +import akka.actor.{ActorContext, ActorRef, typed} +import scala.concurrent.duration._ +// +import net.psforever.actors.session.AvatarActor +import net.psforever.objects.Vehicle +import net.psforever.packet.game.{AvatarDeadStateMessage, BroadcastWarpgateUpdateMessage, DeadState, HotSpotInfo => PacketHotSpotInfo, HotSpotUpdateMessage, ZoneInfoMessage, ZonePopulationUpdateMessage} +import net.psforever.services.Service +import net.psforever.services.galaxy.GalaxyResponse +import net.psforever.types.{MemberAction, PlanetSideEmpire} + +class SessionGalaxyHandlers( + val sessionData: SessionData, + avatarActor: typed.ActorRef[AvatarActor.Command], + galaxyService: ActorRef, + implicit val context: ActorContext + ) extends CommonSessionInterfacingFunctionality { + def handle(reply: GalaxyResponse.Response): Unit = { + reply match { + case GalaxyResponse.HotSpotUpdate(zoneIndex, priority, hotSpotInfo) => + sendResponse( + HotSpotUpdateMessage( + zoneIndex, + priority, + hotSpotInfo.map { spot => PacketHotSpotInfo(spot.DisplayLocation.x, spot.DisplayLocation.y, 40) } + ) + ) + + case GalaxyResponse.MapUpdate(msg) => + sendResponse(msg) + + case GalaxyResponse.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 GalaxyResponse.FlagMapUpdate(msg) => + sendResponse(msg) + + case GalaxyResponse.TransferPassenger(tempChannel, vehicle, _, manifest) => + val playerName = player.Name + log.debug(s"TransferPassenger: $playerName received the summons to transfer to ${vehicle.Zone.id} ...") + manifest.passengers + .find { _.name.equals(playerName) } + .collect { + case entry if vehicle.Seats(entry.mount).occupant.isEmpty => + player.VehicleSeated = None + vehicle.Seats(entry.mount).mount(player) + player.VehicleSeated = vehicle.GUID + Some(vehicle) + case entry if vehicle.Seats(entry.mount).occupant.contains(player) => + Some(vehicle) + case entry => + log.warn( + s"TransferPassenger: $playerName tried to mount seat ${entry.mount} during summoning, but it was already occupied, and ${player.Sex.pronounSubject} was rebuked" + ) + None + }.orElse { + manifest.cargo.find { _.name.equals(playerName) }.flatMap { entry => + vehicle.CargoHolds(entry.mount).occupant.collect { + case cargo if cargo.Seats(0).occupants.exists(_.Name.equals(playerName)) => cargo + } + } + } match { + case Some(v: Vehicle) => + galaxyService ! Service.Leave(Some(tempChannel)) //temporary vehicle-specific channel (see above) + sessionData.zoning.spawn.deadState = DeadState.Release + sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, unk5=true)) + sessionData.zoning.interstellarFerry = Some(v) //on the other continent and registered to that continent's GUID system + sessionData.zoning.spawn.LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1 seconds, None) + case _ => + sessionData.zoning.interstellarFerry match { + case None => + galaxyService ! Service.Leave(Some(tempChannel)) //no longer being transferred between zones + sessionData.zoning.interstellarFerryTopLevelGUID = None + case Some(_) => ; + //wait patiently + } + } + + case GalaxyResponse.LockedZoneUpdate(zone, time) => + sendResponse(ZoneInfoMessage(zone.Number, empire_status=false, lock_time=time)) + + case GalaxyResponse.UnlockedZoneUpdate(zone) => + sendResponse(ZoneInfoMessage(zone.Number, empire_status=true, lock_time=0L)) + val popBO = 0 + val popTR = zone.Players.count(_.faction == PlanetSideEmpire.TR) + val popNC = zone.Players.count(_.faction == PlanetSideEmpire.NC) + val popVS = zone.Players.count(_.faction == PlanetSideEmpire.VS) + sendResponse(ZonePopulationUpdateMessage(zone.Number, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) + + case GalaxyResponse.LogStatusChange(name) if avatar.people.friend.exists { _.name.equals(name) } => + avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name) + + case GalaxyResponse.SendResponse(msg) => + sendResponse(msg) + + case _ => () + } + } +}*/ 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 f42254d06..6d78bd875 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala @@ -7,6 +7,7 @@ import net.psforever.objects.vehicles.MountableWeapons import net.psforever.objects._ import net.psforever.packet.game.PlanetsideAttributeEnum.PlanetsideAttributeEnum import net.psforever.packet.game._ +import net.psforever.services.Service import net.psforever.services.local.LocalResponse import net.psforever.types.{ChatMessageType, PlanetSideGUID, Vector3} @@ -21,99 +22,108 @@ class SessionLocalHandlers( * @param reply na */ def handle(toChannel: String, guid: PlanetSideGUID, reply: LocalResponse.Response): Unit = { - val tplayer_guid = if (player.HasGUID) { player.GUID } - else { PlanetSideGUID(0) } + val resolvedPlayerGuid = if (player.HasGUID) { + player.GUID + } else { + Service.defaultPlayerGUID + } + val isNotSameTarget = resolvedPlayerGuid != guid reply match { - case LocalResponse.DeployableMapIcon(behavior, deployInfo) => - if (tplayer_guid != guid) { - sendResponse(DeployableObjectsInfoMessage(behavior, deployInfo)) - } + case LocalResponse.DeployableMapIcon(behavior, deployInfo) if isNotSameTarget => + sendResponse(DeployableObjectsInfoMessage(behavior, deployInfo)) case LocalResponse.DeployableUIFor(item) => sessionData.updateDeployableUIElements(avatar.deployables.UpdateUIElement(item)) case LocalResponse.Detonate(dguid, _: BoomerDeployable) => sendResponse(TriggerEffectMessage(dguid, "detonate_boomer")) - sendResponse(PlanetsideAttributeMessage(dguid, 29, 1)) - sendResponse(ObjectDeleteMessage(dguid, 0)) + sendResponse(PlanetsideAttributeMessage(dguid, attribute_type=29, attribute_value=1)) + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) case LocalResponse.Detonate(dguid, _: ExplosiveDeployable) => - sendResponse(GenericObjectActionMessage(dguid, 19)) - sendResponse(PlanetsideAttributeMessage(dguid, 29, 1)) - sendResponse(ObjectDeleteMessage(dguid, 0)) + sendResponse(GenericObjectActionMessage(dguid, code=19)) + sendResponse(PlanetsideAttributeMessage(dguid, attribute_type=29, attribute_value=1)) + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) case LocalResponse.Detonate(_, obj) => log.warn(s"LocalResponse.Detonate: ${obj.Definition.Name} not configured to explode correctly") - case LocalResponse.DoorOpens(door_guid) => - if (tplayer_guid != guid) { - sendResponse(GenericObjectStateMsg(door_guid, 16)) - } + case LocalResponse.DoorOpens(doorGuid) if isNotSameTarget => + sendResponse(GenericObjectStateMsg(doorGuid, state=16)) - case LocalResponse.DoorCloses(door_guid) => //door closes for everyone - sendResponse(GenericObjectStateMsg(door_guid, 17)) + case LocalResponse.DoorCloses(doorGuid) => //door closes for everyone + sendResponse(GenericObjectStateMsg(doorGuid, state=17)) + + case LocalResponse.EliminateDeployable(obj: TurretDeployable, dguid, _, _) if obj.Destroyed => + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) case LocalResponse.EliminateDeployable(obj: TurretDeployable, dguid, pos, _) => - if (obj.Destroyed) { - sendResponse(ObjectDeleteMessage(dguid, 0)) - } else { - obj.Destroyed = true - DeconstructDeployable( - obj, - dguid, - pos, - obj.Orientation, - if (obj.MountPoints.isEmpty) 2 else 1 - ) - } + obj.Destroyed = true + DeconstructDeployable( + obj, + dguid, + pos, + obj.Orientation, + deletionType= if (obj.MountPoints.isEmpty) { 2 } else { 1 } + ) + + case LocalResponse.EliminateDeployable(obj: ExplosiveDeployable, dguid, _, _) + if obj.Destroyed || obj.Jammed || obj.Health == 0 => + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) case LocalResponse.EliminateDeployable(obj: ExplosiveDeployable, dguid, pos, effect) => - if (obj.Destroyed || obj.Jammed || obj.Health == 0) { - sendResponse(ObjectDeleteMessage(dguid, 0)) - } else { - obj.Destroyed = true - DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect) - } + obj.Destroyed = true + DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect) + + case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed => + //if active, deactivate + obj.Active = false + sendResponse(GenericObjectActionMessage(dguid, code=29)) + sendResponse(GenericObjectActionMessage(dguid, code=30)) + //standard deployable elimination behavior + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) + + case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) if obj.Active => + //if active, deactivate + obj.Active = false + sendResponse(GenericObjectActionMessage(dguid, code=29)) + sendResponse(GenericObjectActionMessage(dguid, code=30)) + //standard deployable elimination behavior + obj.Destroyed = true + DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2) + + case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Destroyed => + //standard deployable elimination behavior + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) => - //if active, deactivate - if (obj.Active) { - obj.Active = false - sendResponse(GenericObjectActionMessage(dguid, 29)) - sendResponse(GenericObjectActionMessage(dguid, 30)) - } //standard deployable elimination behavior - if (obj.Destroyed) { - sendResponse(ObjectDeleteMessage(dguid, 0)) - } else { - obj.Destroyed = true - DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType = 2) - } + obj.Destroyed = true + DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2) + + case LocalResponse.EliminateDeployable(obj, dguid, _, _) if obj.Destroyed => + sendResponse(ObjectDeleteMessage(dguid, unk1=0)) case LocalResponse.EliminateDeployable(obj, dguid, pos, effect) => - if (obj.Destroyed) { - sendResponse(ObjectDeleteMessage(dguid, 0)) - } else { - obj.Destroyed = true - DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect) - } + obj.Destroyed = true + DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect) - case LocalResponse.SendHackMessageHackCleared(target_guid, unk1, unk2) => - sendResponse(HackMessage(0, target_guid, guid, 0, unk1, HackState.HackCleared, unk2)) + case LocalResponse.SendHackMessageHackCleared(targetGuid, unk1, unk2) => + sendResponse(HackMessage(unk1=0, targetGuid, guid, progress=0, unk1, HackState.HackCleared, unk2)) - case LocalResponse.HackObject(target_guid, unk1, unk2) => - HackObject(target_guid, unk1, unk2) + case LocalResponse.HackObject(targetGuid, unk1, unk2) => + HackObject(targetGuid, unk1, unk2) - case LocalResponse.SendPlanetsideAttributeMessage(target_guid, attribute_number, attribute_value) => - SendPlanetsideAttributeMessage(target_guid, attribute_number, attribute_value) + case LocalResponse.PlanetsideAttribute(targetGuid, attributeType, attributeValue) => + SendPlanetsideAttributeMessage(targetGuid, attributeType, attributeValue) - case LocalResponse.SendGenericObjectActionMessage(target_guid, action_number) => - sendResponse(GenericObjectActionMessage(target_guid, action_number)) + case LocalResponse.GenericObjectAction(targetGuid, actionNumber) => + sendResponse(GenericObjectActionMessage(targetGuid, actionNumber)) - case LocalResponse.SendGenericActionMessage(action_number) => - sendResponse(GenericActionMessage(action_number)) + case LocalResponse.GenericActionMessage(actionNumber) => + sendResponse(GenericActionMessage(actionNumber)) - case LocalResponse.SendChatMsg(msg) => + case LocalResponse.ChatMessage(msg) => sendResponse(msg) case LocalResponse.SendPacket(packet) => @@ -121,49 +131,43 @@ class SessionLocalHandlers( case LocalResponse.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, 0.8000001f)) + 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 LocalResponse.LluDespawned(lluGuid, position) => - sendResponse(TriggerSoundMessage(TriggeredSound.LLUDeconstruct, position, unk = 20, 0.8000001f)) - sendResponse(ObjectDeleteMessage(lluGuid, 0)) + 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 match { - case Some(guid) if guid == lluGuid => - sessionData.specialItemSlotGuid = None - player.Carrying = None - case _ => () + sessionData.specialItemSlotGuid.collect { case guid if guid == lluGuid => + sessionData.specialItemSlotGuid = None + player.Carrying = None } - case LocalResponse.ObjectDelete(object_guid, unk) => - if (tplayer_guid != guid) { - sendResponse(ObjectDeleteMessage(object_guid, unk)) - } + case LocalResponse.ObjectDelete(objectGuid, unk) if isNotSameTarget => + sendResponse(ObjectDeleteMessage(objectGuid, unk)) case LocalResponse.ProximityTerminalEffect(object_guid, true) => - sendResponse(ProximityTerminalUseMessage(PlanetSideGUID(0), object_guid, unk=true)) + sendResponse(ProximityTerminalUseMessage(Service.defaultPlayerGUID, object_guid, unk=true)) - case LocalResponse.ProximityTerminalEffect(object_guid, false) => - sendResponse(ProximityTerminalUseMessage(PlanetSideGUID(0), object_guid, unk=false)) - sessionData.terminals.ForgetAllProximityTerminals(object_guid) + case LocalResponse.ProximityTerminalEffect(objectGuid, false) => + sendResponse(ProximityTerminalUseMessage(Service.defaultPlayerGUID, objectGuid, unk=false)) + sessionData.terminals.ForgetAllProximityTerminals(objectGuid) case LocalResponse.RouterTelepadMessage(msg) => - sendResponse(ChatMsg(ChatMessageType.UNK_229, wideContents=false, "", msg, None)) + sendResponse(ChatMsg(ChatMessageType.UNK_229, wideContents=false, recipient="", msg, note=None)) - case LocalResponse.RouterTelepadTransport(passenger_guid, src_guid, dest_guid) => - sessionData.useRouterTelepadEffect(passenger_guid, src_guid, dest_guid) + case LocalResponse.RouterTelepadTransport(passengerGuid, srcGuid, destGuid) => + sessionData.useRouterTelepadEffect(passengerGuid, srcGuid, destGuid) case LocalResponse.SendResponse(msg) => sendResponse(msg) - case LocalResponse.SetEmpire(object_guid, empire) => - sendResponse(SetEmpireMessage(object_guid, empire)) + case LocalResponse.SetEmpire(objectGuid, empire) => + sendResponse(SetEmpireMessage(objectGuid, empire)) case LocalResponse.ShuttleEvent(ev) => val msg = OrbitalShuttleTimeMsg( @@ -172,7 +176,7 @@ class SessionLocalHandlers( ev.t1, ev.t2, ev.t3, - ev.pairs.map { case ((a, b), c) => PadAndShuttlePair(a, b, c) } + pairs=ev.pairs.map { case ((a, b), c) => PadAndShuttlePair(a, b, c) } ) sendResponse(msg) @@ -183,42 +187,32 @@ class SessionLocalHandlers( sendResponse(ObjectDetachMessage(pguid, sguid, pos, orient)) case LocalResponse.ShuttleState(sguid, pos, orient, state) => - sendResponse(VehicleStateMessage(sguid, 0, pos, orient, None, Some(state), 0, 0, 15, is_decelerating=false, is_cloaked=false)) + 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 LocalResponse.ToggleTeleportSystem(router, system_plan) => - sessionData.toggleTeleportSystem(router, system_plan) + case LocalResponse.ToggleTeleportSystem(router, systemPlan) => + sessionData.toggleTeleportSystem(router, systemPlan) - case LocalResponse.TriggerEffect(target_guid, effect, effectInfo, triggerLocation) => - sendResponse(TriggerEffectMessage(target_guid, effect, effectInfo, triggerLocation)) + case LocalResponse.TriggerEffect(targetGuid, effect, effectInfo, triggerLocation) => + sendResponse(TriggerEffectMessage(targetGuid, effect, effectInfo, triggerLocation)) case LocalResponse.TriggerSound(sound, pos, unk, volume) => sendResponse(TriggerSoundMessage(sound, pos, unk, volume)) - case LocalResponse.UpdateForceDomeStatus(building_guid, activated) => - if (activated) { - sendResponse(GenericObjectActionMessage(building_guid, 11)) - } else { - sendResponse(GenericObjectActionMessage(building_guid, 12)) - } + case LocalResponse.UpdateForceDomeStatus(buildingGuid, true) => + sendResponse(GenericObjectActionMessage(buildingGuid, 11)) - case LocalResponse.RechargeVehicleWeapon(vehicle_guid, weapon_guid) => - if (tplayer_guid == guid) { - continent.GUID(vehicle_guid) match { - case Some(vehicle: MountableWeapons) => - vehicle.PassengerInSeat(player) match { - case Some(seat_num: Int) => - vehicle.WeaponControlledFromSeat(seat_num) foreach { - case weapon: Tool if weapon.GUID == weapon_guid => - sendResponse(InventoryStateMessage(weapon.AmmoSlot.Box.GUID, weapon.GUID, weapon.Magazine)) - case _ => ; - } - case _ => ; - } - case _ => ; + 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, 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 _ => ; + case _ => () } } @@ -244,25 +238,25 @@ class SessionLocalHandlers( /** * na - * @param target_guid na + * @param targetGuid na * @param unk1 na * @param unk2 na */ - def HackObject(target_guid: PlanetSideGUID, unk1: Long, unk2: Long): Unit = { - sendResponse(HackMessage(0, target_guid, PlanetSideGUID(0), 100, unk1, HackState.Hacked, unk2)) + def HackObject(targetGuid: PlanetSideGUID, unk1: Long, unk2: Long): Unit = { + sendResponse(HackMessage(unk1=0, targetGuid, player_guid=Service.defaultPlayerGUID, progress=100, unk1, HackState.Hacked, unk2)) } /** * Send a PlanetsideAttributeMessage packet to the client - * @param target_guid The target of the attribute - * @param attribute_number The attribute number - * @param attribute_value The attribute value + * @param targetGuid The target of the attribute + * @param attributeType The attribute number + * @param attributeValue The attribute value */ def SendPlanetsideAttributeMessage( - target_guid: PlanetSideGUID, - attribute_number: PlanetsideAttributeEnum, - attribute_value: Long + targetGuid: PlanetSideGUID, + attributeType: PlanetsideAttributeEnum, + attributeValue: Long ): Unit = { - sendResponse(PlanetsideAttributeMessage(target_guid, attribute_number, attribute_value)) + sendResponse(PlanetsideAttributeMessage(targetGuid, attributeType, attributeValue)) } } 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 b34a4210e..23d18afb9 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala @@ -31,176 +31,241 @@ class SessionMountHandlers( */ def handle(tplayer: Player, reply: Mountable.Exchange): Unit = { reply match { - case Mountable.CanMount(obj: ImplantTerminalMech, seat_number, _) => + case Mountable.CanMount(obj: ImplantTerminalMech, seatNumber, _) => sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use") log.info(s"${player.Name} mounts an implant terminal") sessionData.terminals.CancelAllProximityUnits() - MountingAction(tplayer, obj, seat_number) + MountingAction(tplayer, obj, seatNumber) sessionData.keepAliveFunc = sessionData.keepAlivePersistence - case Mountable.CanMount(obj: Vehicle, seat_number, _) if obj.Definition == GlobalDefinitions.orbital_shuttle => + case Mountable.CanMount(obj: Vehicle, seatNumber, _) + if obj.Definition == GlobalDefinitions.orbital_shuttle => sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") log.info(s"${player.Name} mounts the orbital shuttle") sessionData.terminals.CancelAllProximityUnits() - MountingAction(tplayer, obj, seat_number) + MountingAction(tplayer, obj, seatNumber) sessionData.keepAliveFunc = sessionData.keepAlivePersistence - case Mountable.CanMount(obj: Vehicle, seat_number, _) => + 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(seat_number) match { - case Some(AccessPermissionGroup.Driver) => "the driver seat" - case Some(seatType) => s"a $seatType seat (#$seat_number)" - 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, 0, obj.Health)) + 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, 45, obj.NtuCapacitorScaled)) - } - if (obj.Definition.MaxCapacitor > 0) { - sendResponse(PlanetsideAttributeMessage(obj_guid, 113, obj.Capacitor)) - } - if (seat_number == 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, 11)) - } else if (obj.WeaponControlledFromSeat(seat_number).isEmpty) { - sessionData.keepAliveFunc = sessionData.keepAlivePersistence - } - sessionData.accessContainer(obj) - sessionData.updateWeaponAtSeatPosition(obj, seat_number) - MountingAction(tplayer, obj, seat_number) + sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=45, obj.NtuCapacitorScaled)) + sendResponse(GenericObjectActionMessage(obj_guid, code=11)) + MountingAction(tplayer, obj, seatNumber) - case Mountable.CanMount(obj: FacilityTurret, seat_number, _) => + case Mountable.CanMount(obj: Vehicle, seatNumber, _) + if obj.Definition == GlobalDefinitions.quadstealth => 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)) - } - sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health)) - sessionData.updateWeaponAtSeatPosition(obj, seat_number) - MountingAction(tplayer, obj, seat_number) - } else { - log.warn( - s"MountVehicleMsg: ${tplayer.Name} wants to mount turret ${obj.GUID.guid}, but needs to wait until it finishes updating" - ) - } + 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: PlanetSideGameObject with WeaponTurret, seat_number, _) => + 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: Vehicle, seatNumber, _) + if seatNumber == 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(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" + } + } 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") log.info(s"${player.Name} mounts the ${obj.Definition.asInstanceOf[BasicDefinition].Name}") - sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health)) - sessionData.updateWeaponAtSeatPosition(obj, seat_number) - MountingAction(tplayer, obj, seat_number) + sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health)) + sessionData.updateWeaponAtSeatPosition(obj, seatNumber) + MountingAction(tplayer, obj, seatNumber) case Mountable.CanMount(obj: Mountable, _, _) => log.warn(s"MountVehicleMsg: $obj is some mountable object and nothing will happen for ${player.Name}") - case Mountable.CanDismount(obj: ImplantTerminalMech, seat_num, _) => + case Mountable.CanDismount(obj: ImplantTerminalMech, seatNum, _) => log.info(s"${tplayer.Name} dismounts the implant terminal") - DismountAction(tplayer, obj, seat_num) + DismountAction(tplayer, obj, seatNum) - case Mountable.CanDismount(obj: Vehicle, seat_num, mount_point) - if obj.Definition == GlobalDefinitions.orbital_shuttle => + case Mountable.CanDismount(obj: Vehicle, _, mountPoint) + if obj.Definition == GlobalDefinitions.orbital_shuttle && obj.MountedIn.nonEmpty => + //dismount to hart lobby val pguid = player.GUID - if (obj.MountedIn.nonEmpty) { - //dismount to hart lobby - log.info(s"${tplayer.Name} dismounts the orbital shuttle into the lobby") - val sguid = obj.GUID - val (pos, zang) = Vehicles.dismountShuttle(obj, mount_point) - tplayer.Position = pos - sendResponse(DelayedPathMountMsg(pguid, sguid, 60, u2=true)) - continent.LocalEvents ! LocalServiceMessage( - continent.id, - LocalAction.SendResponse(ObjectDetachMessage(sguid, pguid, pos, 0, 0, zang)) - ) - } else { - log.info(s"${player.Name} is prepped for dropping") - //get ready for orbital drop - DismountAction(tplayer, obj, seat_num) - continent.actor ! ZoneActor.RemoveFromBlockMap(player) //character doesn't need it - //DismountAction(...) uses vehicle service, so use that service to coordinate the remainder of the messages - continent.VehicleEvents ! VehicleServiceMessage( - player.Name, - VehicleAction.SendResponse(Service.defaultPlayerGUID, PlayerStasisMessage(pguid)) //the stasis message - ) - //when the player dismounts, they will be positioned where the shuttle was when it disappeared in the sky - //the player will fall to the ground and is perfectly vulnerable in this state - //additionally, our player must exist in the current zone - //having no in-game avatar target will throw us out of the map screen when deploying and cause softlock - continent.VehicleEvents ! VehicleServiceMessage( - player.Name, - VehicleAction.SendResponse( - Service.defaultPlayerGUID, - PlayerStateShiftMessage(ShiftState(0, obj.Position, obj.Orientation.z, None)) //cower in the shuttle bay - ) - ) - continent.VehicleEvents ! VehicleServiceMessage( - continent.id, - VehicleAction.SendResponse(pguid, GenericObjectActionMessage(pguid, 9)) //conceal the player - ) - } + log.info(s"${tplayer.Name} dismounts the orbital shuttle into the lobby") + val sguid = obj.GUID + val (pos, zang) = Vehicles.dismountShuttle(obj, mountPoint) + tplayer.Position = pos + sendResponse(DelayedPathMountMsg(pguid, sguid, u1=60, u2=true)) + continent.LocalEvents ! LocalServiceMessage( + continent.id, + LocalAction.SendResponse(ObjectDetachMessage(sguid, pguid, pos, roll=0, pitch=0, zang)) + ) sessionData.keepAliveFunc = sessionData.zoning.NormalKeepAlive - case Mountable.CanDismount(obj: Vehicle, seat_num, _) if obj.Definition == GlobalDefinitions.droppod => + case Mountable.CanDismount(obj: Vehicle, seatNum, _) + if obj.Definition == GlobalDefinitions.orbital_shuttle => + //get ready for orbital drop + val pguid = player.GUID + val events = continent.VehicleEvents + log.info(s"${player.Name} is prepped for dropping") + DismountAction(tplayer, obj, seatNum) + continent.actor ! ZoneActor.RemoveFromBlockMap(player) //character doesn't need it + //DismountAction(...) uses vehicle service, so use that service to coordinate the remainder of the messages + events ! VehicleServiceMessage( + player.Name, + VehicleAction.SendResponse(Service.defaultPlayerGUID, PlayerStasisMessage(pguid)) //the stasis message + ) + //when the player dismounts, they will be positioned where the shuttle was when it disappeared in the sky + //the player will fall to the ground and is perfectly vulnerable in this state + //additionally, our player must exist in the current zone + //having no in-game avatar target will throw us out of the map screen when deploying and cause softlock + events ! VehicleServiceMessage( + player.Name, + VehicleAction.SendResponse( + Service.defaultPlayerGUID, + PlayerStateShiftMessage(ShiftState(unk=0, obj.Position, obj.Orientation.z, vel=None)) //cower in the shuttle bay + ) + ) + events ! VehicleServiceMessage( + continent.id, + VehicleAction.SendResponse(pguid, GenericObjectActionMessage(pguid, code=9)) //conceal the player + ) + sessionData.keepAliveFunc = sessionData.zoning.NormalKeepAlive + + case Mountable.CanDismount(obj: Vehicle, seatNum, _) + if obj.Definition == GlobalDefinitions.droppod => log.info(s"${tplayer.Name} has landed on ${continent.id}") sessionData.unaccessContainer(obj) - DismountAction(tplayer, obj, seat_num) + DismountAction(tplayer, obj, seatNum) obj.Actor ! Vehicle.Deconstruct() - case Mountable.CanDismount(obj: Vehicle, seat_num, _) => - val player_guid: PlanetSideGUID = tplayer.GUID - if (player_guid == player.GUID) { - //disembarking self - log.info(s"${player.Name} dismounts the ${obj.Definition.Name}'s ${ - obj.SeatPermissionGroup(seat_num) match { - case Some(AccessPermissionGroup.Driver) => "driver seat" - case Some(seatType) => s"$seatType seat (#$seat_num)" - case None => "seat" - } - }") - sessionData.vehicles.ConditionalDriverVehicleControl(obj) - sessionData.unaccessContainer(obj) - DismountAction(tplayer, obj, seat_num) - } else { - continent.VehicleEvents ! VehicleServiceMessage( - continent.id, - VehicleAction.KickPassenger(player_guid, seat_num, unk2=true, obj.GUID) - ) - } + case Mountable.CanDismount(obj: Vehicle, seatNum, _) + if tplayer.GUID == player.GUID => + //disembarking self + log.info(s"${player.Name} dismounts the ${obj.Definition.Name}'s ${ + obj.SeatPermissionGroup(seatNum) match { + case Some(AccessPermissionGroup.Driver) => "driver seat" + case Some(seatType) => s"$seatType seat (#$seatNum)" + case None => "seat" + } + }") + sessionData.vehicles.ConditionalDriverVehicleControl(obj) + sessionData.unaccessContainer(obj) + DismountAction(tplayer, obj, seatNum) - case Mountable.CanDismount(obj: PlanetSideGameObject with WeaponTurret, seat_num, _) => + case Mountable.CanDismount(obj: Vehicle, seat_num, _) => + continent.VehicleEvents ! VehicleServiceMessage( + continent.id, + VehicleAction.KickPassenger(tplayer.GUID, seat_num, unk2=true, obj.GUID) + ) + + case Mountable.CanDismount(obj: PlanetSideGameObject with WeaponTurret, seatNum, _) => log.info(s"${tplayer.Name} dismounts a ${obj.Definition.asInstanceOf[ObjectDefinition].Name}") - DismountAction(tplayer, obj, seat_num) + DismountAction(tplayer, obj, seatNum) case Mountable.CanDismount(obj: Mountable, _, _) => log.warn(s"DismountVehicleMsg: $obj is some dismountable object but nothing will happen for ${player.Name}") - case Mountable.CanNotMount(obj: Vehicle, mount_point) => - log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's mount $mount_point, but was not allowed") - obj.GetSeatFromMountPoint(mount_point) match { - case Some(seatNum) if obj.SeatPermissionGroup(seatNum).contains(AccessPermissionGroup.Driver) => + case Mountable.CanNotMount(obj: Vehicle, mountPoint) => + log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's mount $mountPoint, but was not allowed") + obj.GetSeatFromMountPoint(mountPoint).collect { + case seatNum if obj.SeatPermissionGroup(seatNum).contains(AccessPermissionGroup.Driver) => sendResponse( - ChatMsg(ChatMessageType.CMT_OPEN, wideContents=false, "", "You are not the driver of this vehicle.", None) + ChatMsg(ChatMessageType.CMT_OPEN, wideContents=false, recipient="", "You are not the driver of this vehicle.", note=None) ) - case _ => } - case Mountable.CanNotMount(obj: Mountable, mount_point) => - log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's mount $mount_point, but was not allowed") + case Mountable.CanNotMount(obj: Mountable, mountPoint) => + log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's mount $mountPoint, but was not allowed") - case Mountable.CanNotDismount(obj, seat_num) => - log.warn( - s"DismountVehicleMsg: ${tplayer.Name} attempted to dismount $obj's mount $seat_num, but was not allowed" - ) + case Mountable.CanNotDismount(obj, seatNum) => + log.warn(s"DismountVehicleMsg: ${tplayer.Name} attempted to dismount $obj's mount $seatNum, but was not allowed") } } @@ -211,15 +276,15 @@ class SessionMountHandlers( * @param seatNum the mount into which the player is mounting */ def MountingAction(tplayer: Player, obj: PlanetSideGameObject with Mountable, seatNum: Int): Unit = { - val player_guid: PlanetSideGUID = tplayer.GUID - val obj_guid: PlanetSideGUID = obj.GUID + val playerGuid: PlanetSideGUID = tplayer.GUID + val objGuid: PlanetSideGUID = obj.GUID sessionData.playerActionsToCancel() avatarActor ! AvatarActor.DeactivateActiveImplants() - avatarActor ! AvatarActor.SuspendStaminaRegeneration(3 seconds) - sendResponse(ObjectAttachMessage(obj_guid, player_guid, seatNum)) + avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds) + sendResponse(ObjectAttachMessage(objGuid, playerGuid, seatNum)) continent.VehicleEvents ! VehicleServiceMessage( continent.id, - VehicleAction.MountVehicle(player_guid, obj_guid, seatNum) + VehicleAction.MountVehicle(playerGuid, objGuid, seatNum) ) } @@ -230,17 +295,17 @@ class SessionMountHandlers( * @param seatNum the mount out of which which the player is disembarking */ def DismountAction(tplayer: Player, obj: PlanetSideGameObject with Mountable, seatNum: Int): Unit = { - val player_guid: PlanetSideGUID = tplayer.GUID + val playerGuid: PlanetSideGUID = tplayer.GUID sessionData.keepAliveFunc = sessionData.zoning.NormalKeepAlive val bailType = if (tplayer.BailProtection) { BailType.Bailed } else { BailType.Normal } - sendResponse(DismountVehicleMsg(player_guid, bailType, wasKickedByDriver = false)) + sendResponse(DismountVehicleMsg(playerGuid, bailType, wasKickedByDriver = false)) continent.VehicleEvents ! VehicleServiceMessage( continent.id, - VehicleAction.DismountVehicle(player_guid, bailType, unk2=false) + VehicleAction.DismountVehicle(playerGuid, bailType, unk2=false) ) } } 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 434af2ca7..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,31 +28,31 @@ 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}") } } def handleProximityTerminalUse(pkt: ProximityTerminalUseMessage): Unit = { - val ProximityTerminalUseMessage(_, object_guid, _) = pkt - continent.GUID(object_guid) match { + val ProximityTerminalUseMessage(_, objectGuid, _) = pkt + continent.GUID(objectGuid) match { 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 $object_guid") + log.error(s"ProximityTerminalUse: ${player.Name} can not find an object with guid ${objectGuid.guid}") } } @@ -60,26 +60,27 @@ class SessionTerminalHandlers( /** * na - * * @param tplayer na * @param msg na * @param order na */ def handle(tplayer: Player, msg: ItemTransactionMessage, order: Terminal.Exchange): Unit = { order match { + case Terminal.BuyEquipment(item) + if tplayer.avatar.purchaseCooldown(item.Definition).nonEmpty => + lastTerminalOrderFulfillment = true + sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false)) + case Terminal.BuyEquipment(item) => - tplayer.avatar.purchaseCooldown(item.Definition) match { - case Some(_) => - lastTerminalOrderFulfillment = true - sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false)) - case None => - avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition) - TaskWorkflow.execute(BuyNewEquipmentPutInInventory( - continent.GUID(tplayer.VehicleSeated) match { case Some(v: Vehicle) => v; case _ => player }, - tplayer, - msg.terminal_guid - )(item)) - } + avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition) + TaskWorkflow.execute(BuyNewEquipmentPutInInventory( + continent.GUID(tplayer.VehicleSeated) match { + case Some(v: Vehicle) => v + case _ => player + }, + tplayer, + msg.terminal_guid + )(item)) case Terminal.SellEquipment() => SellEquipmentFromInventory(tplayer, tplayer, msg.terminal_guid)(Player.FreeHandSlot) @@ -100,77 +101,75 @@ class SessionTerminalHandlers( avatarActor ! AvatarActor.SellImplant(msg.terminal_guid, implant) lastTerminalOrderFulfillment = true - case Terminal.BuyVehicle(vehicle, weapons, trunk) => - tplayer.avatar.purchaseCooldown(vehicle.Definition) match { - case Some(_) => - sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false)) - case None => - continent.map.terminalToSpawnPad - .find { case (termid, _) => termid == msg.terminal_guid.guid } - .collect { - case (a: Int, b: Int) => (continent.GUID(a), continent.GUID(b)) - case _ => (None, None) - } - .get match { - case (Some(term: Terminal), Some(pad: VehicleSpawnPad)) => - avatarActor ! AvatarActor.UpdatePurchaseTime(vehicle.Definition) - vehicle.Faction = tplayer.Faction - vehicle.Position = pad.Position - vehicle.Orientation = pad.Orientation + Vector3.z(pad.Definition.VehicleCreationZOrientOffset) - //default loadout, weapons - val vWeapons = vehicle.Weapons - weapons.foreach(entry => { - vWeapons.get(entry.start) match { - case Some(slot) => - entry.obj.Faction = tplayer.Faction - slot.Equipment = None - slot.Equipment = entry.obj - case None => - log.warn( - s"BuyVehicle: ${player.Name} tries to apply default loadout to $vehicle on spawn, but can not find a mounted weapon for ${entry.start}" - ) - } - }) - //default loadout, trunk - val vTrunk = vehicle.Trunk - vTrunk.Clear() - trunk.foreach(entry => { - entry.obj.Faction = tplayer.Faction - vTrunk.InsertQuickly(entry.start, entry.obj) - }) - TaskWorkflow.execute(registerVehicleFromSpawnPad(vehicle, pad, term)) - sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = true)) - 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)) - } - } + case Terminal.BuyVehicle(vehicle, _, _) + if tplayer.avatar.purchaseCooldown(vehicle.Definition).nonEmpty => + sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false)) lastTerminalOrderFulfillment = true - case Terminal.NoDeal() => - val order: String = if (msg == null) { - "missing order" - } else { - s"${msg.transaction_type} order" + 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)) } + .collect { case (Some(term: Terminal), Some(pad: VehicleSpawnPad)) => + avatarActor ! AvatarActor.UpdatePurchaseTime(vehicle.Definition) + vehicle.Faction = tplayer.Faction + vehicle.Position = pad.Position + vehicle.Orientation = pad.Orientation + Vector3.z(pad.Definition.VehicleCreationZOrientOffset) + //default loadout, weapons + val vWeapons = vehicle.Weapons + weapons.foreach { entry => + vWeapons.get(entry.start) match { + case Some(slot) => + entry.obj.Faction = tplayer.Faction + slot.Equipment = None + slot.Equipment = entry.obj + case None => + log.warn( + s"BuyVehicle: ${player.Name} tries to apply default loadout to $vehicle on spawn, but can not find a mounted weapon for ${entry.start}" + ) + } + } + //default loadout, trunk + val vTrunk = vehicle.Trunk + vTrunk.Clear() + trunk.foreach { entry => + entry.obj.Faction = tplayer.Faction + vTrunk.InsertQuickly(entry.start, entry.obj) + } + TaskWorkflow.execute(registerVehicleFromSpawnPad(vehicle, pad, term)) + sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = true)) + if (GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition)) { + sendResponse(UnuseItemMessage(player.GUID, msg.terminal_guid)) + } + }.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 } - 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 } } - /* */ + /* support */ /** * Construct tasking that adds a completed and registered vehicle into the scene. @@ -209,7 +208,7 @@ class SessionTerminalHandlers( val term_guid = terminal.GUID val targets = FindProximityUnitTargetsInScope(terminal) val currentTargets = terminal.Targets - targets.foreach(target => { + targets.foreach { target => if (!currentTargets.contains(target)) { StartUsingProximityUnit(terminal, target) } else if (targets.isEmpty) { @@ -217,7 +216,7 @@ class SessionTerminalHandlers( s"HandleProximityTerminalUse: ${player.Name} could not find valid targets to give to proximity unit ${terminal.Definition.Name}@${term_guid.guid}" ) } - }) + } } /** @@ -226,7 +225,7 @@ class SessionTerminalHandlers( * @return na */ def FindProximityUnitTargetsInScope(terminal: Terminal with ProximityUnit): Seq[PlanetSideGameObject] = { - terminal.Definition.asInstanceOf[ProximityDefinition].TargetValidation.keySet collect { + terminal.Definition.asInstanceOf[ProximityDefinition].TargetValidation.keySet.collect { case EffectTarget.Category.Player => Some(player) case EffectTarget.Category.Vehicle | EffectTarget.Category.Aircraft => continent.GUID(player.VehicleSeated) } collect { @@ -241,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 => @@ -256,7 +254,7 @@ class SessionTerminalHandlers( terminal.Definition match { case GlobalDefinitions.adv_med_terminal | GlobalDefinitions.medical_terminal => usingMedicalTerminal = Some(term_guid) - case _ => ; + case _ => () } } } @@ -269,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)) } } @@ -282,11 +280,8 @@ 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 = { - val term_guid = terminal.GUID - if (usingMedicalTerminal.contains(term_guid)) { - usingMedicalTerminal = None - } + def LocalStopUsingProximityUnit(terminal: Terminal with ProximityUnit): Unit = { + ForgetAllProximityTerminals(terminal.GUID) } /** @@ -296,21 +291,31 @@ class SessionTerminalHandlers( * @see `postStop` */ def CancelAllProximityUnits(): Unit = { - continent.GUID(usingMedicalTerminal) match { - case Some(terminal: Terminal with ProximityUnit) => + 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) - case _ => ; + ForgetAllProximityTerminals(guid) } } /** * na */ - def ForgetAllProximityTerminals(term_guid: PlanetSideGUID): Unit = { - if (usingMedicalTerminal.contains(term_guid)) { + def ForgetAllProximityTerminals(termGuid: PlanetSideGUID): Unit = { + if (usingMedicalTerminal.contains(termGuid)) { usingMedicalTerminal = None } } 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 f60a156eb..504399c98 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala @@ -3,7 +3,7 @@ package net.psforever.actors.session.support import akka.actor.{ActorContext, ActorRef, typed} import net.psforever.actors.session.AvatarActor -import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit} +import net.psforever.objects.equipment.{Equipment, JammableMountedWeapons, JammableUnit} import net.psforever.objects.guid.{GUIDTask, TaskWorkflow} import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.pad.VehicleSpawnPad @@ -31,153 +31,34 @@ class SessionVehicleHandlers( * @param reply na */ def handle(toChannel: String, guid: PlanetSideGUID, reply: VehicleResponse.Response): Unit = { - val tplayer_guid = if (player.HasGUID) player.GUID else PlanetSideGUID(0) + val resolvedPlayerGuid = if (player.HasGUID) { + player.GUID + } else { + Service.defaultPlayerGUID + } + val isNotSameTarget = resolvedPlayerGuid != guid reply match { - case VehicleResponse.AttachToRails(vehicle_guid, pad_guid) => - sendResponse(ObjectAttachMessage(pad_guid, vehicle_guid, 3)) - - case VehicleResponse.ChildObjectState(object_guid, pitch, yaw) => - if (tplayer_guid != guid) { - sendResponse(ChildObjectStateMessage(object_guid, pitch, yaw)) - } - - case VehicleResponse.ConcealPlayer(player_guid) => - sendResponse(GenericObjectActionMessage(player_guid, 9)) - - case VehicleResponse.DismountVehicle(bailType, wasKickedByDriver) => - if (tplayer_guid != guid) { - sendResponse(DismountVehicleMsg(guid, bailType, wasKickedByDriver)) - } - - case VehicleResponse.DeployRequest(object_guid, state, unk1, unk2, pos) => - if (tplayer_guid != guid) { - sendResponse(DeployRequestMessage(guid, object_guid, state, unk1, unk2, pos)) - } - - case VehicleResponse.DetachFromRails(vehicle_guid, pad_guid, pad_position, pad_orientation_z) => - val pad = continent.GUID(pad_guid).get.asInstanceOf[VehicleSpawnPad].Definition - sendResponse( - ObjectDetachMessage( - pad_guid, - vehicle_guid, - pad_position + Vector3.z(pad.VehicleCreationZOffset), - pad_orientation_z + pad.VehicleCreationZOrientOffset - ) - ) - - case VehicleResponse.EquipmentInSlot(pkt) => - if (tplayer_guid != guid) { - sendResponse(pkt) - } - - case VehicleResponse.FrameVehicleState(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA) => - if (tplayer_guid != guid) { - sendResponse(FrameVehicleStateMessage(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA)) - } - - case VehicleResponse.GenericObjectAction(object_guid, action) => - if (tplayer_guid != guid) { - sendResponse(GenericObjectActionMessage(object_guid, action)) - } - - case VehicleResponse.HitHint(source_guid) => - if (player.isAlive) { - sendResponse(HitHint(source_guid, player.GUID)) - } - - case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) => - if (tplayer_guid != guid) { - //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? - val obj_guid = obj.GUID - sendResponse(ObjectDeleteMessage(obj_guid, 0)) - sendResponse( - ObjectCreateDetailedMessage( - obj.Definition.ObjectId, - obj_guid, - ObjectCreateMessageParent(parent_guid, start), - con_data - ) - ) - } - - case VehicleResponse.KickPassenger(_, wasKickedByDriver, vehicle_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)) - if (tplayer_guid == guid) { - val typeOfRide = continent.GUID(vehicle_guid) match { - case Some(obj: Vehicle) => - sessionData.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!") - } - - case VehicleResponse.InventoryState2(obj_guid, parent_guid, value) => - if (tplayer_guid != guid) { - sendResponse(InventoryStateMessage(obj_guid, 0, parent_guid, value)) - } - - case VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata) => - //this is not be suitable for vehicles with people who are seated in it before it spawns (if that is possible) - if (tplayer_guid != guid) { - sendResponse(ObjectCreateMessage(vtype, vguid, vdata)) - Vehicles.ReloadAccessPermissions(vehicle, player.Name) - } - - case VehicleResponse.MountVehicle(vehicle_guid, seat) => - if (tplayer_guid != guid) { - sendResponse(ObjectAttachMessage(vehicle_guid, guid, seat)) - } - - case VehicleResponse.ObjectDelete(itemGuid) => - if (tplayer_guid != guid) { - sendResponse(ObjectDeleteMessage(itemGuid, 0)) - } - - case VehicleResponse.Ownership(vehicleGuid) => - if (tplayer_guid == guid) { // Only the player that owns this vehicle needs the ownership packet - avatarActor ! AvatarActor.SetVehicle(Some(vehicleGuid)) - sendResponse(PlanetsideAttributeMessage(tplayer_guid, 21, vehicleGuid)) - } - - case VehicleResponse.PlanetsideAttribute(vehicle_guid, attribute_type, attribute_value) => - if (tplayer_guid != guid) { - sendResponse(PlanetsideAttributeMessage(vehicle_guid, attribute_type, attribute_value)) - } - - case VehicleResponse.ResetSpawnPad(pad_guid) => - sendResponse(GenericObjectActionMessage(pad_guid, 23)) - - case VehicleResponse.RevealPlayer(player_guid) => - sendResponse(GenericObjectActionMessage(player_guid, 10)) - - case VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission) => - if (tplayer_guid != guid) { - sendResponse(PlanetsideAttributeMessage(vehicle_guid, seat_group, permission)) - } - - case VehicleResponse.StowEquipment(vehicle_guid, slot, item_type, item_guid, item_data) => - if (tplayer_guid != guid) { - //TODO prefer ObjectAttachMessage, but how to force ammo pools to update properly? - sendResponse( - ObjectCreateDetailedMessage(item_type, item_guid, ObjectCreateMessageParent(vehicle_guid, slot), item_data) - ) - } - - case VehicleResponse.UnloadVehicle(_, vehicle_guid) => - sendResponse(ObjectDeleteMessage(vehicle_guid, 0)) - - case VehicleResponse.UnstowEquipment(item_guid) => - if (tplayer_guid != guid) { - //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? - sendResponse(ObjectDeleteMessage(item_guid, 0)) - } + case VehicleResponse.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 case VehicleResponse.VehicleState( - vehicle_guid, + vehicleGuid, unk1, pos, ang, @@ -185,58 +66,190 @@ class SessionVehicleHandlers( unk2, unk3, unk4, - wheel_direction, + wheelDirection, unk5, unk6 - ) => - if (tplayer_guid != guid) { - sendResponse( - VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6) - ) - if (player.VehicleSeated.contains(vehicle_guid)) { - player.Position = pos - } - } + ) 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 VehicleResponse.ChildObjectState(objectGuid, pitch, yaw) if isNotSameTarget => + sendResponse(ChildObjectStateMessage(objectGuid, pitch, yaw)) + + case VehicleResponse.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 VehicleResponse.DismountVehicle(bailType, wasKickedByDriver) if isNotSameTarget => + sendResponse(DismountVehicleMsg(guid, bailType, wasKickedByDriver)) + + case VehicleResponse.MountVehicle(vehicleGuid, seat) if isNotSameTarget => + sendResponse(ObjectAttachMessage(vehicleGuid, guid, seat)) + + case VehicleResponse.DeployRequest(objectGuid, state, unk1, unk2, pos) if isNotSameTarget => + sendResponse(DeployRequestMessage(guid, objectGuid, state, unk1, unk2, pos)) + case VehicleResponse.SendResponse(msg) => sendResponse(msg) + case VehicleResponse.AttachToRails(vehicleGuid, padGuid) => + sendResponse(ObjectAttachMessage(padGuid, vehicleGuid, slot=3)) + + case VehicleResponse.ConcealPlayer(playerGuid) => + sendResponse(GenericObjectActionMessage(playerGuid, code=9)) + + case VehicleResponse.DetachFromRails(vehicleGuid, padGuid, padPosition, padOrientationZ) => + val pad = continent.GUID(padGuid).get.asInstanceOf[VehicleSpawnPad].Definition + sendResponse( + ObjectDetachMessage( + padGuid, + vehicleGuid, + padPosition + Vector3.z(pad.VehicleCreationZOffset), + padOrientationZ + pad.VehicleCreationZOrientOffset + ) + ) + + case VehicleResponse.EquipmentInSlot(pkt) if isNotSameTarget => + sendResponse(pkt) + + case VehicleResponse.GenericObjectAction(objectGuid, action) if isNotSameTarget => + sendResponse(GenericObjectActionMessage(objectGuid, action)) + + case VehicleResponse.HitHint(sourceGuid) if player.isAlive => + sendResponse(HitHint(sourceGuid, player.GUID)) + + case VehicleResponse.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 VehicleResponse.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) => + sessionData.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!") + + case VehicleResponse.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 VehicleResponse.InventoryState2(objGuid, parentGuid, value) if isNotSameTarget => + sendResponse(InventoryStateMessage(objGuid, unk=0, parentGuid, value)) + + case VehicleResponse.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 VehicleResponse.ObjectDelete(itemGuid) if isNotSameTarget => + sendResponse(ObjectDeleteMessage(itemGuid, unk1=0)) + + case VehicleResponse.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 VehicleResponse.PlanetsideAttribute(vehicleGuid, attributeType, attributeValue) if isNotSameTarget => + sendResponse(PlanetsideAttributeMessage(vehicleGuid, attributeType, attributeValue)) + + case VehicleResponse.ResetSpawnPad(padGuid) => + sendResponse(GenericObjectActionMessage(padGuid, code=23)) + + case VehicleResponse.RevealPlayer(playerGuid) => + sendResponse(GenericObjectActionMessage(playerGuid, code=10)) + + case VehicleResponse.SeatPermissions(vehicleGuid, seatGroup, permission) if isNotSameTarget => + sendResponse(PlanetsideAttributeMessage(vehicleGuid, seatGroup, permission)) + + case VehicleResponse.StowEquipment(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 VehicleResponse.UnloadVehicle(_, vehicleGuid) => + sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=0)) + + case VehicleResponse.UnstowEquipment(itemGuid) if isNotSameTarget => + //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? + sendResponse(ObjectDeleteMessage(itemGuid, unk1=0)) + case VehicleResponse.UpdateAmsSpawnPoint(list) => sessionData.zoning.spawn.amsSpawnPoints = list.filter(tube => tube.Faction == player.Faction) sessionData.zoning.spawn.DrawCurrentAmsSpawnPoint() - case VehicleResponse.TransferPassengerChannel(old_channel, temp_channel, vehicle, vehicle_to_delete) => - if (tplayer_guid != guid) { - sessionData.zoning.interstellarFerry = Some(vehicle) - sessionData.zoning.interstellarFerryTopLevelGUID = Some(vehicle_to_delete) - continent.VehicleEvents ! Service.Leave( - Some(old_channel) - ) //old vehicle-specific channel (was s"${vehicle.Actor}") - galaxyService ! Service.Join(temp_channel) //temporary vehicle-specific channel - log.debug(s"TransferPassengerChannel: ${player.Name} now subscribed to $temp_channel for vehicle gating") - } + case VehicleResponse.TransferPassengerChannel(oldChannel, tempChannel, vehicle, vehicleToDelete) if isNotSameTarget => + sessionData.zoning.interstellarFerry = Some(vehicle) + sessionData.zoning.interstellarFerryTopLevelGUID = Some(vehicleToDelete) + continent.VehicleEvents ! Service.Leave(Some(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 VehicleResponse.KickCargo(vehicle, speed, delay) => - if (player.VehicleSeated.nonEmpty && sessionData.zoning.spawn.deadState == DeadState.Alive) { - if (speed > 0) { - val strafe = - if (Vehicles.CargoOrientation(vehicle) == 1) 2 - else 1 - val reverseSpeed = - if (strafe > 1) 0 - else speed - //strafe or reverse, not both - sessionData.vehicles.serverVehicleControlVelocity = Some(reverseSpeed) - sendResponse(ServerVehicleOverrideMsg(lock_accelerator=true, lock_wheel=true, reverse=true, unk4=false, 0, strafe, reverseSpeed, Some(0))) - import scala.concurrent.ExecutionContext.Implicits.global - context.system.scheduler.scheduleOnce( - delay milliseconds, - context.self, - VehicleServiceResponse(toChannel, PlanetSideGUID(0), VehicleResponse.KickCargo(vehicle, 0, delay)) - ) - } else { - sessionData.vehicles.serverVehicleControlVelocity = None - sendResponse(ServerVehicleOverrideMsg(lock_accelerator=false,lock_wheel=false, reverse=false, unk4=false, 0, 0, 0, None)) - } + case VehicleResponse.KickCargo(vehicle, speed, delay) + if player.VehicleSeated.nonEmpty && sessionData.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 + sessionData.vehicles.serverVehicleControlVelocity = Some(reverseSpeed) + sendResponse(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 + context.system.scheduler.scheduleOnce( + delay milliseconds, + context.self, + VehicleServiceResponse(toChannel, PlanetSideGUID(0), VehicleResponse.KickCargo(vehicle, speed=0, delay)) + ) + + case VehicleResponse.KickCargo(_, _, _) + if player.VehicleSeated.nonEmpty && sessionData.zoning.spawn.deadState == DeadState.Alive => + sessionData.vehicles.serverVehicleControlVelocity = None + sendResponse(ServerVehicleOverrideMsg( + lock_accelerator=false, + lock_wheel=false, + reverse=false, + unk4=false, + lock_vthrust=0, + lock_strafe=0, + movement_speed=0, + 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, _) => @@ -244,30 +257,25 @@ class SessionVehicleHandlers( 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, 22, 1L)) //mount points off - sendResponse(PlanetsideAttributeMessage(player.GUID, 21, vehicle_guid)) //ownership - vehicle.MountPoints.find { case (_, mp) => mp.seatIndex == 0 } match { - case Some((mountPoint, _)) => vehicle.Actor ! Mountable.TryMount(player, mountPoint) - case _ => ; + 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.PlayerSeatedInVehicle(vehicle, _) => val vehicle_guid = vehicle.GUID - sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on + sendResponse(PlanetsideAttributeMessage(vehicle_guid, attribute_type=22, attribute_value=0L)) //mount points on Vehicles.ReloadAccessPermissions(vehicle, player.Name) sessionData.vehicles.ServerVehicleLock(vehicle) case VehicleResponse.ServerVehicleOverrideStart(vehicle, _) => val vdef = vehicle.Definition - sessionData.vehicles.ServerVehicleOverride(vehicle, vdef.AutoPilotSpeed1, if (GlobalDefinitions.isFlightVehicle(vdef)) 1 else 0) + sessionData.vehicles.ServerVehicleOverride( + vehicle, + vdef.AutoPilotSpeed1, + flight= if (GlobalDefinitions.isFlightVehicle(vdef)) { 1 } else { 0 } + ) case VehicleResponse.ServerVehicleOverrideEnd(vehicle, _) => session = session.copy(avatar = avatar.copy(vehicle = Some(vehicle.GUID))) @@ -277,63 +285,77 @@ class SessionVehicleHandlers( sendResponse(ChatMsg( ChatMessageType.CMT_OPEN, wideContents=true, - "", + recipient="", s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}", - None + note=None )) case VehicleResponse.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) 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, "", msg, None)) + sendResponse(ChatMsg(isType, flag, recipient="", msg, None)) - case VehicleResponse.ChangeLoadout(target, old_weapons, added_weapons, old_inventory, new_inventory) => + case VehicleResponse.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) match { - case Some(vehicle: Vehicle) => - if (player.avatar.vehicle.contains(target)) { - import net.psforever.login.WorldSession.boolToInt - //owner: must unregister old equipment, and register and install new equipment - (old_weapons ++ old_inventory).foreach { - case (obj, eguid) => - sendResponse(ObjectDeleteMessage(eguid, 0)) - TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) - } - sessionData.applyPurchaseTimersBeforePackingLoadout(player, vehicle, added_weapons ++ new_inventory) - //jammer or unjamm new weapons based on vehicle status - val vehicleJammered = vehicle.Jammed - added_weapons - .map { - _.obj - } - .collect { - case jamItem: JammableUnit if jamItem.Jammed != vehicleJammered => - jamItem.Jammed = vehicleJammered - JammableMountedWeapons.JammedWeaponStatus(vehicle.Zone, jamItem, vehicleJammered) - } - } else if (sessionData.accessedContainer.map { _.GUID }.contains(target)) { - //external participant: observe changes to equipment - (old_weapons ++ old_inventory).foreach { case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, 0)) } + 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)) + } + sessionData.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) } - vehicle.PassengerInSeat(player) match { - case Some(seatNum) => - //participant: observe changes to equipment - (old_weapons ++ old_inventory).foreach { - case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, 0)) - } - sessionData.updateWeaponAtSeatPosition(vehicle, seatNum) - case None => - //observer: observe changes to external equipment - old_weapons.foreach { case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, 0)) } - } - case _ => ; + changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory) } - case _ => ; + case VehicleResponse.ChangeLoadout(target, oldWeapons, _, oldInventory, _) + if sessionData.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 VehicleResponse.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 _ => () + } + } + + private def changeLoadoutDeleteOldEquipment( + vehicle: Vehicle, + oldWeapons: Iterable[(Equipment, PlanetSideGUID)], + oldInventory: Iterable[(Equipment, PlanetSideGUID)] + ): Unit = { + vehicle.PassengerInSeat(player) match { + case Some(seatNum) => + //participant: observe changes to equipment + (oldWeapons ++ oldInventory).foreach { + case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0)) + } + sessionData.updateWeaponAtSeatPosition(vehicle, seatNum) + case None => + //observer: observe changes to external equipment + oldWeapons.foreach { case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0)) } } } } 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