diff --git a/common/src/main/scala/net/psforever/objects/OwnableByPlayer.scala b/common/src/main/scala/net/psforever/objects/OwnableByPlayer.scala new file mode 100644 index 00000000..dd24d89e --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/OwnableByPlayer.scala @@ -0,0 +1,65 @@ +// Copyright (c) 2019 PSForever +package net.psforever.objects + +import net.psforever.packet.game.PlanetSideGUID + +trait OwnableByPlayer { + private var owner : Option[PlanetSideGUID] = None + private var ownerName : Option[String] = None + + def Owner : Option[PlanetSideGUID] = owner + + def Owner_=(owner : PlanetSideGUID) : Option[PlanetSideGUID] = Owner_=(Some(owner)) + + def Owner_=(owner : Player) : Option[PlanetSideGUID] = Owner_=(Some(owner.GUID)) + + def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = { + owner match { + case Some(_) => + this.owner = owner + case None => + this.owner = None + } + Owner + } + + def OwnerName : Option[String] = ownerName + + def OwnerName_=(owner : String) : Option[String] = OwnerName_=(Some(owner)) + + def OwnerName_=(owner : Player) : Option[String] = OwnerName_=(Some(owner.Name)) + + def OwnerName_=(owner : Option[String]) : Option[String] = { + owner match { + case Some(_) => + ownerName = owner + case None => + ownerName = None + } + OwnerName + } + + /** + * na + * @param player na + * @return na + */ + def AssignOwnership(player : Player) : OwnableByPlayer = AssignOwnership(Some(player)) + + /** + * na + * @param playerOpt na + * @return na + */ + def AssignOwnership(playerOpt : Option[Player]) : OwnableByPlayer = { + playerOpt match { + case Some(player) => + Owner = player + OwnerName = player + case None => + Owner = None + OwnerName = None + } + this + } +} diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index 0026124c..f4c141de 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -72,10 +72,10 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ with MountedWeapons with Deployment with Vitality + with OwnableByPlayer with StandardResistanceProfile with Container { private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR - private var owner : Option[PlanetSideGUID] = None private var health : Int = 1 private var shields : Int = 0 private var decal : Int = 0 @@ -139,26 +139,6 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ MountedIn } - def Owner : Option[PlanetSideGUID] = { - this.owner - } - - def Owner_=(owner : PlanetSideGUID) : Option[PlanetSideGUID] = Owner_=(Some(owner)) - - def Owner_=(owner : Player) : Option[PlanetSideGUID] = Owner_=(Some(owner.GUID)) - - def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = { - owner match { - case Some(_) => - if(Definition.CanBeOwned) { - this.owner = owner - } - case None => - this.owner = None - } - Owner - } - def Health : Int = { health } @@ -499,7 +479,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ if(trunkAccess.isEmpty || trunkAccess.contains(player.GUID)) { groupPermissions(3) match { case VehicleLockState.Locked => //only the owner - owner.isEmpty || (owner.isDefined && player.GUID == owner.get) + Owner.isEmpty || (Owner.isDefined && player.GUID == Owner.get) case VehicleLockState.Group => //anyone in the owner's squad or platoon faction == player.Faction //TODO this is not correct case VehicleLockState.Empire => //anyone of the owner's faction diff --git a/common/src/main/scala/net/psforever/objects/ce/Deployable.scala b/common/src/main/scala/net/psforever/objects/ce/Deployable.scala index e72c7544..799751cf 100644 --- a/common/src/main/scala/net/psforever/objects/ce/Deployable.scala +++ b/common/src/main/scala/net/psforever/objects/ce/Deployable.scala @@ -9,12 +9,11 @@ import net.psforever.packet.game.{DeployableIcon, PlanetSideGUID} import net.psforever.types.PlanetSideEmpire trait Deployable extends FactionAffinity - with Vitality { + with Vitality + with OwnableByPlayer { this : PlanetSideGameObject => private var health : Int = 1 private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL - private var owner : Option[PlanetSideGUID] = None - private var ownerName : Option[String] = None def Health : Int = health @@ -32,38 +31,6 @@ trait Deployable extends FactionAffinity Faction } - def Owner : Option[PlanetSideGUID] = owner - - def Owner_=(owner : PlanetSideGUID) : Option[PlanetSideGUID] = Owner_=(Some(owner)) - - def Owner_=(owner : Player) : Option[PlanetSideGUID] = Owner_=(Some(owner.GUID)) - - def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = { - owner match { - case Some(_) => - this.owner = owner - case None => - this.owner = None - } - Owner - } - - def OwnerName : Option[String] = ownerName - - def OwnerName_=(owner : String) : Option[String] = OwnerName_=(Some(owner)) - - def OwnerName_=(owner : Player) : Option[String] = OwnerName_=(Some(owner.Name)) - - def OwnerName_=(owner : Option[String]) : Option[String] = { - owner match { - case Some(_) => - ownerName = owner - case None => - ownerName = None - } - OwnerName - } - def DamageModel : DamageResistanceModel = Definition.asInstanceOf[DamageResistanceModel] def Definition : ObjectDefinition with BaseDeployableDefinition diff --git a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala index 69c4642e..d2eeb66e 100644 --- a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala @@ -155,11 +155,11 @@ import scodec.codecs._ * `81 - ???`
* `113 - Vehicle capacitor - e.g. Leviathan EMP charge` * - * @param player_guid the player - * @param attribute_type na - * @param attribute_value na + * @param guid the object + * @param attribute_type the field + * @param attribute_value the value */ -final case class PlanetsideAttributeMessage(player_guid : PlanetSideGUID, +final case class PlanetsideAttributeMessage(guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends PlanetSideGamePacket { @@ -169,12 +169,16 @@ final case class PlanetsideAttributeMessage(player_guid : PlanetSideGUID, } object PlanetsideAttributeMessage extends Marshallable[PlanetsideAttributeMessage] { - def apply(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Int) : PlanetsideAttributeMessage = { - PlanetsideAttributeMessage(player_guid, attribute_type, attribute_value.toLong) + def apply(guid : PlanetSideGUID, attribute_type : Int, attribute_value : Int) : PlanetsideAttributeMessage = { + PlanetsideAttributeMessage(guid, attribute_type, attribute_value.toLong) + } + + def apply(guid : PlanetSideGUID, attribute_type : Int, attribute_value : PlanetSideGUID) : PlanetsideAttributeMessage = { + PlanetsideAttributeMessage(guid, attribute_type, attribute_value.guid) } implicit val codec : Codec[PlanetsideAttributeMessage] = ( - ("player_guid" | PlanetSideGUID.codec) :: + ("guid" | PlanetSideGUID.codec) :: ("attribute_type" | uint8L) :: ("attribute_value" | uint32L) ).as[PlanetsideAttributeMessage] diff --git a/common/src/main/scala/services/vehicle/VehicleAction.scala b/common/src/main/scala/services/vehicle/VehicleAction.scala index e26fb0fb..982f8977 100644 --- a/common/src/main/scala/services/vehicle/VehicleAction.scala +++ b/common/src/main/scala/services/vehicle/VehicleAction.scala @@ -36,5 +36,6 @@ object VehicleAction { final case class TransferPassengerChannel(player_guid : PlanetSideGUID, temp_channel : String, new_channel : String, vehicle : Vehicle) extends Action final case class TransferPassenger(player_guid : PlanetSideGUID, temp_channel : String, vehicle : Vehicle, vehicle_to_delete : PlanetSideGUID) extends Action - final case class KickCargo(player_guid : PlanetSideGUID, cargo : Vehicle, speed : Int) extends Action + final case class ForceDismountVehicleCargo(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) extends Action + final case class KickCargo(player_guid : PlanetSideGUID, cargo : Vehicle, speed : Int, delay : Long) extends Action } diff --git a/common/src/main/scala/services/vehicle/VehicleResponse.scala b/common/src/main/scala/services/vehicle/VehicleResponse.scala index 1442205e..ca312e94 100644 --- a/common/src/main/scala/services/vehicle/VehicleResponse.scala +++ b/common/src/main/scala/services/vehicle/VehicleResponse.scala @@ -40,5 +40,6 @@ object VehicleResponse { final case class TransferPassengerChannel(old_channel : String, temp_channel : String, vehicle : Vehicle) extends Response final case class TransferPassenger(temp_channel : String, vehicle : Vehicle, vehicle_to_delete : PlanetSideGUID) extends Response - final case class KickCargo(cargo : Vehicle, speed : Int) extends Response + final case class ForceDismountVehicleCargo(vehicle_guid : PlanetSideGUID, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) extends Response + final case class KickCargo(cargo : Vehicle, speed : Int, delay : Long) extends Response } diff --git a/common/src/main/scala/services/vehicle/VehicleService.scala b/common/src/main/scala/services/vehicle/VehicleService.scala index 7098454f..b32c0721 100644 --- a/common/src/main/scala/services/vehicle/VehicleService.scala +++ b/common/src/main/scala/services/vehicle/VehicleService.scala @@ -140,9 +140,13 @@ class VehicleService extends Actor { VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.TransferPassenger(temp_channel, vehicle, vehicle_to_delete)) ) - case VehicleAction.KickCargo(player_guid, cargo, speed) => + case VehicleAction.ForceDismountVehicleCargo(player_guid, vehicle_guid, bailed, requestedByPassenger, kicked) => VehicleEvents.publish( - VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickCargo(cargo, speed)) + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.ForceDismountVehicleCargo(vehicle_guid, bailed, requestedByPassenger, kicked)) + ) + case VehicleAction.KickCargo(player_guid, cargo, speed, delay) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickCargo(cargo, speed, delay)) ) case _ => ; } diff --git a/common/src/main/scala/services/vehicle/support/VehicleRemover.scala b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala index f5b0707b..de832306 100644 --- a/common/src/main/scala/services/vehicle/support/VehicleRemover.scala +++ b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala @@ -4,6 +4,7 @@ package services.vehicle.support import net.psforever.objects.Vehicle import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.zones.Zone +import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.DriveState import services.{RemoverActor, Service} import services.vehicle.{VehicleAction, VehicleServiceMessage} @@ -26,6 +27,21 @@ class VehicleRemover extends RemoverActor { val vehicleGUID = vehicle.GUID val zoneId = entry.zone.Id vehicle.Actor ! Vehicle.PrepareForDeletion + //escape being someone else's cargo + (vehicle.MountedIn match { + case Some(carrierGUID) => + entry.zone.Vehicles.find(v => v.GUID == carrierGUID) + case None => + None + }) match { + case Some(carrier : Vehicle) => + val driverName = carrier.Seats(0).Occupant match { + case Some(driver) => driver.Name + case _ => zoneId + } + context.parent ! VehicleServiceMessage(s"$driverName", VehicleAction.ForceDismountVehicleCargo(PlanetSideGUID(0), vehicleGUID, true, false, false)) + case _ => ; + } //kick out all passengers vehicle.Seats.values.foreach(seat => { seat.Occupant match { @@ -37,6 +53,12 @@ class VehicleRemover extends RemoverActor { } case None => ; } + //abandon all cargo + vehicle.CargoHolds.values + .collect { case hold if hold.isOccupied => + val cargo = hold.Occupant.get + context.parent ! VehicleServiceMessage(zoneId, VehicleAction.ForceDismountVehicleCargo(PlanetSideGUID(0), cargo.GUID, true, false, false)) + } }) } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 2c566124..e285dbc5 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -64,7 +64,6 @@ import services.local.support.{HackCaptureActor, RouterTelepadActivation} import services.support.SupportActor class WorldSessionActor extends Actor with MDCContextAware { - import WorldSessionActor._ private[this] val log = org.log4s.getLogger @@ -132,7 +131,7 @@ class WorldSessionActor extends Actor with MDCContextAware { */ implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0 - override def postStop() = { + override def postStop() : Unit = { //TODO normally, player avatar persists a minute or so after disconnect; we are subject to the SessionReaper clientKeepAlive.cancel reviveTimer.cancel @@ -160,21 +159,22 @@ class WorldSessionActor extends Actor with MDCContextAware { if(player.isAlive) { //actually being alive or manually deconstructing player.Position = Vector3.Zero - if(player.VehicleSeated.nonEmpty) { - //quickly and briefly kill player to avoid disembark animation - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0)) - DismountVehicleOnLogOut() - DisownVehicle() + //if seated, dismount + player.VehicleSeated match { + case Some(_) => + //quickly and briefly kill player to avoid disembark animation? + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0)) + DismountVehicleOnLogOut() + case _ => ; } avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid)) taskResolver ! GUIDTask.UnregisterAvatar(player)(continent.GUID) //TODO normally, the actual player avatar persists a minute or so after the user disconnects } else if(continent.LivePlayers.contains(player) && !continent.Corpses.contains(player)) { - //player disconnected while waiting for a revive + //player disconnected while waiting for a revive, maybe //similar to handling ReleaseAvatarRequestMessage player.Release - DisownVehicle() player.VehicleSeated match { case None => FriskCorpse(player) //TODO eliminate dead letters @@ -191,7 +191,7 @@ class WorldSessionActor extends Actor with MDCContextAware { taskResolver ! GUIDTask.UnregisterAvatar(player)(continent.GUID) } - case Some(vehicle_guid) => + case Some(_) => val player_guid = player.GUID player.Position = Vector3.Zero //save character before doing this avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid)) @@ -199,7 +199,8 @@ class WorldSessionActor extends Actor with MDCContextAware { DismountVehicleOnLogOut() } } - DisownVehicle() + //disassociate and start the deconstruction timer for any currently owned vehicle + SpecialCaseDisownVehicle() continent.Population ! Zone.Population.Leave(avatar) } } @@ -221,6 +222,29 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + /** + * If a vehicle is owned by a character, disassociate the vehicle, then schedule it for deconstruction. + * @see `DisownVehicle()` + * @return the vehicle previously owned, if any + */ + def SpecialCaseDisownVehicle() : Option[Vehicle] = { + DisownVehicle() match { + case out @ Some(vehicle) => + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(vehicle), continent)) + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent, + if(vehicle.Flying) { + Some(0 seconds) //immediate deconstruction + } + else { + vehicle.Definition.DeconstructionTime //normal deconstruction + } + )) + out + case None => + None + } + } + def receive = Initializing def Initializing : Receive = { @@ -373,7 +397,7 @@ class WorldSessionActor extends Actor with MDCContextAware { avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot)) } sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off - sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player.GUID.guid)) //ownership + sendResponse(PlanetsideAttributeMessage(player.GUID, 21, vehicle_guid)) //ownership case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, pad) => val vehicle_guid = vehicle.GUID @@ -540,8 +564,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(obj : BoomerDeployable) => val guid = obj.GUID val factionOnContinentChannel = s"${continent.Id}/${player.Faction}" - obj.Owner = None - obj.OwnerName = None + obj.AssignOwnership(None) avatar.Deployables.Remove(obj) UpdateDeployableUIElements(avatar.Deployables.UpdateUIElement(obj.Definition.Item)) localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent)) @@ -580,8 +603,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val playerGUID = player.GUID val faction = player.Faction val factionOnContinentChannel = s"${continent.Id}/${faction}" - obj.Owner = playerGUID - obj.OwnerName = player.Name + obj.AssignOwnership(player) obj.Faction = faction avatar.Deployables.Add(obj) UpdateDeployableUIElements(avatar.Deployables.UpdateUIElement(obj.Definition.Item)) @@ -635,8 +657,7 @@ class WorldSessionActor extends Actor with MDCContextAware { TryDropConstructionTool(tool, index, obj.Position) sendResponse(ObjectDeployedMessage.Failure(obj.Definition.Name)) obj.Position = Vector3.Zero - obj.Owner = None - obj.OwnerName = None + obj.AssignOwnership(None) continent.Deployables ! Zone.Deployable.Dismiss(obj) } @@ -780,6 +801,7 @@ class WorldSessionActor extends Actor with MDCContextAware { localService ! Service.Leave(Some(continentId)) localService ! Service.Leave(Some(factionOnContinentChannel)) vehicleService ! Service.Leave(Some(continentId)) + vehicleService ! Service.Leave(Some(factionOnContinentChannel)) player.Continent = zoneId continent = zone continent.Population ! Zone.Population.Join(avatar) @@ -1348,8 +1370,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case None => ; } - tplayer.VehicleOwned = Some(obj_guid) - obj.Owner = Some(tplayer.GUID) + OwnVehicle(obj, tplayer) } AccessContents(obj) UpdateWeaponAtSeatPosition(obj, seat_num) @@ -1764,11 +1785,11 @@ class WorldSessionActor extends Actor with MDCContextAware { avatar.Certifications += cert StartBundlingPackets() AddToDeployableQuantities(cert, player.Certifications) - sendResponse(PlanetsideAttributeMessage(guid, 24, cert.id.toLong)) + sendResponse(PlanetsideAttributeMessage(guid, 24, cert.id)) tplayer.Certifications.intersect(Certification.Dependencies.Like(cert)).foreach(entry => { log.info(s"$cert replaces the learned certification $entry that cost ${Certification.Cost.Of(entry)} points") avatar.Certifications -= entry - sendResponse(PlanetsideAttributeMessage(guid, 25, entry.id.toLong)) + sendResponse(PlanetsideAttributeMessage(guid, 25, entry.id)) }) StopBundlingPackets() sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, true)) @@ -1787,12 +1808,12 @@ class WorldSessionActor extends Actor with MDCContextAware { avatar.Certifications -= cert StartBundlingPackets() RemoveFromDeployablesQuantities(cert, player.Certifications) - sendResponse(PlanetsideAttributeMessage(guid, 25, cert.id.toLong)) + sendResponse(PlanetsideAttributeMessage(guid, 25, cert.id)) tplayer.Certifications.intersect(Certification.Dependencies.FromAll(cert)).foreach(entry => { log.info(s"$name is also forgetting the ${Certification.Cost.Of(entry)}-point $entry certification which depends on $cert") avatar.Certifications -= entry RemoveFromDeployablesQuantities(entry, player.Certifications) - sendResponse(PlanetsideAttributeMessage(guid, 25, entry.id.toLong)) + sendResponse(PlanetsideAttributeMessage(guid, 25, entry.id)) }) StopBundlingPackets() sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, true)) @@ -2023,7 +2044,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case VehicleResponse.Ownership(vehicle_guid) => - sendResponse(PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid)) + sendResponse(PlanetsideAttributeMessage(tplayer_guid, 21, vehicle_guid)) case VehicleResponse.PlanetsideAttribute(vehicle_guid, attribute_type, attribute_value) => if(tplayer_guid != guid) { @@ -2098,7 +2119,9 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - case VehicleResponse.KickCargo(vehicle, speed) => + case VehicleResponse.ForceDismountVehicleCargo(vehicle_guid, bailed, requestedByPassenger, kicked) => + DismountVehicleCargo(tplayer_guid, vehicle_guid, bailed, requestedByPassenger, kicked) + case VehicleResponse.KickCargo(vehicle, speed, delay) => if(player.VehicleSeated.nonEmpty && deadState == DeadState.Alive) { if(speed > 0) { val strafe = if(CargoOrientation(vehicle) == 1) 2 else 1 @@ -2107,7 +2130,7 @@ class WorldSessionActor extends Actor with MDCContextAware { controlled = Some(reverseSpeed) sendResponse(ServerVehicleOverrideMsg(true, true, true, false, 0, strafe, reverseSpeed, Some(0))) import scala.concurrent.ExecutionContext.Implicits.global - context.system.scheduler.scheduleOnce(4500 milliseconds, self, VehicleServiceResponse(toChannel, tplayer_guid, VehicleResponse.KickCargo(vehicle, 0))) + context.system.scheduler.scheduleOnce(delay milliseconds, self, VehicleServiceResponse(toChannel, tplayer_guid, VehicleResponse.KickCargo(vehicle, 0, delay))) } else { controlled = None @@ -2678,12 +2701,6 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(SetCurrentAvatarMessage(guid, 0, 0)) sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on //TODO once per respawn? sendResponse(PlayerStateShiftMessage(ShiftState(1, tplayer.Position, tplayer.Orientation.z))) - //transfer vehicle ownership - player.VehicleOwned match { - case Some(vehicle_guid) if player.VehicleSeated.contains(vehicle_guid) => - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.Ownership(guid, vehicle_guid)) - case _ => ; - } if(spectator) { sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None)) } @@ -2734,6 +2751,14 @@ class WorldSessionActor extends Actor with MDCContextAware { }) StopBundlingPackets() drawDeloyableIcon = DontRedrawIcons + //assert or transfer vehicle ownership + continent.GUID(player.VehicleOwned) match { + case Some(vehicle : Vehicle) if vehicle.OwnerName.contains(tplayer.Name) => + vehicle.Owner = guid + vehicleService ! VehicleServiceMessage(s"${continent.Id}/${tplayer.Faction}", VehicleAction.Ownership(guid, vehicle.GUID)) + case _ => + player.VehicleOwned = None + } //if driver of a vehicle, summon any passengers and cargo vehicles left behind on previous continent GetVehicleAndSeat() match { case (Some(vehicle), Some(0)) => @@ -2863,16 +2888,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ DismountVehicleCargoMsg(player_guid, vehicle_guid, bailed, requestedByPassenger, kicked) => log.info(msg.toString) if(!requestedByPassenger) { - continent.GUID(vehicle_guid) match { - case Some(cargo : Vehicle) => - continent.GUID(cargo.MountedIn) match { - case Some(ferry : Vehicle) => - HandleDismountVehicleCargo(player_guid, vehicle_guid, cargo, ferry.GUID, ferry, bailed, requestedByPassenger, kicked) - case _ => - log.warn(s"DismountVehicleCargoMsg: target ${cargo.Definition.Name} does not know what treats it as cargo") - } - case _ => ; - } + DismountVehicleCargo(player_guid, vehicle_guid, bailed, requestedByPassenger, kicked) } case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) => @@ -2908,6 +2924,7 @@ class WorldSessionActor extends Actor with MDCContextAware { localService ! Service.Join(continentId) localService ! Service.Join(factionOnContinentChannel) vehicleService ! Service.Join(continentId) + vehicleService ! Service.Join(factionOnContinentChannel) galaxyService ! Service.Join("galaxy") configZone(continent) sendResponse(TimeOfDayMessage(1191182336)) @@ -3342,7 +3359,7 @@ class WorldSessionActor extends Actor with MDCContextAware { deadState = DeadState.Release //cancel movement updates PlayerActionsToCancel() continent.GUID(player.VehicleSeated) match { - case Some(vehicle : Vehicle) => + case Some(vehicle : Vehicle) if vehicle.MountedIn.isEmpty => vehicle.PassengerInSeat(player) match { case Some(0) => vehicle.Position = pos @@ -3354,7 +3371,7 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Position = pos //avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, player.GUID)) LoadZonePhysicalSpawnPoint(zone, pos, Vector3.Zero, 0) - case _ => //seated in something that is not a vehicle, or we're dead, in which case we can't move + case _ => //seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move deadState = DeadState.Alive } @@ -3366,7 +3383,7 @@ class WorldSessionActor extends Actor with MDCContextAware { deadState = DeadState.Release //cancel movement updates PlayerActionsToCancel() continent.GUID(player.VehicleSeated) match { - case Some(vehicle : Vehicle) if player.isAlive => + case Some(vehicle : Vehicle) if vehicle.MountedIn.isEmpty => vehicle.PassengerInSeat(player) match { case Some(0) => vehicle.Position = pos @@ -3378,7 +3395,7 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Position = pos sendResponse(PlayerStateShiftMessage(ShiftState(0, pos, player.Orientation.z, None))) deadState = DeadState.Alive //must be set here - case _ => //seated in something that is not a vehicle, or we're dead, in which case we can't move + case _ => //seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move deadState = DeadState.Alive } @@ -4276,8 +4293,7 @@ class WorldSessionActor extends Actor with MDCContextAware { dObj.Position = pos dObj.Orientation = orient dObj.Faction = player.Faction - dObj.Owner = player.GUID - dObj.OwnerName = player.Name + dObj.AssignOwnership(player) val tasking : TaskResolver.GiveTask = dObj match { case turret : TurretDeployable => GUIDTask.RegisterDeployableTurret(turret)(continent.GUID) @@ -4617,7 +4633,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ DeployRequestMessage(player_guid, vehicle_guid, deploy_state, unk2, unk3, pos) => log.info(s"DeployRequest: $msg") - if(player.VehicleOwned == Some(vehicle_guid) && player.VehicleOwned == player.VehicleSeated) { + if(player.VehicleOwned.contains(vehicle_guid) && player.VehicleOwned == player.VehicleSeated) { continent.GUID(vehicle_guid) match { case Some(obj : Vehicle) => obj.Actor ! Deployment.TryDeploymentChange(deploy_state) @@ -5038,7 +5054,7 @@ class WorldSessionActor extends Actor with MDCContextAware { def Execute(resolver : ActorRef) : Unit = { localDriver.VehicleSeated = localVehicle.GUID - localVehicle.Owner = localDriver.GUID + OwnVehicle(localVehicle, localDriver) localAnnounce ! NewPlayerLoaded(localDriver) //alerts WSA resolver ! scala.util.Success(this) } @@ -5322,12 +5338,39 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param vehicle the `Vehicle` */ def ReloadVehicleAccessPermissions(vehicle : Vehicle) : Unit = { - val vehicle_guid = vehicle.GUID - (0 to 3).foreach(group => { - sendResponse( - PlanetsideAttributeMessage(vehicle_guid, group + 10, vehicle.PermissionGroup(group).get.id) - ) - }) + if(vehicle.Faction == player.Faction) { + val vehicle_guid = vehicle.GUID + (0 to 3).foreach(group => { + sendResponse( + PlanetsideAttributeMessage(vehicle_guid, group + 10, vehicle.PermissionGroup(group).get.id) + ) + }) + } + } + + /** + * na + * @param vehicle na + * @param tplayer na + * @return na + */ + def OwnVehicle(vehicle : Vehicle, tplayer : Player) : Option[Vehicle] = OwnVehicle(vehicle, Some(tplayer)) + + /** + * na + * @param vehicle na + * @param playerOpt na + * @return na + */ + def OwnVehicle(vehicle : Vehicle, playerOpt : Option[Player]) : Option[Vehicle] = { + playerOpt match { + case Some(tplayer) => + tplayer.VehicleOwned = vehicle.GUID + vehicle.AssignOwnership(playerOpt) + Some(vehicle) + case None => + None + } } /** @@ -5340,7 +5383,6 @@ class WorldSessionActor extends Actor with MDCContextAware { * The vehicle must exist in the game world on the current continent. * This is similar but unrelated to the natural exchange of ownership when someone else sits in the vehicle's driver seat. * This is the player side of vehicle ownership removal. - * @see `SpecialCaseVehicleDespawn(Player, Vehicle)` * @param tplayer the player */ def DisownVehicle(tplayer : Player) : Option[Vehicle] = { @@ -5349,7 +5391,7 @@ class WorldSessionActor extends Actor with MDCContextAware { tplayer.VehicleOwned = None continent.GUID(vehicle_guid) match { case Some(vehicle : Vehicle) => - SpecialCaseVehicleDespawn(tplayer, vehicle) + DisownVehicle(tplayer, vehicle) case _ => None } @@ -5359,35 +5401,25 @@ class WorldSessionActor extends Actor with MDCContextAware { } /** - * Disassociate a vehicle from the player that owns it, if that player really was the previous owner. + * Disassociate a player from a vehicle that he owns without associating a different player as the owner. + * Set the vehicle's driver seat permissions and passenger and gunner seat permissions to "allow empire," + * then reload them for all clients. * This is the vehicle side of vehicle ownership removal. - * Additionally, start the vehicle deconstruction timer if conditions are valid. - * @see `DisownVehicle(Player)` * @param tplayer the player - * @param vehicle the discovered vehicle */ - private def SpecialCaseVehicleDespawn(tplayer : Player, vehicle : Vehicle) : Option[Vehicle] = { - if(vehicle.Owner.contains(tplayer.GUID)) { - vehicle.Owner = None - vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(vehicle), continent)) - vehicle.CargoHolds.values - .collect { case hold if hold.isOccupied => SpecialCaseVehicleDespawn(player, hold.Occupant.get) } - continent.GUID(vehicle.MountedIn) match { - case Some(ferry : Vehicle) => - HandleDismountVehicleCargo( - PlanetSideGUID(0), - vehicle.GUID, - vehicle, - ferry.GUID, - ferry, - false, - false, - false - ) - vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent, Some(0 seconds))) //instant vehicle decay - case _ => - vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent, vehicle.Definition.DeconstructionTime)) //normal vehicle decay - } + private def DisownVehicle(tplayer : Player, vehicle : Vehicle) : Option[Vehicle] = { + val pguid = tplayer.GUID + if(vehicle.Owner.contains(pguid)) { + vehicle.AssignOwnership(None) + val factionOnContinent = s"${continent.Id}/${vehicle.Faction}" + vehicleService ! VehicleServiceMessage(factionOnContinent, VehicleAction.Ownership(pguid, PlanetSideGUID(0))) + val vguid = vehicle.GUID + val empire = VehicleLockState.Empire.id + (0 to 2).foreach(group => { + vehicle.PermissionGroup(group, empire) + vehicleService ! VehicleServiceMessage(factionOnContinent, VehicleAction.SeatPermissions(pguid, vguid, group, empire)) + }) + ReloadVehicleAccessPermissions(vehicle) Some(vehicle) } else { @@ -6629,8 +6661,7 @@ class WorldSessionActor extends Actor with MDCContextAware { player.VehicleSeated = guid if(seat == 0) { //if driver - player.VehicleOwned = guid - vehicle.Owner = player.GUID + OwnVehicle(vehicle, player) vehicle.CargoHolds.values .collect { case hold if hold.isOccupied => hold.Occupant.get } .foreach { _.MountedIn = guid } @@ -7521,13 +7552,11 @@ class WorldSessionActor extends Actor with MDCContextAware { } removed match { case Some(telepad : TelepadDeployable) => - telepad.Owner = None - telepad.OwnerName = None + telepad.AssignOwnership(None) localService ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), continent)) localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, continent, Some(0 seconds))) //normal decay case Some(old) => - old.Owner = None - old.OwnerName = None + old.AssignOwnership(None) localService ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(old), continent)) localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(old, continent, Some(0 seconds))) if(msg) { //max test @@ -7959,7 +7988,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param tplayer the target player being moved around; * not necessarily the same player as the `WorldSessionActor`-global `player` * @param zone_id the zone in which the player will be placed - * @return a tuple composed of an ActorRef` destination and a message to send to that destination + * @return a tuple composed of an `ActorRef` destination and a message to send to that destination */ def LoadZoneAsPlayer(tplayer : Player, zone_id : String) : (ActorRef, Any) = { if(zone_id == continent.Id) { @@ -8058,12 +8087,12 @@ class WorldSessionActor extends Actor with MDCContextAware { hold.Occupant.get } occupiedCargoHolds.foreach{ cargo => - cargo.Seat(0) match { - case Some(seat) if seat.isOccupied => - vehicleService ! VehicleServiceMessage(s"${seat.Occupant.get.Name}", VehicleAction.TransferPassengerChannel(pguid, s"${cargo.Actor}", toChannel, cargo)) + cargo.Seats(0).Occupant match { + case Some(occupant) => + vehicleService ! VehicleServiceMessage(s"${occupant.Name}", VehicleAction.TransferPassengerChannel(pguid, s"${cargo.Actor}", toChannel, cargo)) case _ => log.error("LoadZoneInVehicleAsDriver: abort; vehicle in cargo hold missing driver") - SpecialCaseVehicleDespawn(player, cargo) + HandleDismountVehicleCargo(player.GUID, cargo.GUID, cargo, vehicle.GUID, vehicle, false, false, true) } } // @@ -8486,6 +8515,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some((mountPoint, hold)) => cargo.MountedIn = None hold.Occupant = None + val driverOpt = cargo.Seats(0).Occupant val rotation : Vector3 = if(CargoOrientation(cargo) == 1) { //TODO: BFRs will likely also need this set //dismount router "sideways" in a lodestar carrier.Orientation.xy + Vector3.z((carrier.Orientation.z - 90) % 360) @@ -8511,11 +8541,16 @@ class WorldSessionActor extends Actor with MDCContextAware { val resetCargoMsg = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0) sendResponse(ejectCargoMsg) //dismount vehicle on UI and disable "shield" effect on lodestar sendResponse(detachCargoMsg) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player_guid, ejectCargoMsg)) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player_guid, detachCargoMsg)) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(PlanetSideGUID(0), resetCargoMsg)) //lazy + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SendResponse(player_guid, ejectCargoMsg)) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SendResponse(player_guid, detachCargoMsg)) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SendResponse(PlanetSideGUID(0), resetCargoMsg)) //lazy log.debug(ejectCargoMsg.toString) log.debug(detachCargoMsg.toString) + if(driverOpt.isEmpty) { + //TODO cargo should drop like a rock like normal; until then, deconstruct it + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(cargo), continent)) + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(cargo, continent, Some(0 seconds))) + } } else { //the carrier vehicle is not flying; just open the door and let the cargo vehicle back out; force it out if necessary @@ -8525,19 +8560,23 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(cargoDetachMessage) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player_guid, cargoStatusMessage)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player_guid, cargoDetachMessage)) - if(kicked) { - cargo.Seat(0).get.Occupant match { - case Some(driver) => - vehicleService ! VehicleServiceMessage(s"${driver.Name}", VehicleAction.KickCargo(player_guid, cargo, cargo.Definition.AutoPilotSpeed2)) - case None => - //driverless vehicle will get cleaned up - } + driverOpt match { + case Some(driver) => + if(kicked) { + vehicleService ! VehicleServiceMessage(s"${driver.Name}", VehicleAction.KickCargo(player_guid, cargo, cargo.Definition.AutoPilotSpeed2, 4500)) + } + import scala.concurrent.duration._ + import scala.concurrent.ExecutionContext.Implicits.global + //check every quarter second if the vehicle has moved far enough away to be considered dismounted + cargoDismountTimer.cancel + cargoDismountTimer = context.system.scheduler.scheduleOnce(250 milliseconds, self, CheckCargoDismount(cargoGUID, carrierGUID, mountPoint, iteration = 0)) + case None => + val resetCargoMsg = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SendResponse(PlanetSideGUID(0), resetCargoMsg)) //lazy + //TODO cargo should back out like normal; until then, deconstruct it + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(cargo), continent)) + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(cargo, continent, Some(0 seconds))) } - import scala.concurrent.duration._ - import scala.concurrent.ExecutionContext.Implicits.global - //check every quarter second if the vehicle has moved far enough away to be considered dismounted - cargoDismountTimer.cancel - cargoDismountTimer = context.system.scheduler.scheduleOnce(250 milliseconds, self, CheckCargoDismount(cargoGUID, carrierGUID, mountPoint, iteration = 0)) } StopBundlingPackets() @@ -8546,6 +8585,28 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + /** + * na + * @param player_guid na + * @param vehicle_guid na + * @param bailed na + * @param requestedByPassenger na + * @param kicked na + */ + def DismountVehicleCargo(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) : Unit = { + continent.GUID(vehicle_guid) match { + case Some(cargo : Vehicle) => + continent.GUID(cargo.MountedIn) match { + case Some(ferry : Vehicle) => + HandleDismountVehicleCargo(player_guid, vehicle_guid, cargo, ferry.GUID, ferry, bailed, requestedByPassenger, kicked) + case _ => + log.warn(s"DismountVehicleCargo: target ${cargo.Definition.Name}@$vehicle_guid does not know what treats it as cargo") + } + case _ => + log.warn(s"DismountVehicleCargo: target $vehicle_guid either is not a vehicle in ${continent.Id} or does not exist") + } + } + def GetPlayerHackSpeed(obj: PlanetSideServerObject with Hackable): Float = { val playerHackLevel = GetPlayerHackLevel() val timeToHack = obj.HackDuration(playerHackLevel)