Vehicle Gating with Passengers (#1072)

* guarding against match errors in various places; increased frequency of psm array purge; fixed issues with seated passengers after vehicle gating

* due to increased frequency of clearing psm data, changed from Array to LongMap
This commit is contained in:
Fate-JH 2023-05-02 15:09:17 -04:00 committed by GitHub
parent a5a232ffdc
commit 66f45edcd3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 61 additions and 44 deletions

View file

@ -717,6 +717,7 @@ class ChatActor(
sessionActor ! SessionActor.SendResponse( sessionActor ! SessionActor.SendResponse(
message.copy(messageType = UNK_229, contents = "@CMT_ZONE_usage") message.copy(messageType = UNK_229, contents = "@CMT_ZONE_usage")
) )
case _ => ()
} }
case (CMT_WARP, _, contents) if gmCommandAllowed => case (CMT_WARP, _, contents) if gmCommandAllowed =>

View file

@ -5,6 +5,7 @@ import akka.actor.typed.scaladsl.adapter._
import akka.actor.{ActorContext, typed} import akka.actor.{ActorContext, typed}
import net.psforever.services.Service import net.psforever.services.Service
import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._ import scala.concurrent.duration._
// //
@ -32,9 +33,8 @@ class SessionAvatarHandlers(
implicit val context: ActorContext implicit val context: ActorContext
) extends CommonSessionInterfacingFunctionality { ) extends CommonSessionInterfacingFunctionality {
//TODO player characters only exist within a certain range of GUIDs for a given zone; this is overkill //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] = { private[support] var lastSeenStreamMessage: mutable.LongMap[SessionAvatarHandlers.LastUpstream] =
SessionAvatarHandlers.blankUpstreamMessages(65535) mutable.LongMap[SessionAvatarHandlers.LastUpstream]()
}
private[this] val hidingPlayerRandomizer = new scala.util.Random private[this] val hidingPlayerRandomizer = new scala.util.Random
/** /**
@ -73,8 +73,8 @@ class SessionAvatarHandlers(
canSeeReallyFar canSeeReallyFar
) if isNotSameTarget => ) if isNotSameTarget =>
val pstateToSave = pstate.copy(timestamp = 0) val pstateToSave = pstate.copy(timestamp = 0)
val (lastMsg, lastTime, lastPosition, wasVisible) = lastSeenStreamMessage(guid.guid) match { val (lastMsg, lastTime, lastPosition, wasVisible) = lastSeenStreamMessage.get(guid.guid) match {
case SessionAvatarHandlers.LastUpstream(Some(msg), visible, time) => (Some(msg), time, msg.pos, visible) case Some(SessionAvatarHandlers.LastUpstream(Some(msg), visible, time)) => (Some(msg), time, msg.pos, visible)
case _ => (None, 0L, Vector3.Zero, false) case _ => (None, 0L, Vector3.Zero, false)
} }
val drawConfig = Config.app.game.playerDraw //m val drawConfig = Config.app.game.playerDraw //m
@ -129,10 +129,10 @@ class SessionAvatarHandlers(
isCloaking isCloaking
) )
) )
lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, now) lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, now))
} else { } else {
//is visible, but skip reinforcement //is visible, but skip reinforcement
lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, lastTime) lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, lastTime))
} }
} else { } else {
//conditions where the target is not currently visible //conditions where the target is not currently visible
@ -151,10 +151,10 @@ class SessionAvatarHandlers(
is_cloaked = isCloaking is_cloaked = isCloaking
) )
) )
lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, now) lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, now))
} else { } else {
//skip drawing altogether //skip drawing altogether
lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, lastTime) lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, lastTime))
} }
} }
@ -549,7 +549,7 @@ class SessionAvatarHandlers(
continent.GUID(weaponGuid).collect { continent.GUID(weaponGuid).collect {
case tool: Tool if tool.Magazine == 0 => case tool: Tool if tool.Magazine == 0 =>
// check that the magazine is still empty before sending WeaponDryFireMessage // check that the magazine is still empty before sending WeaponDryFireMessage
// if it has been reloaded since then, other clients not see it firing // if it has been reloaded since then, other clients will not see it firing
sendResponse(WeaponDryFireMessage(weaponGuid)) sendResponse(WeaponDryFireMessage(weaponGuid))
case _ => case _ =>
sendResponse(WeaponDryFireMessage(weaponGuid)) sendResponse(WeaponDryFireMessage(weaponGuid))
@ -562,8 +562,4 @@ class SessionAvatarHandlers(
object SessionAvatarHandlers { object SessionAvatarHandlers {
private[support] case class LastUpstream(msg: Option[AvatarResponse.PlayerState], visible: Boolean, time: Long) private[support] case class LastUpstream(msg: Option[AvatarResponse.PlayerState], visible: Boolean, time: Long)
private[support] def blankUpstreamMessages(n: Int): Array[LastUpstream] = {
Array.fill[SessionAvatarHandlers.LastUpstream](n)(elem = LastUpstream(None, visible=false, 0L))
}
} }

View file

@ -732,18 +732,19 @@ class ZoningOperations(
(manifest.passengers.find { (manifest.passengers.find {
_.name.equals(playerName) _.name.equals(playerName)
} match { } match {
case Some(entry) if vehicle.Seats(entry.mount).occupant.isEmpty => case Some(entry) if vehicle.Seat(entry.mount).flatMap { _.occupant }.isEmpty =>
player.VehicleSeated = None player.VehicleSeated = None
vehicle.Seats(entry.mount).mount(player) vehicle.Seats(entry.mount).mount(player)
player.VehicleSeated = vehicle.GUID player.VehicleSeated = vehicle.GUID
Some(vehicle) Some((None, Some(vehicle)))
case Some(entry) if vehicle.Seats(entry.mount).occupant.contains(player) => case Some(entry) if vehicle.Seat(entry.mount).flatMap { _.occupant }.contains(player) =>
Some(vehicle) player.VehicleSeated = vehicle.GUID
Some((None, Some(vehicle)))
case Some(entry) => case Some(entry) =>
log.warn( log.warn(
s"TransferPassenger: $playerName tried to mount seat ${entry.mount} during summoning, but it was already occupied, and ${player.Sex.pronounSubject} was rebuked" s"TransferPassenger: $playerName tried to mount seat ${entry.mount} during summoning, but it was already occupied, and ${player.Sex.pronounSubject} was rebuked"
) )
None Some((None, None))
case None => case None =>
//log.warn(s"TransferPassenger: $playerName is missing from the manifest of a summoning ${vehicle.Definition.Name} from ${vehicle.Zone.id}") //log.warn(s"TransferPassenger: $playerName is missing from the manifest of a summoning ${vehicle.Definition.Name} from ${vehicle.Zone.id}")
None None
@ -753,8 +754,8 @@ class ZoningOperations(
} match { } match {
case Some(entry) => case Some(entry) =>
vehicle.CargoHolds(entry.mount).occupant match { vehicle.CargoHolds(entry.mount).occupant match {
case out@Some(cargo) if cargo.Seats(0).occupants.exists(_.Name.equals(playerName)) => case out @ Some(cargo) if cargo.Seats(0).occupants.exists(_.Name.equals(playerName)) =>
out Some((Some(vehicle), out))
case _ => case _ =>
None None
} }
@ -762,13 +763,11 @@ class ZoningOperations(
None None
} }
} match { } match {
case Some(cargo: Vehicle) => case Some((Some(ferry), Some(cargo))) =>
galaxyService ! Service.Leave(Some(temp_channel)) //temporary vehicle-specific channel (see above) cargo.MountedIn = ferry.GUID
spawn.deadState = DeadState.Release handleTransferPassengerVehicle(cargo, temp_channel)
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, unk5=true)) case Some((None, Some(_: Vehicle))) =>
interstellarFerry = Some(cargo) //on the other continent and registered to that continent's GUID system handleTransferPassengerVehicle(vehicle, temp_channel)
cargo.MountedIn = vehicle.GUID
spawn.LoadZonePhysicalSpawnPoint(cargo.Continent, cargo.Position, cargo.Orientation, respawnTime = 1 seconds, None)
case _ => case _ =>
interstellarFerry match { interstellarFerry match {
case None => case None =>
@ -779,6 +778,14 @@ class ZoningOperations(
} }
} }
private def handleTransferPassengerVehicle(vehicle: Vehicle, temporaryChannel: String): Unit = {
galaxyService ! Service.Leave(Some(temporaryChannel)) //temporary vehicle-specific channel (see above)
spawn.deadState = DeadState.Release
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, unk5=true))
interstellarFerry = Some(vehicle) //on the other continent and registered to that continent's GUID system
spawn.LoadZonePhysicalSpawnPoint(vehicle.Continent, vehicle.Position, vehicle.Orientation, respawnTime = 1 seconds, None)
}
def handleDroppodLaunchDenial(errorCode: DroppodError): Unit = { def handleDroppodLaunchDenial(errorCode: DroppodError): Unit = {
sendResponse(DroppodLaunchResponseMessage(errorCode, player.GUID)) sendResponse(DroppodLaunchResponseMessage(errorCode, player.GUID))
} }
@ -1446,7 +1453,6 @@ class ZoningOperations(
Deployables.Disown(continent, avatar, context.self) Deployables.Disown(continent, avatar, context.self)
spawn.drawDeloyableIcon = spawn.RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone spawn.drawDeloyableIcon = spawn.RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone
sessionData.squad.squadSetup = sessionData.squad.ZoneChangeSquadSetup sessionData.squad.squadSetup = sessionData.squad.ZoneChangeSquadSetup
sessionData.avatarResponse.lastSeenStreamMessage = SessionAvatarHandlers.blankUpstreamMessages(65535)
} }
/** /**
@ -1923,6 +1929,7 @@ class ZoningOperations(
if (zoningStatus == Zoning.Status.Deconstructing) { if (zoningStatus == Zoning.Status.Deconstructing) {
sessionData.stopDeconstructing() sessionData.stopDeconstructing()
} }
sessionData.avatarResponse.lastSeenStreamMessage.clear()
upstreamMessageCount = 0 upstreamMessageCount = 0
setAvatar = false setAvatar = false
sessionData.persist() sessionData.persist()
@ -1956,6 +1963,7 @@ class ZoningOperations(
if (zoningStatus == Zoning.Status.Deconstructing) { if (zoningStatus == Zoning.Status.Deconstructing) {
sessionData.stopDeconstructing() sessionData.stopDeconstructing()
} }
sessionData.avatarResponse.lastSeenStreamMessage.clear()
upstreamMessageCount = 0 upstreamMessageCount = 0
setAvatar = false setAvatar = false
sessionData.persist() sessionData.persist()

View file

@ -804,8 +804,8 @@ object WorldSession {
val name = definition.Name val name = definition.Name
GlobalDefinitions.isGrenade(definition) && (name.contains("frag") || name.contains("plasma")) GlobalDefinitions.isGrenade(definition) && (name.contains("frag") || name.contains("plasma"))
} match { } match {
case Some(InventoryItem(equipment, slotNum)) =>Some(equipment.asInstanceOf[Tool], slotNum) case Some(InventoryItem(equipment, slotNum)) => Some(equipment.asInstanceOf[Tool], slotNum)
case None => None case _ => None
} }
} }
} }

View file

@ -86,7 +86,7 @@ class GridInventory extends Container {
items.values.find({ case InventoryItem(obj, _) => obj.HasGUID && obj.GUID == guid }) match { items.values.find({ case InventoryItem(obj, _) => obj.HasGUID && obj.GUID == guid }) match {
case Some(InventoryItem(_, index)) => case Some(InventoryItem(_, index)) =>
Some(index) Some(index)
case None => case _ =>
None None
} }
} }

View file

@ -69,7 +69,7 @@ trait SectorTraits {
* @param eqFunc a custom equivalence function to distinguish between the entities in the list * @param eqFunc a custom equivalence function to distinguish between the entities in the list
* @tparam A the type of object that will be the entities stored in the list * @tparam A the type of object that will be the entities stored in the list
*/ */
class SectorListOf[A](eqFunc: (A, A) => Boolean = (a: A, b: A) => a equals b) { class SectorListOf[A](eqFunc: (A, A) => Boolean = (a: A, b: A) => a == b) {
private val internalList: ListBuffer[A] = ListBuffer[A]() private val internalList: ListBuffer[A] = ListBuffer[A]()
/** /**

View file

@ -59,6 +59,7 @@ object LoginMessage extends Marshallable[LoginMessage] {
a match { a match {
case username :: Some(password) :: None :: HNil => Left(username :: password :: HNil) case username :: Some(password) :: None :: HNil => Left(username :: password :: HNil)
case username :: None :: Some(token) :: HNil => Right(token :: username :: HNil) case username :: None :: Some(token) :: HNil => Right(token :: username :: HNil)
case username :: _ :: _ :: HNil => Right(username :: "" :: HNil) //this will fail
} }
either(bool, passwordPath, tokenPath).xmap[Struct](from, to) either(bool, passwordPath, tokenPath).xmap[Struct](from, to)

View file

@ -166,6 +166,9 @@ object PropertyOverrideMessage extends Marshallable[PropertyOverrideMessage] {
case target :: _ :: Some(first) :: Some(other) :: HNil => case target :: _ :: Some(first) :: Some(other) :: HNil =>
GamePropertyTarget(target, first +: other) GamePropertyTarget(target, first +: other)
case target :: _ :: _ :: Some(other) :: HNil =>
GamePropertyTarget(target, other)
}, },
{ {
case GamePropertyTarget(target, list) => case GamePropertyTarget(target, list) =>
@ -202,6 +205,9 @@ object PropertyOverrideMessage extends Marshallable[PropertyOverrideMessage] {
case zone :: _ :: Some(first) :: Some(other) :: HNil => case zone :: _ :: Some(first) :: Some(other) :: HNil =>
GamePropertyScope(zone, first +: other) GamePropertyScope(zone, first +: other)
case zone :: _ :: None :: Some(other) :: HNil =>
GamePropertyScope(zone, other)
}, },
{ {
case GamePropertyScope(zone, list) => case GamePropertyScope(zone, list) =>

View file

@ -48,7 +48,7 @@ object InventoryData {
} }
).xmap[InventoryData]( ).xmap[InventoryData](
{ {
case _ :: 0 :: c :: HNil => case _ :: _ :: c :: HNil =>
InventoryData(c) InventoryData(c)
}, },
{ {

View file

@ -29,7 +29,7 @@ object LittleBuddyProjectileData extends Marshallable[LittleBuddyProjectileData]
("unk4" | bool) ("unk4" | bool)
).exmap[LittleBuddyProjectileData]( ).exmap[LittleBuddyProjectileData](
{ {
case data :: u2 :: 31 :: u4 :: HNil => case data :: u2 :: _ :: u4 :: HNil =>
Attempt.successful(LittleBuddyProjectileData(data, u2, u4)) Attempt.successful(LittleBuddyProjectileData(data, u2, u4))
}, },
{ {

View file

@ -3,10 +3,10 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.PacketHelpers import net.psforever.packet.PacketHelpers
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID
import scodec.Attempt.Successful import scodec.Attempt.{Failure, Successful}
import scodec.Codec import scodec.{Codec, Err}
import scodec.codecs._ import scodec.codecs._
import shapeless.HNil //note: do not import shapeless.:: at top level; it messes up List's :: functionality import shapeless.HNil
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
@ -222,6 +222,9 @@ object MountableInventory {
case _ :: slot :: Some(next) :: HNil => case _ :: slot :: Some(next) :: HNil =>
Successful(Some(InventorySeat(slot, next))) Successful(Some(InventorySeat(slot, next)))
case _ =>
Failure(Err("bad seat decoding"))
}, },
{ {
case None => case None =>

View file

@ -147,7 +147,7 @@ object ObjectCreateBase {
cls :: guid :: None :: HNil cls :: guid :: None :: HNil
}, },
{ {
case cls :: guid :: None :: HNil => case cls :: guid :: _ :: HNil =>
cls :: guid :: HNil cls :: guid :: HNil
} }
) )
@ -157,17 +157,19 @@ object ObjectCreateBase {
*/ */
private val parent: Codec[parentPattern] = ( private val parent: Codec[parentPattern] = (
("parentGuid" | PlanetSideGUID.codec) :: //16u ("parentGuid" | PlanetSideGUID.codec) :: //16u
("objectClass" | uintL(0xb)) :: //11u ("objectClass" | uintL(0xb)) :: //11u
("guid" | PlanetSideGUID.codec) :: //16u ("guid" | PlanetSideGUID.codec) :: //16u
("parentSlotIndex" | PacketHelpers.encodedStringSize) //8u or 16u ("parentSlotIndex" | PacketHelpers.encodedStringSize) //8u or 16u
).xmap[parentPattern]( ).exmap[parentPattern](
{ {
case pguid :: cls :: guid :: slot :: HNil => case pguid :: cls :: guid :: slot :: HNil =>
cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil Attempt.Successful(cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil)
}, },
{ {
case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil => case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil =>
pguid :: cls :: guid :: slot :: HNil Attempt.Successful(pguid :: cls :: guid :: slot :: HNil)
case _ =>
Attempt.Failure(Err("expecting parent data, but found none"))
} }
) )

View file

@ -642,7 +642,7 @@ class SquadService extends Actor {
val squad = features.Squad val squad = features.Squad
LeaveSquad(squad.Membership(position).CharId, features) LeaveSquad(squad.Membership(position).CharId, features)
out out
case None => case _ =>
None None
} }
case FindLfsSoldiersForRole(_) => case FindLfsSoldiersForRole(_) =>